How can I create layout container that I can use as the ItemsPanel
of an ItemsControl
that stacks the items in two columns and uses the right column for the remaining items that didn't have enough space in the left column (something like a uniform grid mixed with wrap panel).
For example:
four items with the pretty much same height will show as the following:
four items where two has significally smaller width will show like this:
Try WrapPanel with Vertical Orientation
<WrapPanel Orientation="Vertical" Height="200">
<Border BorderBrush="Red" BorderThickness="2" Height="100" Width="100"/>
<Border BorderBrush="Red" BorderThickness="2" Height="100" Width="100"/>
<Border BorderBrush="Red" BorderThickness="2" Height="100" Width="100"/>
<Border BorderBrush="Red" BorderThickness="2" Height="100" Width="100"/>
</WrapPanel>
output
<WrapPanel Orientation="Vertical" Height="200">
<Border BorderBrush="Red" BorderThickness="2" Height="100" Width="100" />
<Border BorderBrush="Red" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Red" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Red" BorderThickness="2" Height="200" Width="100" />
</WrapPanel>
Output
Edit: As your comment says wrappanel doesnt fit your requirement .Lets create our panel according to your requrement.
Below is a Custom Panel that sets the controls Vertically and when the Height of children goes more than Panel Height it moves the children to next column exactly same as WrapPanel with Vertical orientation but It has a property Columns which user can specify in xaml and the Panel wont create more than that Columns value
CustomPanel.cs
public class CustomPanel : Panel
{
//TODO :Create as Attached Property
public int Columns { get; set; }
// Default public constructor
public CustomPanel(): base()
{
}
protected override Size MeasureOverride(Size availableSize)
{
Size size = ArrangeAndMeasure(availableSize, true);
if (double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width))
return size;
else
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
return ArrangeAndMeasure(finalSize, false);
}
Size ArrangeAndMeasure(Size finalSize,bool isMeasure)
{
//if columns not specified set it value 1.
Columns = Columns == 0 ? 1 : Columns;
Size size = new Size(0, 0);
double maxWidth = 0.0;
int colCount = 1;
foreach (UIElement child in InternalChildren)
{
if ((size.Height + child.DesiredSize.Height > finalSize.Height) && Columns >= colCount)
{
//if all height consumed move to next column
size.Width += maxWidth;
size.Height = 0.0;
colCount++;
}
if (isMeasure)
child.Measure(finalSize);
else
child.Arrange(new Rect(new Point(size.Width, size.Height), child.DesiredSize));
size.Height += child.DesiredSize.Height;
if (maxWidth < child.DesiredSize.Width)
maxWidth = child.DesiredSize.Width;
}
return size;
}
}
xaml
<local:CustomPanel Height="200" Columns="2">
<Border BorderBrush="LimeGreen" BorderThickness="2" Height="200" Width="100" />
<Border BorderBrush="Red" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Black" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Blue" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Yellow" BorderThickness="2" Height="50" Width="100" />
</local:CustomPanel>
output
xaml
<local:CustomPanel Height="200" Columns="2">
<Border BorderBrush="Red" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Black" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Blue" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Yellow" BorderThickness="2" Height="50" Width="100" />
</local:CustomPanel>
Output
xaml
<local:CustomPanel Height="200" Columns="2">
<Border BorderBrush="Red" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Black" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Blue" BorderThickness="2" Height="50" Width="100" />
<Border BorderBrush="Yellow" BorderThickness="2" Height="200" Width="100" />
</local:CustomPanel>
Output