Reputation: 20354
My application displays multiple items in an ItemsControl
that uses a UniformGrid
as its ItemsPanel
.
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- Arrange all items vertically, distribute the space evenly -->
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Now each of the items will take as much space as there is and the full width. That's how it should be. But each item must not get smaller than a certain height, say 250 pixels. Therefore, my ItemsControl
is put inside a ScrollViewer
which will start to scroll if the minimum height doesn't fit on the screen anymore (but fill until then).
Setting each item's MinHeight
property to 250 will make it taller but won't change how the UniformGrid
arranges them in any way, so they're clipped. That's not the solution.
Setting the entire ItemsControl's or the UniformGrid's MinHeight
property does work, but then I need to calculate it from the number of items. A good place to get the updated number of items is to override the OnItemsChanged
method in my ItemsControl
code-behind. But that won't give me the actual items panel. I have the following code for that in another project, but it always returns null here:
ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
So that doesn't work, too.
Walking the visual tree is probably not an option, too, because it's too late. I need to set the minimum height before that items panel is arranged so that it won't flicker. (I generate complex content on a size change so any late layouting must be avoided.)
What options do I have to get what I need?
I may drop the UniformGrid
in favour of something else, if it's possible. Any suggestions?
Upvotes: 2
Views: 2984
Reputation: 20354
I found that I've solved the problem with the following code some time ago. I have a derived class in my application so I can override the MeasureOverride
method of ItemsControl
.
class MyItemsControl : ItemsControl
{
// ...
protected override Size MeasureOverride(Size constraint)
{
Size size = base.MeasureOverride(constraint);
// Keep minimum height per item
int minHeight = Items.Count * 250;
if (size.Height < minHeight)
{
size.Height = minHeight;
}
return size;
}
}
The XAML code of my class looks like this:
<ItemsControl x:Class="MyItemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- Arrange all items vertically, distribute the space evenly -->
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Templates and other stuff ... -->
</ItemsControl>
Upvotes: 0
Reputation: 376
You can employ a lot of techniques to achieve this. However on the approach is to extend the UniformGrid.
The solution in this case is to override the MeasureOverride()
to calculate the new size either based on the min height of all the children in the row or the MinRowHeight, which ever is greater.
Usage :
<UserControl x:Class="SOF.UniformGridMinHeight"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sof="clr-namespace:SOF"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<sof:UniformGridEx Columns="3" MinRowHeight="100">
<Border Background="Red"/>
<Border Background="Blue"/>
<Border Background="Green"/>
<Border Background="Yellow"/>
<Border Background="Pink"/>
<Border Background="Purple"/>
<Border Background="LightCoral"/>
<Border Background="DimGray"/>
<Border Background="OrangeRed"/>
<Border Background="Olive"/>
<Border Background="Salmon"/>
</sof:UniformGridEx>
Extended UniformGrid:
public class UniformGridEx : UniformGrid
{
public static readonly DependencyProperty MinRowHeightProperty = DependencyProperty.Register(
"MinRowHeight", typeof(double), typeof(UniformGrid), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure));
private int _columns;
private int _rows;
public double MinRowHeight
{
get { return (double)GetValue(MinRowHeightProperty); }
set { SetValue(MinRowHeightProperty, value); }
}
protected override Size MeasureOverride(Size constraint)
{
UpdateComputedValues();
var calculatedSize = base.MeasureOverride(constraint);
if (MinRowHeight > 0)
{
calculatedSize = new Size(calculatedSize.Width, Math.Max(calculatedSize.Height, _rows * MinRowHeight));
}
return calculatedSize;
}
private void UpdateComputedValues()
{
_columns = Columns;
_rows = Rows;
if (FirstColumn >= _columns)
{
FirstColumn = 0;
}
if ((_rows == 0) || (_columns == 0))
{
int nonCollapsedCount = 0;
for (int i = 0, count = InternalChildren.Count; i < count; ++i)
{
UIElement child = InternalChildren[i];
if (child.Visibility != Visibility.Collapsed)
{
nonCollapsedCount++;
}
}
if (nonCollapsedCount == 0)
{
nonCollapsedCount = 1;
}
if (_rows == 0)
{
if (_columns > 0)
{
_rows = (nonCollapsedCount + FirstColumn + (_columns - 1)) / _columns;
}
else
{
_rows = (int)Math.Sqrt(nonCollapsedCount);
if ((_rows * _rows) < nonCollapsedCount)
{
_rows++;
}
_columns = _rows;
}
}
else if (_columns == 0)
{
_columns = (nonCollapsedCount + (_rows - 1)) / _rows;
}
}
}
}
Upvotes: 4
Reputation: 69959
A UniformGrid
performs a very exact task... providing all items with the exact same sized space to render themselves. If you don't require that functionality, then you are using the wrong Panel
. Try using the WrapPanel
Class instead, which can better handle different sized children:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Upvotes: 0