WPF WrapPanel с некоторыми элементами, имеющими высоту * - PullRequest
4 голосов
/ 05 января 2011

Как мне сделать WrapPanel с некоторыми предметами, имеющими высоту *?

Обманчиво простой вопрос, который я пытался решить.Я хочу элемент управления (или некоторую магию компоновки XAML), который ведет себя подобно сетке, которая имеет несколько строк с высотой *, но поддерживает перенос столбцов.Ад;Назовите это WrapGrid.:)

Вот макет, чтобы визуализировать это.Представьте себе сетку, определенную следующим образом:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="400">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" MinHeight="30">I'm auto-sized.</Button>
        <Button Grid.Row="1" MinHeight="90">I'm star-sized.</Button>
        <Button Grid.Row="2" MinHeight="30">I'm auto-sized.</Button>
        <Button Grid.Row="3" MinHeight="90">I'm star-sized, too!</Button>
        <Button Grid.Row="4" MinHeight="30">I'm auto-sized.</Button>
        <Button Grid.Row="5" MinHeight="30">I'm auto-sized.</Button>
    </Grid>
</Window>

image

Я хочу, чтобы эта панель выполняла , оборачивая элемент в дополнительный столбец, когда элемент не может получитьменьше, чем его minHeight. Вот ужасный MSPaint с некоторыми макетами, которые я сделал, детализируя этот процесс.

image

Напомним из XAML, что кнопки автоматического размера имеют minHeights 30, а кнопки звездного размера имеют minHeights 90.

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

Как это можно сделать? Я приму любое решение, будь то через xaml или с некоторым кодом (хотя я бы предпочел чистый XAML, если это возможно, поскольку код позади xaml сложнее реализовать)в IronPython).

Обновлен с вознаграждением


Решение Meleak

Мне удалось выяснить, как использовать решение Meleak в моем IPyapp:

1) Я скомпилировал WrapGridPanel.cs в DLL с csc:

C:\Projects\WrapGridTest\WrapGridTest>csc /target:library "WrapGridPanel.cs" /optimize /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\PresentationFramework.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\PresentationCore.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\WindowsBase.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Xaml.dll" 

Обновление: Добавлен переключатель /optimize, это требуетнебольшое увеличение производительности

2) Я добавил его в xaml своего приложения со следующей строкой:

xmlns:local="clr-namespace:WrapGridTest;assembly=WrapGridPanel.dll"

Это работает нормально, но это нарушает конструктор.Я пока не могу найти обходной путь для этого, похоже, это ошибка в VS2010.В качестве обходного пути, чтобы иметь возможность использовать конструктор, я просто программно добавляю WrapGridPanel во время выполнения:

clr.AddReference("WrapGridPanel.dll")
from WrapGridTest import WrapGridPanel
wgp = WrapGridPanel()

Низкая производительность при изменении размера :

В моем приложении IronPython изменение размера окна, содержащего этот WrapGridPanel, происходит медленно и непросто.Может ли алгоритм RecalcMatrix() быть оптимизирован?Может ли это быть вызвано реже?Может быть, переопределение MeasureOverride и ArrangeOverride, как предположил Николас, будет работать лучше?

Обновление : в соответствии с профилировщиком инструментов VS2010 97% времени, затрачиваемого на RecalcMatrix (), расходуется на Clear () и Add ().Изменение каждого элемента на месте было бы огромным улучшением производительности.Я сам делаю удар по нему, но всегда сложно изменить чей-то код ... http://i.stack.imgur.com/tMTWU.png

Обновление: проблемы с производительностью были в основном устранены.Спасибо Meleak!

Вот макет части реального пользовательского интерфейса моего приложения в XAML, если вы хотите попробовать его.http://pastebin.com/2EWY8NS0

Ответы [ 2 ]

5 голосов
/ 08 января 2011

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

Обновление снова
Исправлена ​​проблема при использовании Margin

Это то, что вы думаетев основном это WrapPanel с горизонтальной ориентацией, где каждый элемент в нем - это 1 столбец Grid.Каждый элемент в столбце имеет соответствующий RowDefinition, где свойство Height соответствует вложенному свойству ("WrapHeight"), установленному для его дочернего элемента.Эта панель должна быть в Grid сама по себе, с Height="*" и Width="Auto", потому что Дети должны располагаться по доступным Height, а не заботиться о доступных Width.

Iсделал реализацию этого, который я назвал WrapGridPanel.Вы можете использовать его следующим образом

<local:WrapGridPanel>
    <Button MinHeight="30" local:WrapGridPanel.WrapHeight="Auto">I'm auto-sized.</Button>
    <Button MinHeight="90" local:WrapGridPanel.WrapHeight="*">I'm star-sized.</Button>
    <Button MinHeight="30" local:WrapGridPanel.WrapHeight="Auto">I'm auto-sized.</Button>
    <Button MinHeight="90" local:WrapGridPanel.WrapHeight="*">I'm star-sized, too!</Button>
    <Button MinHeight="30" local:WrapGridPanel.WrapHeight="Auto">I'm auto-sized.</Button>
    <Button MinHeight="30" local:WrapGridPanel.WrapHeight="Auto">I'm auto-sized.</Button>
</local:WrapGridPanel>

alt text

WrapGridPanel.cs

[ContentProperty("WrapChildren")] 
public class WrapGridPanel : Grid
{
    private WrapPanel m_wrapPanel = new WrapPanel();
    public WrapGridPanel()
    {
        ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1.0, GridUnitType.Auto) } );
        RowDefinitions.Add(new RowDefinition { Height = new GridLength(1.0, GridUnitType.Star) } );
        Children.Add(m_wrapPanel);
        WrapChildren = new ObservableCollection<FrameworkElement>();
        WrapChildren.CollectionChanged += WrapChildren_CollectionChanged;
        DependencyPropertyDescriptor actualHeightDescriptor
            = DependencyPropertyDescriptor.FromProperty(WrapGridPanel.ActualHeightProperty,
                                                        typeof(WrapGridPanel));
        if (actualHeightDescriptor != null)
        {
            actualHeightDescriptor.AddValueChanged(this, ActualHeightChanged);
        }
    }

    public static void SetWrapHeight(DependencyObject element, GridLength value)
    {
        element.SetValue(WrapHeightProperty, value);
    }
    public static GridLength GetWrapHeight(DependencyObject element)
    {
        return (GridLength)element.GetValue(WrapHeightProperty);
    }
    public ObservableCollection<FrameworkElement> WrapChildren
    {
        get { return (ObservableCollection<FrameworkElement>)base.GetValue(WrapChildrenProperty); }
        set { base.SetValue(WrapChildrenProperty, value); }
    }

    void ActualHeightChanged(object sender, EventArgs e)
    {
        RecalcMatrix();
    }
    void WrapChildren_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        RecalcMatrix();
    }

    List<List<FrameworkElement>> m_elementList = null;
    private bool SetupMatrix()
    {
        m_elementList = new List<List<FrameworkElement>>();
        double minHeightSum = 0;
        m_elementList.Add(new List<FrameworkElement>());
        int column = 0;
        if (WrapChildren.Count > 0)
        {
            foreach (FrameworkElement child in WrapChildren)
            {
                double tempMinHeight = 0.0;
                if (WrapGridPanel.GetWrapHeight(child).GridUnitType != GridUnitType.Star)
                {
                    tempMinHeight = Math.Max(child.ActualHeight, child.MinHeight) + child.Margin.Top + child.Margin.Bottom;
                }
                else
                {
                    tempMinHeight = child.MinHeight + child.Margin.Top + child.Margin.Bottom;
                }
                minHeightSum += tempMinHeight;
                if (minHeightSum > ActualHeight)
                {
                    minHeightSum = tempMinHeight;
                    m_elementList.Add(new List<FrameworkElement>());
                    column++;
                }
                m_elementList[column].Add(child);
            }
        }
        if (m_elementList.Count != m_wrapPanel.Children.Count)
        {
            return true;
        }
        for (int i = 0; i < m_elementList.Count; i++)
        {
            List<FrameworkElement> columnList = m_elementList[i];
            Grid wrapGrid = m_wrapPanel.Children[i] as Grid;
            if (columnList.Count != wrapGrid.Children.Count)
            {
                return true;
            }
        }
        return false;
    }
    private void RecalcMatrix()
    {
        if (ActualHeight == 0 || SetupMatrix() == false)
        {
            return;
        }

        Binding heightBinding = new Binding("ActualHeight");
        heightBinding.Source = this;
        while (m_elementList.Count > m_wrapPanel.Children.Count)
        {
            Grid wrapGrid = new Grid();
            wrapGrid.SetBinding(Grid.HeightProperty, heightBinding);
            m_wrapPanel.Children.Add(wrapGrid);
        }
        while (m_elementList.Count < m_wrapPanel.Children.Count)
        {
            Grid wrapGrid = m_wrapPanel.Children[m_wrapPanel.Children.Count - 1] as Grid;
            wrapGrid.Children.Clear();
            m_wrapPanel.Children.Remove(wrapGrid);
        }

        for (int i = 0; i < m_elementList.Count; i++)
        {
            List<FrameworkElement> columnList = m_elementList[i];
            Grid wrapGrid = m_wrapPanel.Children[i] as Grid;
            wrapGrid.RowDefinitions.Clear();
            for (int j = 0; j < columnList.Count; j++)
            {
                FrameworkElement child = columnList[j];
                GridLength wrapHeight = WrapGridPanel.GetWrapHeight(child);
                Grid.SetRow(child, j);
                Grid parentGrid = child.Parent as Grid;
                if (parentGrid != wrapGrid)
                {
                    if (parentGrid != null)
                    {
                        parentGrid.Children.Remove(child);
                    }
                    wrapGrid.Children.Add(child);
                }

                RowDefinition rowDefinition = new RowDefinition();
                rowDefinition.Height = new GridLength(Math.Max(1, child.MinHeight), wrapHeight.GridUnitType);
                wrapGrid.RowDefinitions.Add(rowDefinition); 
            }
        }
    }

    public static readonly DependencyProperty WrapHeightProperty =
            DependencyProperty.RegisterAttached("WrapHeight",
                                                typeof(GridLength),
                                                typeof(WrapGridPanel),
                                                new FrameworkPropertyMetadata(new GridLength(1.0, GridUnitType.Auto)));

    public static readonly DependencyProperty WrapChildrenProperty =
            DependencyProperty.Register("WrapChildren",
                                        typeof(ObservableCollection<FrameworkElement>),
                                        typeof(WrapGridPanel),
                                        new UIPropertyMetadata(null));
}

Обновление
Исправлена ​​проблема с несколькими столбцами размером со звездочку.
Новое примерное приложение здесь: http://www.mediafire.com/?28z4rbd4pp790t2

Обновление
Изображение, которое пытается объяснить, что делает WrapGridPanel

alt text

1 голос
/ 05 января 2011

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

Поначалу это может звучать пугающе, но это не так сложно.

Вы, вероятно, можете где-нибудь выкопать исходный код WrapPanel (моно?) И адаптировать его под свои нужды.

Вам не нужны столбцы и строки. Все, что вам нужно, это прикрепленное Size свойство:

<StuffPanel Orientation="Vertical">
    <Button StuffPanel.Size="Auto" MinHeight="30">I'm auto-sized.</Button>
    <Button StuffPanel.Size="*" MinHeight="90">I'm star-sized.</Button>
    <Button StuffPanel.Size="Auto"  MinHeight="30">I'm auto-sized.</Button>
    <Button StuffPanel.Size="*" MinHeight="90">I'm star-sized, too!</Button>
    <Button StuffPanel.Size="Auto"  MinHeight="30">I'm auto-sized.</Button>
    <Button StuffPanel.Size="Auto" MinHeight="30">I'm auto-sized.</Button>
</StuffPanel>
...