简体   繁体   中英

C#/WPF - Equally stretch buttons in nested ItemsControl to fill in entire space

First of all, I have a ItemsControl , which aims to display several groups of contents; inside each group, there is another ItemsControl , which aims to display serveral Button inside.

My .xaml:

<Window x:Class="CX11TestSolution.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="768"
    Width="1024">
<ItemsControl ItemsSource="{Binding HahaList}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Label Content="{Binding Name}" />
                <ItemsControl Grid.Row="1"
                              ItemsSource="{Binding ItemList}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <UniformGrid Columns="5" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Button Content="{Binding Name}" />
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Columns="1" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

My .xaml.cs:

public partial class MainWindow : Window
{
    public IEnumerable<SomeClass> HahaList { get; }
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        HahaList = new List<SomeClass>()
        {
            new SomeClass("Group 1", new List<SomeClass>() {
                new SomeClass("11"),
                new SomeClass("12"),
                new SomeClass("13"),
                new SomeClass("14"),
                new SomeClass("15"),
                new SomeClass("16"),
            }),
            new SomeClass("Group 2", new List<SomeClass>() {
                new SomeClass("21"),
                new SomeClass("22"),
                new SomeClass("23"),
                new SomeClass("24"),
            }),
            new SomeClass("Group 3", new List<SomeClass>() {
                new SomeClass("31"),
                new SomeClass("32"),
                new SomeClass("33"),
            }),
            new SomeClass("Group 4", new List<SomeClass>() {
                new SomeClass("41"),
                new SomeClass("42"),
            }),
            new SomeClass("Group 5", new List<SomeClass>() {
                new SomeClass("52"),
            }),
        };
    }
}

public class SomeClass
{
    public string Name { get; }
    public IEnumerable<SomeClass> ItemList { get; }
    public SomeClass(string name, IEnumerable<SomeClass> list)
    {
        Name = name;
        ItemList = list;
    }
    public SomeClass(string name)
    {
        Name = name;
    }
}

Result: 结果

So my question is that how do I equally stretch the height of the buttons so that they will fill in the entire area?

I have actually tried to set the ItemsPanel of the parent ItemsControl to a UniformGrid which has a Columns of 1, and it does work when the number of buttons in each group is no more than 5. When it exceeds 5, the sixth button will jump to the next row, thus shrinking the first row to make each group have equal height. 在此处输入图片说明 Thank you in advance.

I would do this manually with a grid. You know there are a max of 5 columns so it's easy to work out exactly how many columns and rows you'll need for a grid that encompasses everything. Use an attached behaviour so that you can set the grid's rows and columns dynamically at runtime, you'll also want a flag to specify whether each row is an "Auto" (headers) or a "1*" (equally spaced). Last, add fields to SomeClass for the Grid column and row number and bind to that in your XAML.

There's probably a way of doing this in pure XAML or with a custom layout class but the method I describe above should only take 10 mins or so to code up and it's easy to test and debug, so you'll probably save yourself a lot of headache in the long run.

UPDATE:

I've just implemented my method of doing this, it requires you to add this to your main view model:

    public int RowCount { get; set; }
    public int ColumnCount {get; set; }     
    public string StarCount { get; set; }
    public string StarRows { get; set; }
    public string StarColumns { get; set; }
    public List<Tuple<SomeClass, bool>> AllItems { get; set; } // second parameter indicates whether it's a header


    private void UpdateGridLayout()
    {
        this.AllItems = new List<Tuple<SomeClass, bool>>();

        this.ColumnCount = 5;
        this.RowCount = HahaList.Sum(x => 1 + (x.ItemList.Count() + this.ColumnCount - 1) / this.ColumnCount);
        int row = 0;
        this.StarColumns = String.Join(",", Enumerable.Range(0, this.ColumnCount).Select(i => i.ToString())); // all columns
        this.StarRows = null;
        foreach (var section in this.HahaList)
        {
            this.AllItems.Add(new Tuple<SomeClass, bool>(section, true));
            section.Row = row;
            section.Column = 0;
            section.ColumnSpan = this.ColumnCount;

            row++;
            if (StarRows != null)
                StarRows += ",";
            StarRows += row.ToString();
            int column = 0;             
            foreach (var item in section.ItemList)
            {
                this.AllItems.Add(new Tuple<SomeClass, bool>(item, false));
                item.Row = row;
                item.Column = column++;
                item.ColumnSpan = 1;
                if (column >= this.ColumnCount)
                {                       
                    column = 0;
                    row++;
                    if (StarRows != null)
                        StarRows += ",";
                    StarRows += row.ToString();
                }

            }
            row++;
        }
        return;
    }

UpdateGridLayout needs to be called after you've set your HahaList, if you want this to support dynamic changes then obviously you'll have to go through it all and add INPC. One notable difference in this code is the addition of the "AllItems" property which has both headers and leaf nodes along with a flag indicating which type each one is (I would have used different classes myself so that you don't have to do this).

SomeClass also needs a few extra properties:

    public int Row { get; set; }
    public int Column { get; set; }
    public int ColumnSpan { get; set; }

And here's the XAML:

<ItemsControl ItemsSource="{Binding AllItems}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid local:GridHelpers.RowCount="{Binding RowCount}"
                  local:GridHelpers.ColumnCount="{Binding ColumnCount}"
                  local:GridHelpers.StarRows="{Binding StarRows}"
                  local:GridHelpers.StarColumns="{Binding StarColumns}"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Grid.Row" Value="{Binding Item1.Row}" />
            <Setter Property="Grid.Column" Value="{Binding Item1.Column}" />
            <Setter Property="Grid.ColumnSpan" Value="{Binding Item1.ColumnSpan}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ContentControl>
                <ContentControl.Style>
                    <Style TargetType="ContentControl">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate>
                                    <Label Content="{Binding Item1.Name}" />
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Item2}" Value="false">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate>
                                            <Button Content="{Binding Item1.Name}" />
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </ContentControl.Style>
            </ContentControl>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The XAML uses a class called GridHelpers which assists in creating dynamic rows and columns, you can get the source code from Rachael Lim's Blog .

Result:

在此处输入图片说明

This is where a Viewbox can be used, but it comes with side effects.

Change MinWidth on the button to get the Label to Button ratio what you like because the design will suffer when the windows shrinks, but does okay when size increases.

使用者介面范例

Code

<Viewbox Stretch="Fill">
    <ItemsControl ItemsSource="{Binding HahaList}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Label Content="{Binding Name}"/>
                    <ItemsControl Grid.Row="1"
                          ItemsSource="{Binding ItemList}">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <UniformGrid Columns="5"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Button Content="{Binding Name}" MinWidth="75"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</Viewbox>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM