Проблемы с Arrange / Measure - Не нарушен ли макет в WPF? - PullRequest
13 голосов
/ 25 ноября 2011

Я пытаюсь сделать то, что я думал, будет простой панелью в 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? Конечно, проход измерения должен быть именно таким: мера проход?

Ответы [ 3 ]

5 голосов
/ 25 ноября 2011

Проблема в вашем случае заключается в том, что вы передаете все доступное пространство каждому ребенку для его вызова 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));
}

Обновление:

В случае, если размер, который вы планируете дать каждому отдельному предмету, не легко рассчитать на основе 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));
}
0 голосов
/ 25 ноября 2011

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

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

<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

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

0 голосов
/ 25 ноября 2011

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

e.Measure(availableSize);

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...