我有一个 ItemsControl,其中 WrapPanel 作为 ItemsPanel。 ItemsControl 位于 ScrollViewer 内部。
根据窗口的宽度,ItemsControl/WrapPanel 应具有更多列以更多地利用屏幕(并且用户必须更少滚动)。
如果我将 WrapPanel 设置为 Orientation="Horizontal",它会按预期工作。但如果我将方向设置为“垂直”,则 ItemsControl/WrapPanel 仅显示一列。
不同的是,我喜欢像报纸专栏一样的专栏,所以像:
A E I
B F J
C G
D H
但是如果 WrapPanel 设置为水平,则列如下:
A B C
D E F
G H I
J
如何设置 WrapPanel 使其具有这样的行为?
我当然可以限制高度。当然,我可以通过一些巧妙的逻辑来实现这一点,即考虑源中的项目数量以及窗口的宽度。但这会很复杂,而且可能一团糟,没有人会理解(甚至我自己,如果我几年后回顾这一点)。
我希望 WPF 中已经存在这样的东西。
这是伪 XAML 代码
<ScrollViewer>
<StackPanel>
<!-- Some other stuff -->
<ItemsControl ItemsSource="{Binding … }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="300"><!-- … --></Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Some more other stuff -->
</StackPanel>
</ScrollViewer>
它适用于 Orientation="Horizontal"(但单元格顺序错误),但当 Orientation="Vertical" 时仅显示一列。
我相信
Orientation="Vertical"
是您正在寻找的选择。它将在换行到新列之前尝试填充列。
A __D __G
| | | | |
B | E | .
| | | | .
C_| F_| .->
您遇到的麻烦是
WrapPanel
被 StackPanel
允许无限量的垂直布局空间,因此不必换行。
要解决此问题,请将
StackPanel
更改为 WrapPanel
或其他不允许无限布局空间的控件。
示例
<WrapPanel>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Rectangle Height="50" Width="50" Fill="#F000"/>
<Rectangle Height="50" Width="50" Fill="#F111"/>
<Rectangle Height="50" Width="50" Fill="#F222"/>
<!- ... ->
<Rectangle Height="50" Width="50" Fill="#F111"/>
<Rectangle Height="50" Width="50" Fill="#F000"/>
</ItemsControl>
</WrapPanel>
结果
好吧,我想出了一个解决方案,那就是纯 XAML。
所以基本上我有一个网格。有两排。网格内部有两个几乎相同的 ItemsControl,带有绑定和 WrapPanel。 第一个有一个 Orientation=“Horizontal”的 WrapPanel。它仅用于计算第二个 ItemsControl 的边界。因此它的可见性=“隐藏”。而且它只跨越第一行。
第二个 ItemsControl 具有 Orientation=“Vertical”的 WrapPanel。它的高度绑定到第一个 ItemsControl 的 ActualHeight。因此,它被迫使用多个列,因为高度不能高于 ItemsControl #1 的高度。 第二个 ItemsControl 跨越两行(这很重要,否则高度可能会卡住,调整窗口大小*)。
这不是一个很好的解决方案。更像是蛮力。但它当然有效。第一个 ItemsControl 计算外部尺寸。我将此尺寸强制为第二个,这样可以以良好的模式分布项目。
<ScrollViewer>
<StackPanel>
<!-- Some other stuff -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- first ItemsControl, to calculate the outer boundaries with -->
<ItemsControl ItemsSource="{Binding … }"
Name="Calculating_Size"
Visibility="Hidden">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="300"><!-- … --></Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Second ItemsControl with fixed size for Vertical WrapPanel -->
<ItemsControl ItemsSource="{Binding … }"
Height="{Binding ActualHeight, ElementName=Calculating_Size}"
Grid.RowSpan="2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="300"><!-- … --></Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Some more other stuff -->
</StackPanel>
</ScrollViewer>
* 第二行的原因是,如果第一个 WrapPanel 重新组织,因为宽度增加,它仍然与高度固定,因为行不能变小,第二个 ItemsControl.但是,如果第二个 ItemsControl 跨越两行,则第一行可能会变得更小,而与第二个 ItemsControl 无关。第二个之后立即变小。但这似乎是一个额外的步骤。
对我来说,答案是通过将 WrapPanel 绑定到父 ScrollContentPresenter 来限制其高度
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"
Height="{Binding (FrameworkElement.ActualHeight), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}">
</WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>