【问题标题】:Set Min and Max height of items in ItemsControl在 ItemsControl 中设置项目的最小和最大高度
【发布时间】:2019-03-26 04:17:45
【问题描述】:

我正在使用 ItemsControl 来显示 1 - 10 个项目(通常是 2 - 4)的列表。我正在努力满足所有这些要求:

  • 所有行的高度必须相同
  • 如果可能,所有行都应显示在最大 300 的高度。
  • 如果没有足够的空间以 300 高显示所有行,则显示在尽可能高的高度。
  • 如果最大可能高度小于 150,则以最大尺寸显示并使用滚动条
  • 如果行没有填满页面,则必须在顶部垂直对齐

这是我目前所拥有的:

<Window x:Class="TestGridRows.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:TestGridRows"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance vm:MainViewModel}"
        Height="570" Width="800">

    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl ItemsSource="{Binding Path=DataItems}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border MinHeight="150" MaxHeight="300" BorderBrush="DarkGray" BorderThickness="1" Margin="5">
                        <TextBlock Text="{Binding Path=TheNameToDisplay}" VerticalAlignment="Center" HorizontalAlignment="Center" />
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Columns="1" IsItemsHost="True" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </ScrollViewer>
</Window>

这是 1 项当前的样子:

这就是它应该的样子:


2 或 3 个项目按预期显示:


对于 4 个以上的项目,滚动条显示正确,但项目的大小都为 150,而不是 300:

问题

当只有 1 个项目时,如何将内容对齐到顶部? (没有明显破坏其他功能)

额外问题:当有 4 个以上的项目时,如何让项目调整为 maxheight 而不是 minheight?

【问题讨论】:

  • 一般MinHeight 以这种方式工作,如果您的项目高度的值小于定义的最小值,它会显示最小值,MaxHeight 也是如此。所以你得到的完全是意料之中的。您的商品的高度值可能低于您的平均值 (200)。
  • @LionKing 我的项目的 ScrollViewer 的 ActualHeight 为 570。我一直在试验 1、2、3 和 5 行,它们都表现出相同的行为
  • @JumpingJezza:听起来您想使用 Grid 作为 ItemsControl 的面板:blog.scottlogic.com/2010/11/15/…
  • 怎么样。用网格替换滚动查看器,用列表框替换项目控制。在列表框上设置垂直对齐顶部。计算视图或视图模型中的行高并在样式中设置。
  • @Andy 如果我更换滚动查看器,那么如何滚动 4+ 项?

标签: wpf xaml layout scroll itemscontrol


【解决方案1】:

在 WPF 布局过程中,将按顺序进行测量和排列。在大多数演员中,如果UIElement 具有可变大小,它将返回所需的最小值作为结果。但是如果任何布局对齐已设置为StretchUIElement 将尽可能沿该方向进行排列。在您的情况下,UniFormGrid 将始终返回 160(即Border.MinHeight + Border.Margin.Top + Border.Margin.Bottom)* 测量结果中所需高度的项目数(将存储在 DesiredSize.DesiredSize.Height 中)。但它将以ItemsControl.ActualHeight 作为排列高度,因为它有StretchVerticalAlignment。因此,如果UniFormGrid.DesiredSize.Height 小于ItemsControl.ActualHeightUniFormGrid 并且任何具有Stretch 的孩子VerticalAlignment 将被垂直拉伸,直到遇到它的MaxHeight。这就是为什么您的单项测试结果居中。如果将UniFormGrid.VerticalAlignmentBorder.VerticalAlignment 更改为Top,则会在ItemsContorl 的顶部获得一个160 高度的项目。


这两个问题最简单的解决方案是根据最大行高和最小行高覆盖测量结果。我在下面写了代码并做了一些基本的测试,它似乎工作得很好。

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class MyScrollViewer : ScrollViewer
    {
        public double DesiredViewportHeight;

        public MyScrollViewer() : base() { }

        protected override Size MeasureOverride(Size constraint)
        {
            // record viewport's height for late calculation 
            DesiredViewportHeight = constraint.Height;

            var result = base.MeasureOverride(constraint);

            // make sure that `ComputedVerticalScrollBarVisibility` will get correct value 
            if (ComputedVerticalScrollBarVisibility == Visibility.Visible && ExtentHeight <= ViewportHeight)
                result = base.MeasureOverride(constraint);

            return result;
        }
    }

    public class MyUniformGrid : UniformGrid
    {
        private MyScrollViewer hostSV;
        private ItemsControl hostIC;

        public MyUniFormGrid() : base() { }

        public double MaxRowHeight { get; set; }
        public double MinRowHeight { get; set; }

        protected override Size MeasureOverride(Size constraint)
        {
            if (hostSV == null)
            {
                hostSV = VisualTreeHelperEx.GetAncestor<MyScrollViewer>(this);
                hostSV.SizeChanged += (s, e) =>
                {
                    if (e.HeightChanged)
                    {
                        // need to redo layout pass after the height of host had changed.  
                        this.InvalidateMeasure();
                    }
                };
            }

            if (hostIC == null)
                hostIC = VisualTreeHelperEx.GetAncestor<ItemsControl>(this);

            var viewportHeight = hostSV.DesiredViewportHeight;
            var rows = hostIC.Items.Count;
            var rowHeight = viewportHeight / rows;
            double desiredHeight = 0;

            // calculate the correct height
            if (rowHeight > MaxRowHeight || rowHeight < MinRowHeight)
                desiredHeight = MaxRowHeight * rows;
            else
                desiredHeight = viewportHeight;

            var result = base.MeasureOverride(constraint);

            return new Size(result.Width, desiredHeight);
        }
    }

    public class VisualTreeHelperEx
    {
        public static T GetAncestor<T>(DependencyObject reference, int level = 1) where T : DependencyObject
        {
            if (level < 1)
                throw new ArgumentOutOfRangeException(nameof(level));

            return GetAncestorInternal<T>(reference, level);
        }

        private static T GetAncestorInternal<T>(DependencyObject reference, int level) where T : DependencyObject
        {
            var parent = VisualTreeHelper.GetParent(reference);

            if (parent == null)
                return null;

            if (parent is T && --level == 0)
                return (T)parent;

            return GetAncestorInternal<T>(parent, level);
        }
    }
}

Xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Height="570" Width="800">

    <local:MyScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl>
            <sys:String>aaa</sys:String>
            <sys:String>aaa</sys:String>
            <sys:String>aaa</sys:String>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="DarkGray" BorderThickness="1" Margin="5">
                        <TextBlock Text="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=ContentPresenter}}"
                                   VerticalAlignment="Center" HorizontalAlignment="Center" />
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <local:MyUniformGrid Columns="1"  MinRowHeight="150" MaxRowHeight="300" VerticalAlignment="Top"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </local:MyScrollViewer>
</Window>

【讨论】:

  • 完美运行!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-30
  • 2021-06-21
  • 2018-11-08
  • 1970-01-01
  • 1970-01-01
  • 2010-11-07
  • 1970-01-01
相关资源
最近更新 更多