WPF:ItemsPanelTemplate 显示每个项目具有固定高度比例的元素

问题描述 投票:0回答:1

我想将

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>

有办法实现我想要的吗?对不起,我的英语不好。非常感谢。

c# .net wpf xaml
1个回答
0
投票

我建议实现自定义

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>
© www.soinside.com 2019 - 2024. All rights reserved.