Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stretching controls to fill ItemsControl

Tags:

I am attempting to write a simple WPF learning project which creates a set of buttons inside a resizeable main window. There is to be one Button per entry in a collection, and the contents of that collection may vary during run-time. I want the buttons to fill the entire window (e.g. 1 button @ 100% width, 2 buttons @ 50% width, 3 buttons @ 33% width, etc. all at 100% height). A simplified version of what I've written so far is:

<ItemsControl x:Name="itemscontrolButtons" ItemsSource="{Binding}">   <ItemsControl.ItemTemplate>     <DataTemplate>       <Button Tag="{Binding}">         <TextBlock Text="{Binding}" />       </Button>     </DataTemplate>   </ItemsControl.ItemTemplate>   <ItemsControl.ItemsPanel>     <ItemsPanelTemplate>       <StackPanel Orientation="Horizontal" />     </ItemsPanelTemplate>   </ItemsControl.ItemsPanel> </ItemsControl> ...     List<string> listButtonTexts = new List<string>() { "Button1", "Button2" }; ... itemscontrolButtons.DataContext = listButtonTexts; 

This results in this:

alt text

I have been unable to make the buttons stretch to fit the width and my attempts to use a Grid instead of StackPanel were fruitless.

Then, as an optional improvement, I would appreciate suggestions on how to adjust it such that if there are so many buttons that they cannot fit properly on a line or are narrower than a threshold, it will wrap onto a new line (thereby halving the button heights if going from 1 to 2 rows).

I'd like to emphasize that I'd like to know how to do this the WPF way. I realize I can consume window resize messages and resize the controls explicitly, and that's how I'd have done it with MFC or WinForms, but from what I've read that is not how such things should be done with WPF.

like image 409
Gregyski Avatar asked Aug 13 '09 04:08

Gregyski


2 Answers

Replace the item control's panel with a UniformGrid, this will size all children so everything fits exactly:

<ItemsControl>     <ItemsControl.ItemsPanel>         <ItemsPanelTemplate>             <UniformGrid Rows="1"/>         </ItemsPanelTemplate>     </ItemsControl.ItemsPanel> </ItemsControl> 
like image 174
Nir Avatar answered Oct 08 '22 18:10

Nir


I was able to build on Nir's answer to fulfill my optional criterion allowing for WPF-managed stretching with multiple rows.

First you must be able to alter the properties of the template UniformGrid. To do that you need to store a reference to it. There are multiple methods for accomplishing this. I chose to handle the Loaded event for the UniformGrid and store a reference at that time.

<ItemsControl ItemsSource="{Binding}">   <ItemsControl.ItemsPanel>     <ItemsPanelTemplate>       <UniformGrid Rows="1" Loaded="UniformGrid_Loaded" />     </ItemsPanelTemplate>   </ItemsControl.ItemsPanel> </ItemsControl> 

And in the code behind:

UniformGrid uniformgridButtons; private void UniformGrid_Loaded(object sender, RoutedEventArgs e) {   uniformgridButtons = sender as UniformGrid; } 

Then, handle the SizeChanged event and adjust the rows parameter according to whatever criteria you wish.

private void Window_SizeChanged(object sender, SizeChangedEventArgs e) {   if ((uniformgridButtons != null) && (this.Width > 0))   {     // Set row count according to your requirements     uniformgridButtons.Rows = (int)(1 + ((this.MinWidth * iButtonCount) / this.Width));   } } 

So this allows WPF to manage the stretching like in Nir's answer, but allows you to explicitly adjust the number of rows. The above code gives this result:

alt text

(Animated sample here)


Update 2009/08/28:

I was adjusting my learning application so that the ItemsControl was in a DataTemplate in a separate ResourceDictionary. However, events such as Loaded cannot be handled in an external ResourceDictionary XAML file because it has no code-behind (usually). Therefore, I needed to cache the reference to the UniformGrid using a different technique.

I instead used Alan Haigh's FindItemsPanel method (10th reply). First, I replaced the ItemsControl with :

<ContentPresenter x:Name="contentpresenterButtons" Content="{Binding obscolButtons}" /> 

Then in my ResourceDictionary, I put the ItemsControl in the DataTemplate:

<DataTemplate DataType="{x:Type local:Buttons}">   <ItemsControl ...  </DataTemplate> 

Finally, I stored the reference to the templated UniformGrid as so:

private void Window_Loaded(object sender, RoutedEventArgs e) {   uniformgridButtons = FindItemsPanel<UniformGrid>(contentpresenterButtons);   // Also call the code in Window_SizeChanged which I put in another method } 

This works just the same as my previous solution but without requiring an event handler in the ResourceDictionary.

like image 40
Gregyski Avatar answered Oct 08 '22 17:10

Gregyski