显示动态数量的控件

时间:2017-07-14 11:30:28

标签: c# wpf

我在显示动态数量的控件时遇到问题,这些控件的范围可以是1到9之间的任意数字。因此,如果用户输入为1,控件应该尝试填充屏幕,如果用户输入为2,则两个控件应均匀分割屏幕,如果数字为3,则一个控件将显示在屏幕的前50%,而两个控件应分割屏幕的底部50%等。

我到目前为止提出的解决方案涉及在后面的代码中制作大量网格行和列,根据用户输入,将控件分配给正确的行和列。但是,这种解决方案感觉像是一个黑客,并导致许多不必要的代码。如果我想稍后扩展控件的数量,它也根本不灵活。

我觉得必须有一种更简单的方法来解决这个问题,有什么建议吗?

2 个答案:

答案 0 :(得分:0)

你可以为每一行使用StackPanel,为你的"网格使用一个"像这样

<StackPanel>
    <StackPanel/>
    <StackPanel/>
</StackPanel>

然后,您可以根据需要添加和填充行,以匹配给定配置的布局 - 例如3个控件 - 第1行2个,第2行1个,4个控件 - 第1行2个,第2行2个,等等......

然后,您可以将每行中控件的宽度绑定到一个属性,该属性是该特定行中控件数量的函数。例如,如果连续3个,则每个控件的宽度为1/3。只要在修改行时更新此width属性,控件宽度就会更新以填充可用空间。

要添加其他布局,您只需在布局配置中添加新规则。

答案 1 :(得分:0)

您可以扩展Grid或类似控件,并覆盖自定义逻辑的布局行为,而无需重新发明轮子。

例如,您可以按以下方式创建动态网格控件(它适用于任意数量的子项并自动调整行数和列数):

public class DynamicGrid : Grid
{
    public static readonly DependencyProperty AdjustColumnWidthProperty =
        DependencyProperty.RegisterAttached("AdjustColumnWidth",
            typeof(double),
            typeof(DynamicGrid),
            new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsArrange));
    public static double GetAdjustColumnWidth(DependencyObject d)
    {
        return (double)d.GetValue(AdjustColumnWidthProperty);
    }
    public static void SetAdjustColumnWidth(DependencyObject d, double value)
    {
        d.SetValue(AdjustColumnWidthProperty, value);
    }

    private int getSquareLength(int items)
    {
        double result = Math.Sqrt(items);
        return (int)Math.Ceiling(result);
    }

    private int getColumns(int length)
    {
        return length;
    }

    private int getRows(int length)
    {
        var count = _currentChildrenCount;

        //assume we can have empty row
        var rows = length - 1;

        //if fits the bill - great!
        if (rows * length >= count)
            return rows;
        else
            return rows + 1;
    }

    private int _currentChildrenCount;
    private void OnNumberOfItemsChangedImpl()
    {
        var numOfChildren = _currentChildrenCount;

        using (var d = Dispatcher.DisableProcessing())
        {
            RowDefinitions.Clear();
            ColumnDefinitions.Clear();

            if (numOfChildren > 0)
            {
                var squareLength = getSquareLength(numOfChildren);

                var numOfCols = getColumns(squareLength);
                var numOfRows = getRows(squareLength);

                for (var i = 0; i < numOfRows; i++)
                    RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
                for (var i = 0; i < numOfCols; i++)
                    ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });

                var adjustWidthFactor = 1.0;
                var adjustWidthOnLastRow = numOfChildren < (numOfCols * numOfRows);
                if (adjustWidthOnLastRow)
                {
                    var notEmptySlots = (numOfChildren % numOfCols);
                    adjustWidthFactor = ((double)numOfCols / (double)notEmptySlots);
                }

                int row = 0, col = 0;
                foreach (var view in Children)
                {
                    var cell = (FrameworkElement)view;

                    SetRow(cell, row);
                    SetColumn(cell, col);

                    if (adjustWidthOnLastRow && row == (numOfRows - 1))
                        SetAdjustColumnWidth(cell, adjustWidthFactor);
                    else
                        SetAdjustColumnWidth(cell, 1.0);

                    if (++col >= numOfCols)
                    {
                        col = 0;
                        row++;
                    }
                }
            }
        }
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var toReturn = base.ArrangeOverride(arrangeSize);

        foreach (var view in Children)
        {
            var cell = (FrameworkElement)view;
            var adjustWidthFactor = GetAdjustColumnWidth(cell);

            var bounds = LayoutInformation.GetLayoutSlot(cell);
            var newBounds = new Rect(
                    x: bounds.Width * adjustWidthFactor * GetColumn(cell),
                    y: bounds.Top,
                    width: bounds.Width * adjustWidthFactor,
                    height: bounds.Height
                );

            cell.Arrange(newBounds);
        }

        return toReturn;
    }

    public DynamicGrid()
    {
        _currentChildrenCount = 0;

        LayoutUpdated += (s, e) => {
            if (Children?.Count != _currentChildrenCount)
            {
                _currentChildrenCount = (Children != null) ? Children.Count : 0;
                OnNumberOfItemsChangedImpl();
            }
        };
    }
}

样本用法1: - 静态集合

<local:DynamicGrid Margin="20">
    <Button>one</Button>
    <Button>two</Button>
    <Button>three</Button>
    <Button>four</Button>
    <Button>five</Button>
    <Button>six</Button>
    <Button>seven</Button>
    <Button>eight</Button>
</local:DynamicGrid>

enter image description here

示例用法2: - 使用ItemsControl

<ItemsControl Margin="20">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <local:DynamicGrid />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border Background="Gray" Margin="5">
                <TextBlock Text="{Binding}" 
                               HorizontalAlignment="Center" 
                               VerticalAlignment="Center" />
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsSource>
        <col:ArrayList>
            <sys:String>one</sys:String>
            <sys:String>two</sys:String>
            <sys:String>three</sys:String>
            <sys:String>four</sys:String>
            <sys:String>five</sys:String>
        </col:ArrayList>
    </ItemsControl.ItemsSource>
</ItemsControl>

enter image description here

样本用法3: - 动态收集

<Grid Margin="20">
    <Grid.RowDefinitions>
        <RowDefinition Height="4*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <ItemsControl>
        <ItemsControl.ItemsSource>
            <Binding Path="Value" ElementName="slider">
                <Binding.Converter>
                    <local:CountToCollectionConverter />
                </Binding.Converter>
            </Binding>
        </ItemsControl.ItemsSource>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border Background="Gray" Margin="5">
                    <TextBlock Text="{Binding}" 
                               HorizontalAlignment="Center" 
                               VerticalAlignment="Center" />
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <local:DynamicGrid />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

    <Slider x:Name="slider"
            Grid.Row="1" 
            Minimum="1" 
            Maximum="12" 
            TickFrequency="1" 
            IsSnapToTickEnabled="True"
            VerticalAlignment="Center" />
</Grid>

enter image description here

如何运作

每当更新Grid上的儿童集合时,它会尝试找到与儿童计数最接近的完美正方形。找到后,它会根据计算出的平方长度计算列数和行数;并相应地定义RowDefinitionsColumnDefinitions。如果最后一行剩余空间,则会调整控件的宽度以填充空格。

请注意:由于问题中未指定任何具体规则,我已将此网格自定义为仅调整最后一行中的项目