UniformGrid with per-row MinHeight

时间:2015-06-02 13:05:35

标签: .net wpf layout

我的应用程序在ItemsControl中显示使用UniformGrid作为其ItemsPanel的多个项目。

<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <!-- Arrange all items vertically, distribute the space evenly -->
        <UniformGrid Columns="1"/>
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

现在每个项目将占用尽可能多的空间和全宽。应该是这样的。但每件物品不得小于某个高度,比如说250像素。因此,ItemsControl被置于ScrollViewer内,如果最小高度不再适合屏幕,则会开始滚动(但在此之前填充)。

将每个项目的MinHeight属性设置为250将使其更高但不会更改UniformGrid以任何方式排列它们的方式,因此它们会被剪裁。那不是解决方案。

设置整个ItemsControl或UniformGrid的MinHeight属性确实有效,但我需要根据项目数来计算它。获取更新项数的好地方是覆盖OnItemsChanged代码隐藏中的ItemsControl方法。但这不会给我实际的项目面板。我在另一个项目中有以下代码,但它总是在这里返回null:

ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);

所以这也行不通。

走视觉树可能也不是一个选择,因为它太晚了。我需要在排列项目面板之前设置最小高度,以使其不会闪烁。 (我在尺寸变化时生成复杂的内容,因此必须避免任何后期布局。)

我有什么选择可以获得我需要的东西?

如果有可能,我可以放弃UniformGrid支持别的东西。有什么建议吗?

3 个答案:

答案 0 :(得分:3)

您可以采用许多技术来实现这一目标。然而,方法是扩展UniformGrid。

在这种情况下,解决方案是覆盖MeasureOverride()以根据行中所有子项的最小高度或更大的MinRowHeight来计算新大小。

用法:

<UserControl x:Class="SOF.UniformGridMinHeight"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:sof="clr-namespace:SOF"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<sof:UniformGridEx Columns="3" MinRowHeight="100">
        <Border Background="Red"/>
        <Border Background="Blue"/>
        <Border Background="Green"/>
        <Border Background="Yellow"/>
        <Border Background="Pink"/>
        <Border Background="Purple"/>
        <Border Background="LightCoral"/>
        <Border Background="DimGray"/>
        <Border Background="OrangeRed"/>
        <Border Background="Olive"/>
        <Border Background="Salmon"/>
</sof:UniformGridEx>

扩展UniformGrid:

public class UniformGridEx : UniformGrid
{
    public static readonly DependencyProperty MinRowHeightProperty = DependencyProperty.Register(
        "MinRowHeight", typeof(double), typeof(UniformGrid), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure));

    private int _columns;
    private int _rows;

    public double MinRowHeight
    {
        get { return (double)GetValue(MinRowHeightProperty); }
        set { SetValue(MinRowHeightProperty, value); }
    }

    protected override Size MeasureOverride(Size constraint)
    {
        UpdateComputedValues();
        var calculatedSize = base.MeasureOverride(constraint);
        if (MinRowHeight > 0)
        {
            calculatedSize = new Size(calculatedSize.Width, Math.Max(calculatedSize.Height, _rows * MinRowHeight));
        }
        return calculatedSize;
    }

    private void UpdateComputedValues()
    {
        _columns = Columns;
        _rows = Rows;

        if (FirstColumn >= _columns)
        {
            FirstColumn = 0;
        }

        if ((_rows == 0) || (_columns == 0))
        {
            int nonCollapsedCount = 0;

            for (int i = 0, count = InternalChildren.Count; i < count; ++i)
            {
                UIElement child = InternalChildren[i];
                if (child.Visibility != Visibility.Collapsed)
                {
                    nonCollapsedCount++;
                }
            }

            if (nonCollapsedCount == 0)
            {
                nonCollapsedCount = 1;
            }

            if (_rows == 0)
            {
                if (_columns > 0)
                {
                    _rows = (nonCollapsedCount + FirstColumn + (_columns - 1)) / _columns;
                }
                else
                {
                    _rows = (int)Math.Sqrt(nonCollapsedCount);
                    if ((_rows * _rows) < nonCollapsedCount)
                    {
                        _rows++;
                    }
                    _columns = _rows;
                }
            }
            else if (_columns == 0)
            {
                _columns = (nonCollapsedCount + (_rows - 1)) / _rows;
            }
        }
    }
}

答案 1 :(得分:0)

UniformGrid执行非常精确的任务...为所有项目提供完全相同大小的空间来渲染自己。如果您不需要该功能,那么您使用了错误的Panel。请尝试使用WrapPanel Class,这样可以更好地处理不同大小的孩子:

<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <WrapPanel />
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

答案 2 :(得分:0)

我发现前段时间我用以下代码解决了这个问题。我的应用程序中有一个派生类,因此我可以覆盖MeasureOverride的{​​{1}}方法。

ItemsControl

我班级的XAML代码如下:

class MyItemsControl : ItemsControl
{
    // ...

    protected override Size MeasureOverride(Size constraint)
    {
        Size size = base.MeasureOverride(constraint);

        // Keep minimum height per item
        int minHeight = Items.Count * 250;
        if (size.Height < minHeight)
        {
            size.Height = minHeight;
        }
        return size;
    }
}