WPF WrapPanel with some elements having height *

How to make WrapPanel with some elements having height *?

Deceptively simple question that I tried to solve. I want a control (or some XAML layout layout) that behaves similarly to a grid that has multiple rows with a height * but supports column wrapping. Hell; name it WrapGrid. :)

Here is a layout to visualize this. Imagine a grid defined as such:

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

MI9MT.png

What I want this panel to do is move the element to an extra column when the element cannot be smaller than its minHeight. Here is the awful MSPaint of some of the layouts that I described in detail in this process,

vBZbL.png

Recall from XAML that auto-sized buttons have minHeights 30, and star-sized buttons have minHeights 90.

This layout is just two gratings side by side, and I manually moved the buttons around in the designer. Apparently, this could be done programmatically and served as a kind of minimized solution for this.

How can I do that? I agree with any solution, whether through xaml or has some code (although I would prefer pure XAML if possible, since the xaml code behind is more difficult to implement in IronPython).

Updated with reward


Meleak Solution

I was able to decide how to use the Meleak solution in my IPy application:

1) I compiled WrapGridPanel.cs in a DLL with 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" 

Update: Added /optimize switch, this provides a slight increase in performance

2) I added it to my xaml application with the following line.

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

This works fine, but it breaks the constructor. At the moment I can not find a workaround, it seems this is a bug in VS2010. Since the workaround is to be able to use the constructor, I just add the WrapGridPanel programmatically at runtime:

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

Slow performance when resizing :

In my IronPython application, resizing the window containing this WrapGridPanel is slow and inhibited. Is it possible to optimize the RecalcMatrix() algorithm? Maybe this can be called less often? Maybe redefining MeasureOverride and ArrangeOverride , as Nicholas suggested, would work better?

Update . According to the VS2010 Instrumentation Profiler, 97% of the time spent on RecalcMatrix () is spent on Clear () and Add (). Modifying each item in place will be a huge performance improvement. I take the brunt, but it always changes the code of another one hard. http://i.stack.imgur.com/tMTWU.png

Update: Performance issues are mostly fixed. Thanks Meleak!

Here is the layout of part of my actual application user interface in XAML if you want to try it. http://pastebin.com/2EWY8NS0

+4
source share
2 answers

Update
Optimized RecalcMatrix , so the user interface is restored only if necessary. It does not affect the user interface if necessary, so it should be much faster.

Refresh again
Fixed problem when using Margin

Thinks that you are mainly looking at the WrapPanel with a horizontal orientation, where each element in it is 1 Grid column. Each element in the column then has a corresponding RowDefinition , where the Height property corresponds to the attached property ( "WrapHeight" ) set in its Child. This panel should be in the Grid itself with Height="*" and Width="Auto" , because children should be positioned at the available Height and not care about the available Width .

I made an implementation of this, which I called WrapGridPanel . You can use it like this:

 <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)); } 

Update
Fixed multiple columns with column size.
New application here: http://www.mediafire.com/?28z4rbd4pp790t2

Update
Image that is trying to explain what WrapGridPanel does

alt text

+5
source

I don’t think there is a standard panel implementation that behaves the way you want. So your best option is probably driving your own.

It may seem intimidating at first, but it's not that difficult.

Most likely, you can copy the source code of WrapPanel (mono?) And adapt it to your needs.

You do not need columns and rows. All you need is an attached Size property:

 <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> 
+1
source

Source: https://habr.com/ru/post/1334263/


All Articles