22

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.

Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
Gregyski
  • 1,707
  • 3
  • 18
  • 26

3 Answers3

57

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>
Nir
  • 29,306
  • 10
  • 67
  • 103
  • Thanks to Nir's answer, I was able to fulfill the optional criterion. Details are in the answer I've posted to this question. It was recommended on Meta that I handle this situation this way. http://meta.stackexchange.com/questions/14306/my-improved-answer-based-on-anothers-accepted-answer-for-my-own-question – Gregyski Aug 17 '09 at 23:53
  • Thanks! This is just what I was missing for spacing items and their panel evenly in a listbox. – Caleb S Mar 15 '13 at 01:47
2

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.

Spooky
  • 2,966
  • 8
  • 27
  • 41
Gregyski
  • 1,707
  • 3
  • 18
  • 26
0
<!-- width must be explicitly set for this example -->
<StackPanel 
    Name="MyStack" 
    Orientation="Horizontal"
    Width="250"
    Load="Window_Loaded"> 
    <Button/>
    <Button/>
    <Button/>
</StackPanel>

public void Window_Loaded(object sender, RoutedEventArgs e)
{
    UIElementCollection elements = MyStack.Children;
    int count = elements.Count;

    foreach (UIElement element in elements)
    {
        if (element is Button)
            ((Button)element).Width = MyStack.Width / count;
    }
}
golden_eagle
  • 90
  • 2
  • 9
  • I edited my post only 20 minutes before you responded to explain that I wanted to avoid explicitly setting the width. I'm sorry I should have thought to include that requirement initially. Explicitly managing the dimensions like this (and by handling resize messages as well) may be necessary for my secondary requirement, if WrapPanel were to be used. Thanks. – Gregyski Aug 13 '09 at 15:11