Reputation: 5675
I am using an ItemsControl to display a list of 1 - 10 items (usually 2 - 4). I am trying to satisfy all these requirements:
This is what I have so far:
<Window x:Class="TestGridRows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:TestGridRows"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance vm:MainViewModel}"
Height="570" Width="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Path=DataItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border MinHeight="150" MaxHeight="300" BorderBrush="DarkGray" BorderThickness="1" Margin="5">
<TextBlock Text="{Binding Path=TheNameToDisplay}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Window>
This is what it currently looks like with 1 item:
and this is what it should look like:
2 or 3 items display as expected:
For 4+ items, the scrollbar appears correctly but the items are all sized to 150, rather than 300:
How do I align the content to top when there is only 1 item? (without breaking the other functionality obviously)
Bonus question: How do I get the items to resize to maxheight instead of minheight when there are 4+ items?
Upvotes: 0
Views: 1714
Reputation: 1883
During the WPF layout process, measuring and arranging will be done in order. In most cast, if an UIElement
has variable size, it will return minimum required as result. But if any layout alignment has been set to Stretch
, UIElement
will take as possible as it can in that direction in arranging. In your case, UniFormGrid
will always return 160(which is Border.MinHeight
+ Border.Margin.Top
+ Border.Margin.Bottom
) * the count of items as desired height in measuring result(which will stored in DesiredSize.DesiredSize.Height
). But it will take ItemsControl.ActualHeight
as arranged height since it has Stretch
VerticalAlignment
. So, if UniFormGrid.DesiredSize.Height
was less then ItemsControl.ActualHeight
, UniFormGrid
and any child has Stretch
VerticalAlignment
will be stretch in vertically, until it encountered its MaxHeight
. This is why your 1 item test resulted in the center. If you change UniFormGrid.VerticalAlignment
or Border.VerticalAlignment
to Top
, you will get a 160 height item in the top of ItemsContorl
.
The most simple solution to both questions is override the measuring result base on maximum row height and minimum row height. I write the codes in below and had done some basic tests, it seems to work just fine.
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MyScrollViewer : ScrollViewer
{
public double DesiredViewportHeight;
public MyScrollViewer() : base() { }
protected override Size MeasureOverride(Size constraint)
{
// record viewport's height for late calculation
DesiredViewportHeight = constraint.Height;
var result = base.MeasureOverride(constraint);
// make sure that `ComputedVerticalScrollBarVisibility` will get correct value
if (ComputedVerticalScrollBarVisibility == Visibility.Visible && ExtentHeight <= ViewportHeight)
result = base.MeasureOverride(constraint);
return result;
}
}
public class MyUniformGrid : UniformGrid
{
private MyScrollViewer hostSV;
private ItemsControl hostIC;
public MyUniFormGrid() : base() { }
public double MaxRowHeight { get; set; }
public double MinRowHeight { get; set; }
protected override Size MeasureOverride(Size constraint)
{
if (hostSV == null)
{
hostSV = VisualTreeHelperEx.GetAncestor<MyScrollViewer>(this);
hostSV.SizeChanged += (s, e) =>
{
if (e.HeightChanged)
{
// need to redo layout pass after the height of host had changed.
this.InvalidateMeasure();
}
};
}
if (hostIC == null)
hostIC = VisualTreeHelperEx.GetAncestor<ItemsControl>(this);
var viewportHeight = hostSV.DesiredViewportHeight;
var rows = hostIC.Items.Count;
var rowHeight = viewportHeight / rows;
double desiredHeight = 0;
// calculate the correct height
if (rowHeight > MaxRowHeight || rowHeight < MinRowHeight)
desiredHeight = MaxRowHeight * rows;
else
desiredHeight = viewportHeight;
var result = base.MeasureOverride(constraint);
return new Size(result.Width, desiredHeight);
}
}
public class VisualTreeHelperEx
{
public static T GetAncestor<T>(DependencyObject reference, int level = 1) where T : DependencyObject
{
if (level < 1)
throw new ArgumentOutOfRangeException(nameof(level));
return GetAncestorInternal<T>(reference, level);
}
private static T GetAncestorInternal<T>(DependencyObject reference, int level) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(reference);
if (parent == null)
return null;
if (parent is T && --level == 0)
return (T)parent;
return GetAncestorInternal<T>(parent, level);
}
}
}
Xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Height="570" Width="800">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl>
<sys:String>aaa</sys:String>
<sys:String>aaa</sys:String>
<sys:String>aaa</sys:String>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="DarkGray" BorderThickness="1" Margin="5">
<TextBlock Text="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=ContentPresenter}}"
VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:MyUniformGrid Columns="1" MinRowHeight="150" MaxRowHeight="300" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</local:MyScrollViewer>
</Window>
Upvotes: 1