WPF WrapPanel,其中一些项目的高度为*

时间:2011-01-04 21:01:41

标签: .net wpf visual-studio wpf-controls

如何使用高度为*?

的某些项目制作WrapPanel

我一直试图解决的一个看似简单的问题。我想要一个控件(或一些XAML布局magickry),其行为类似于网格,其中某些行的高度为*,但支持包裹列。地狱;称之为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>

我想让这个面板做的是当项目不能小于minHeight时,将项目包装到一个附加列中。这是一个可怕的MSPaint,我做了一些详细说明这个过程的模型。

回想一下XAML,自动调整大小的按钮的minHeights为30,而星形按钮的minHeights为90.

这个模型只是两个并排的网格,我在设计器中手动移动了按钮。可以想象,这可以通过编程方式完成,并作为一种复杂的解决方案。

如何做到这一点?我会接受任何解决方案,无论是通过xaml还是有一些代码隐藏(尽管如果可能的话我会更喜欢纯XAML,因为后面的xaml代码在IronPython中更难实现)。

更新了赏金


Meleak的解决方案

我设法在我的IPy应用程序中找到了如何使用Meleak的解决方案:

1)我将WrapGridPanel.cs编译成了一个带有csc

的DLL
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()算法吗?或许可以不那么频繁地召唤它?正如尼古拉斯建议的那样,可能覆盖MeasureOverrideArrangeOverride会表现得更好吗?

更新:根据VS2010 Instrumentation Profiler,RecalcMatrix()花费的时间的97%用于Clear()和Add()。就地修改每个元素将是一个巨大的性能改进。我自己也在抨击它,但修改其他人的代码总是很难... http://i.stack.imgur.com/tMTWU.png

更新:性能问题大多已被解决。谢谢Meleak!

如果您想尝试一下,这是我在XAML中实际应用程序UI的一部分的模型。 http://pastebin.com/2EWY8NS0

2 个答案:

答案 0 :(得分:5)

<强>更新
优化RecalcMatrix,以便只在需要时重建UI。除非必要,否则它不会触及UI,因此它应该更快。

再次更新
修复了使用Margin

时出现的问题

您认为所看到的内容基本上是WrapPanel水平方向,其中每个元素都是1列Grid。然后,列中的每个元素都有一个对应的RowDefinition,其中Height属性与其子项上设置的附加属性("WrapHeight")匹配。该小组必须位于Grid本身,Height="*"Width="Auto",因为儿童应该由可用的Height定位,而不关心可用{{1} }}

我实现了这个,我称之为Width。你可以像这样使用它

WrapGridPanel

alt text

<强> WrapGridPanel.cs

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

<强>更新
修复了多个星级列问题 这里有新的示例应用:http://www.mediafire.com/?28z4rbd4pp790t2

<强>更新
试图解释[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)); } 做什么的图片

alt text

答案 1 :(得分:1)

我不认为有一个标准的面板实现,表现得像你想要的那样。所以,你最好的选择可能就是自己动手。

起初听起来可能有些吓人,但并不困难。

您可以在某处挖掘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>