Установите минимальную и максимальную высоту элементов в ItemsControl - PullRequest
0 голосов
/ 22 октября 2018

Я использую 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 элемент: actual1

, и вот как это должно выглядеть так: expected1


2 или 3 элемента отображаются должным образом: actual3


Для 4+ элементов полоса прокрутки отображается правильно, но все элементы имеют размер до 150,а не 300: actual4

Вопрос

Как выровнять содержимое по верху при наличиитолько 1 предмет?(очевидно, не нарушая другую функциональность)

Бонусный вопрос: Как получить размеры элементов, которые можно изменить до максимального, а не минимального, если есть 4+ элемента?

1 Ответ

0 голосов
/ 09 апреля 2019

В процессе компоновки WPF измерение и упорядочение будут выполняться по порядку.В большинстве приведений, если UIElement имеет переменный размер, он вернет минимальный требуемый результат.Но если какое-либо выравнивание макета было установлено на Stretch, UIElement займет как можно больше в этом направлении при аранжировке.В вашем случае UniFormGrid всегда будет возвращать 160 (что составляет Border.MinHeight + Border.Margin.Top + Border.Margin.Bottom) * количество элементов в качестве желаемой высоты в результате измерения (которое будет сохранено в DesiredSize.DesiredSize.Height).Но он будет принимать ItemsControl.ActualHeight в качестве установленной высоты, поскольку он имеет Stretch VerticalAlignment.Таким образом, если UniFormGrid.DesiredSize.Height было меньше, чем ItemsControl.ActualHeight, UniFormGrid и любой ребенок с Stretch VerticalAlignment будет растягиваться по вертикали, пока не встретит его MaxHeight.Вот почему ваш тест по 1 пункту оказался в центре.Если вы измените UniFormGrid.VerticalAlignment или Border.VerticalAlignment на Top, вы получите элемент высоты 160 в верхней части ItemsContorl.


Самое простое решение обоих вопросов - переопределить измерениерезультат основан на максимальной высоте строки и минимальной высоте строки.Я пишу коды ниже и провел несколько базовых тестов, кажется, что он работает просто отлично.

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>
...