我想将
ItemsControl
与 ItemsSource
一起使用。我的问题是如何确保项目在显示时保持其高度比。这些项目可以有不同的大小,每次我调整窗口大小时,项目都应该相应地调整大小,但它们的高度比应该保持不变。我想在不使用滚动条的情况下显示所有项目。
我尝试使用
Grid
并将每行的 Height
的 RowDefinition
设置为星号(x*)。显然,当预先知道项目数量时(在本例中,项目数量为 3,它们的高度比为 2:3:4),这很有效:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="4*"/>
</Grid.RowDefinitions>
<control:MyControl Grid.Row="0"/>
<control:MyControl Grid.Row="1"/>
<control:MyControl Grid.Row="2"/>
</Grid>
但是,由于我需要使用
ItemsSource
,因此在行数未知的情况下使用 RowDefinitions
是不可行的。此外,如果我使用UniformGrid
,它会均匀分配所有项目的尺寸,而不是保持高度比例。它是这样的:
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Column="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<control:MyControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
有办法实现我想要的吗?对不起,我的英语不好。非常感谢。
我建议实现自定义
Panel
来计算所需的比例,以便在任何给定视口中显示所有项目。
然后,要实际缩放行,您可以使用
Viewbox
作为项目容器(否则项目的内容可能会被剪切)。
ScaleToFitPanel.cs
首先,
ScaleToFitPanel
将允许项目容器请求其所需的尺寸。
然后
ScaleToFitPanel
将计算使所有项目适合可见区域(视口)所需的高度比例。
最后,
ScaleToFitPanel
将新的高度(根据之前计算的高度比例和最初所需的项目容器尺寸计算得出)应用于每个项目容器。
请注意,这只会缩放项目容器,而不会缩放项目容器的视觉内容。
Viewbox
,实际上是项目容器,负责缩放内容。
public class ScaleToFitPanel : VirtualizingStackPanel
{
protected override Size MeasureOverride(Size constraint)
{
_ = base.MeasureOverride(constraint);
IEnumerable<UIElement> itemContainers = this.InternalChildren.Cast<UIElement>();
// To know the ratio, we need to enumerate the complete collection to get the rquired height
// before we can finally measure the item containers.
double requiredPanelHeight = itemContainers.Sum(itemContainer => itemContainer.DesiredSize.Height);
double desiredPanelHeight = double.IsInfinity(constraint.Height)
? requiredPanelHeight
: constraint.Height;
var desiredPanelSize = new Size(0, desiredPanelHeight);
double ratio = desiredPanelSize.Height / requiredPanelHeight;
bool isAllItemContainersAlreadyFitIntoPanel = ratio >= 1;
if (isAllItemContainersAlreadyFitIntoPanel)
{
return desiredPanelSize;
}
foreach (UIElement itemContainer in itemContainers)
{
double newItemContainerHeight = itemContainer.DesiredSize.Height * ratio;
var newItemContainerSize = new Size(desiredPanelSize.Width, newItemContainerHeight);
itemContainer.Measure(newItemContainerSize);
desiredPanelSize.Width = Math.Max(desiredPanelSize.Width, itemContainer.DesiredSize.Width);
}
return desiredPanelSize;
}
}
ScalingItemsControl.cs
自定义
ItemsControl
定义了一个新的项目容器,即 Viewbox
,并将上面的 ScaleToFitPanel
设置为默认 ItemsControl.ItemsPanel
public class ScalingItemsControl : ItemsControl
{
private const string ItemsPanelTemplateMarkup = @"
<ItemsPanelTemplate xmlns:local=""clr-namespace:Net.Wpf;assembly=Net.Wpf"" xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
<local:ScaleToFitPanel />
</ItemsPanelTemplate>";
public ScalingItemsControl()
{
var itemsPanelTemplate = (ItemsPanelTemplate)XamlReader.Parse(ScalingItemsControl.ItemsPanelTemplateMarkup);
this.ItemsPanel = itemsPanelTemplate;
}
// Make Viewbox the new default item container
protected override DependencyObject GetContainerForItemOverride()
=> new Viewbox() { StretchDirection = StretchDirection.DownOnly, Stretch = Stretch.Uniform };
protected override void PrepareContainerForItemOverride(DependencyObject itemContainer, object item)
{
// The underlying item host that will apply the ItemTemplate
var contentHost = new ContentControl();
// Prepare the host (set Content, apply DataTemplate etc.)
base.PrepareContainerForItemOverride(contentHost, item);
// Wrap the visual item content (e.g., the ItemTemplate)
// into the Viewbox (the item container)
if (itemContainer is Decorator decorator)
{
decorator.Child = contentHost;
}
}
protected override bool IsItemItsOwnContainerOverride(object item)
=> item is Decorator;
}
MainWindow.xaml
<Window>
<local:ScalingItemsControl ItemsSource="{Binding}"
Height="100">
<local:ScalingItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataItem}">
<control:MyControl/>
</DataTemplate>
</local:ScalingItemsControl.ItemTemplate>
</local:ScalingItemsControl>
</Window>