Подтвердить что ты не робот

Проблемы с организацией/измерением - Разбита ли макет в WPF?

Я пытаюсь сделать то, что, как я думал, будет простой панелью в WPF, которая имеет следующие свойства:

  • Если объединенные высоты детей меньше доступной высоты, все дети отображаются на их желаемой высоте.

  • Если комбинированные высоты детей больше, чем доступная высота, все дети уменьшаются на одну и ту же процентную высоту, чтобы соответствовать.

Моя панель выглядит так:

public class MyStackPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        Size requiredSize = new Size();

        foreach (UIElement e in InternalChildren)
        {
            e.Measure(availableSize);
            requiredSize.Height += e.DesiredSize.Height;
            requiredSize.Width = Math.Max(requiredSize.Width, e.DesiredSize.Width);
        }

        return new Size(
            Math.Min(availableSize.Width, requiredSize.Width),
            Math.Min(availableSize.Height, requiredSize.Height));
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        double requiredHeight = 0;

        foreach (UIElement e in InternalChildren)
        {
            requiredHeight += e.DesiredSize.Height;
        }

        double scale = 1;

        if (requiredHeight > finalSize.Height)
        {
            scale = finalSize.Height / requiredHeight;
        }

        double y = 0;

        foreach (UIElement e in InternalChildren)
        {
            double height = e.DesiredSize.Height * scale;
            e.Arrange(new Rect(0, y, finalSize.Width, height));
            y += height;
        }

        return finalSize;
    }
}

Мой тест XAML выглядит следующим образом:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="300" Width="300">
    <Window.Resources>
        <x:Array x:Key="Items" Type="{x:Type sys:String}">
            <sys:String>Item1</sys:String>
            <sys:String>Item2</sys:String>
            <sys:String>Item3</sys:String>
            <sys:String>Item4</sys:String>
        </x:Array>
    </Window.Resources>
    <local:MyStackPanel>
        <ListBox ItemsSource="{StaticResource Items}"/>
        <ListBox ItemsSource="{StaticResource Items}"/>
        <ListBox ItemsSource="{StaticResource Items}"/>
        <ListBox ItemsSource="{StaticResource Items}"/>
        <ListBox ItemsSource="{StaticResource Items}"/>
    </local:MyStackPanel>
</Window>

Но вывод выглядит следующим образом:

Layout Problem

Как вы можете видеть, элементы обрезаются - в списках должны отображаться полосы прокрутки. Детские предметы не соответствуют размеру, указанному им в прохождении аранжировки.

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

Однако я не могу этого сделать, потому что Мне нужно, чтобы результаты измерения проходили, чтобы узнать, какой размер дать детям в проходе.

Похоже на ситуацию с курицей и яйцом. Разбивается ли макет в WPF? Разумеется, пропуск меры должен быть только тем, что мера проходит?

4b9b3361

Ответ 1

Проблема в вашем случае состоит в том, что вы передаете все доступное пространство каждому ребенку в его вызов Measure (e.Measure(availableSize)). Но вам нужно передать только ту часть пространства, которую вы на самом деле собираетесь им дать. Вот так:

protected override Size MeasureOverride(Size availableSize)
{
    Size requiredSize = new Size();

    var itemAvailableSize = new Size(availableSize.Width, availableSize.Height / InternalChildren.Count);

    foreach (UIElement e in InternalChildren)
    {
        e.Measure(itemAvailableSize);
        requiredSize.Height += e.DesiredSize.Height;
        requiredSize.Width = Math.Max(requiredSize.Width, e.DesiredSize.Width);
    }

    return new Size(
        Math.Min(availableSize.Width, requiredSize.Width),
        Math.Min(availableSize.Height, requiredSize.Height));
}

Update:

В случае, когда размер, который вы планируете предоставить каждому отдельному элементу, нелегко рассчитывается на основе availableSize и зависит от других желаемых размеров элементов, вы можете выполнить первый раунд измерения на всех элементах, проходящих через double.PositiveInfinity, как Height. После этого вы узнаете, насколько важны все элементы, и вы можете рассчитать, сколько места вы фактически собираетесь отдавать каждому элементу. Затем вам нужно снова вызвать Measure с вычисленным пространством.

Вот пример:

protected override Size MeasureOverride(Size availableSize)
{
    var requiredSize = new Size();

    double allItemsHeight = 0;

    foreach (UIElement e in InternalChildren)
    {
        e.Measure(new Size(availableSize.Width, double.PositiveInfinity));
        allItemsHeight += e.DesiredSize.Height;
    }

    double scale = 1;

    if (allItemsHeight > availableSize.Height)
    {
        scale = availableSize.Height / allItemsHeight;
    }

    foreach (UIElement e in InternalChildren)
    {
        double height = e.DesiredSize.Height * scale;

        e.Measure(new Size(availableSize.Width, height));

        requiredSize.Height += e.DesiredSize.Height;
        requiredSize.Width = Math.Max(requiredSize.Width, e.DesiredSize.Width);
    }

    return new Size(
        Math.Min(availableSize.Width, requiredSize.Width),
        Math.Min(availableSize.Height, requiredSize.Height));
}

Ответ 2

Это очень интересный вопрос, и я не думаю, что могу ответить "Разбитый макет". Я убежден, что это не должно быть, но глядя на ваш пример, я не могу понять, как "исправить", он использует пропуски меры/макета.

Однако я могу предложить следующее в качестве альтернативы получить (грубо) желаемый эффект:

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Rows="{Binding Items.Count,
                 RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}}" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ListBox ItemsSource="{StaticResource Items}"/>
    <ListBox ItemsSource="{StaticResource Items}"/>
    <ListBox ItemsSource="{StaticResource Items}"/>
    <ListBox ItemsSource="{StaticResource Items}"/>
    <ListBox ItemsSource="{StaticResource Items}"/>
</ItemsControl>

enter image description here

Это назначит единый объем пространства для каждого элемента в списке. Это не совсем работает, если для заполнения всего доступного пространства недостаточно предметов, но оно почти там.

Ответ 3

Проблема в том, что вы сообщаете своим дочерним элементам, что они могут иметь весь доступный размер в MeasureOverride

e.Measure(availableSize);

Вам нужно ограничить размер, чтобы сообщить им, что они будут изменены. Вы должны иметь возможность реализовать аналогичную логику в MeasureOverride.