How do I make a WrapPanel with some items having a Height of *?
A deceptively simple question that I have been trying to solve. I want a control (or some XAML layout magickry) that behaves similar to a Grid that has some rows with a Height of *, but supports wrapping of columns. Hell; call it a WrapGrid. :)
Here's a mockup 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>
What I want this panel to do is wrap an item into an additional column when the item can not get any smaller than its minHeight. Here is a horrible MSPaint of some mockups I made detailing this process.
Recall from the XAML that the auto-sized buttons have minHeights of 30, and the star-sized buttons have minHeights of 90.
This mockup is just two grids side by side and I manually moved buttons around in the designer. Conceivably, this could be done programmatically and serve as a sort of convoluted solution to this.
How can this be done? I will accept any solution whether it's through xaml or has some code-behind (though I would prefer pure XAML if possible since xaml code behind is tougher to implement in IronPython).
Updated with a bounty
Meleak's Solution
I managed to work out how to use Meleak's solution in my IPy app:
1) I compiled WrapGridPanel.cs
into 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 the /optimize
switch, this nets a small performance increase
2) I added it to my application's xaml with the following line.
xmlns:local="clr-namespace:WrapGridTest;assembly=WrapGridPanel.dll"
This runs fine, but it breaks the designer. I can't really find a workaround for this yet, it looks to be a bug in VS2010. So as a workaround, in order to be able to use the designer, 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 hitchy. Could the RecalcMatrix()
algorithm be optimized? Could it perhaps be called less frequently? Maybe overriding MeasureOverride
and ArrangeOverride
, as Nicholas suggested, would perform better?
Update: According to the VS2010 Instrumentation Profiler, 97% of the time spent in RecalcMatrix() is spent on Clear() and Add(). Modifying each element in-place would be a huge performance improvement. I'm taking a whack at it myself but it's always tough modifying someone else's code... http://i.stack.imgur.com/tMTWU.png
Update: Performance issues have been mostly ironed out. Thanks Meleak!
Here is a mockup of part of my actual application's UI, in XAML, if you wish to try it out. http://pastebin.com/2EWY8NS0
I don't think there is a standard panel implementation that behave like you want. So, your best option is probably to roll your own.
It may sound intimidating at first, but it's not that difficult.
You can probably dig the
WrapPanel
source code somewhere (mono?), and adapt it to your need.You don't need columns and rows. All you need is an attached
Size
property:Update
Optimized
RecalcMatrix
so the UI is only rebuilt when needed. It doesn't touch the UI unless necessary so it should be much faster.Update Again
Fixed problem when using
Margin
Is think what you're looking at is basically a
WrapPanel
with Horizontal Orientation where every element in it is a 1 ColumnGrid
. Each element in a Column then has a correspondingRowDefinition
where theHeight
Property matches an attached property ("WrapHeight"
) set on its Child. This Panel would have to be in aGrid
itself, withHeight="*"
andWidth="Auto"
because the Children should be positioned by the availableHeight
and not care about the availableWidth
.I made an implementation of this which I called a
WrapGridPanel
. You can use it like thisWrapGridPanel.cs
Update
Fixed more than one star-sized column problem.
New sample app here: http://www.mediafire.com/?28z4rbd4pp790t2
Update
A picture that tries to explain what
WrapGridPanel
does