如何在 TreeView 中包装 WrapPanel children?
How to wrap WrapPanel children in a TreeView?
我有一个包含 parent 和几个 children 的 TreeView。 children 又由一个 WrapPanel 组成,它有自己的 children("One"、"Two"、"Three" 等)。当 parent window 不足以容纳这些最后的元素时,我如何才能将它们换行?
这是我的代码:
<TreeView ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<TreeViewItem Header="Parent" IsExpanded="True" >
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<TextBlock Text="One" />
<TextBlock Text="Two" />
<TextBlock Text="Three" />
<TextBlock Text="Four" />
<TextBlock Text="Five" />
<TextBlock Text="Six" />
<TextBlock Text="Seven" />
<TextBlock Text="Eight" />
<TextBlock Text="Nine" />
<TextBlock Text="Ten" />
</ItemsControl.Items>
</ItemsControl>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<TextBlock Text="One" />
<TextBlock Text="Two" />
<TextBlock Text="Three" />
<TextBlock Text="Four" />
<TextBlock Text="Five" />
<TextBlock Text="Six" />
<TextBlock Text="Seven" />
<TextBlock Text="Eight" />
<TextBlock Text="Nine" />
<TextBlock Text="Ten" />
</ItemsControl.Items>
</ItemsControl>
</TreeViewItem>
</TreeView>
这是它目前产生的结果:
请测试此解决方法。您必须将 SizeChanged="TreeView_SizeChanged"
添加到 TreeView
项。
private void TreeView_SizeChanged(object sender, SizeChangedEventArgs e)
{
var treeView = (TreeView) sender;
foreach (TreeViewItem treeViewItem in treeView.Items)
{
foreach (ItemsControl ic in treeViewItem.Items)
{
Point relativeLocation = ic.TranslatePoint(new Point(0, 0), treeView);
var wpMaxWidth = treeView.ActualWidth - relativeLocation.X;
WrapPanel itemsWp = GetVisualChild<WrapPanel>(ic);
itemsWp.MaxWidth = wpMaxWidth;
}
}
}
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual) VisualTreeHelper.GetChild(parent, i);
var child = v as T;
return child ?? GetVisualChild<T>(v);
}
return null;
}
解决方案 2:使用 MultiBinding
和 IMultiValueConverter
。
...
<WrapPanel.MaxWidth>
<MultiBinding Converter="{StaticResource Converter1}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=TreeView}"/>
</MultiBinding.Bindings>
</MultiBinding>
</WrapPanel.MaxWidth>
...
IMultiValueConverter
Convert
方法实现:
...
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var wp = values[0] as WrapPanel;
var tv = values[1] as TreeView;
if (wp == null || tv == null)
return 0d;
Point relativeLocation = wp.TranslatePoint(new Point(0, 0), tv);
var wpMaxWidth = tv.ActualWidth - relativeLocation.X;
return wpMaxWidth;
}
...
注意:容器大小更改后必须刷新Binding
。
我修改了 dbvega's 解决方案 2 以考虑垂直滚动条宽度和垂直滚动条可见性。还添加了对树视图宽度更改的重新计算。
XAML:
...
<WrapPanel.Width>
<MultiBinding Converter="{StaticResource Converter1}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}" Path="ViewportWidth"/>
</MultiBinding.Bindings>
</MultiBinding>
</WrapPanel.Width>
...
转换器:
...
object IMultiValueConverter.Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
foreach (var v in values)
if (v is null)
return DependencyProperty.UnsetValue;
var panel = (Panel)values[0];
var scrollviewer = (ScrollViewer)values[1];
Point relativeLocation = panel.TranslatePoint(new Point(0, 0), scrollviewer);
var width = scrollviewer.ViewportWidth - relativeLocation.X;
return width;
}
...
我问这个问题已经有几年了,在此期间我学到了很多关于 WPF 布局的知识。事实上,这可以单独在 XAML 中完成,我现在将 post 用于将来遇到此问题的任何人。关键是模板化 TreeViewItem 并检查它用于填充树的每个级别的 XAML。
简而言之,TreeView 的每一层都由一个包含两行三列的网格组成。第一行是 TreeViewItem header,第二行是 children.
也有三列。第 0 列用于扩展器图标,第 1 列用于 header 内容,第 2 列用于所有剩余的 space。 children 都位于 ContentPresenter 中,该 ContentPresenter 占据第二行的第 1 列和第 2 列。此网格的列定义揭示了一些有趣的东西:
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
扩展器图标默认为 16x16,但第 0 列的 MinWidth 设置为 19 像素。因此,除非您更改扩展器图标,否则 19 像素将是 ContentPresenter 开头的缩进。因此,您需要做的就是将每个 child ItemsControl 绑定到其 TreeViewItem 的宽度,并在 right-手边:
<ItemsControl Width="{Binding RelativeSource={RelativeSource AncestorType= {x:Type TreeViewItem}, AncestorLevel=1}, Path=ActualWidth}" Padding="0 0 19 0">
结果是pixel-perfect换行:
(请注意,此解决方案还将适应出现和消失的滚动条,并且会在出现和消失时相应地 re-arrange WrapPanel 项目)。
我有一个包含 parent 和几个 children 的 TreeView。 children 又由一个 WrapPanel 组成,它有自己的 children("One"、"Two"、"Three" 等)。当 parent window 不足以容纳这些最后的元素时,我如何才能将它们换行?
这是我的代码:
<TreeView ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<TreeViewItem Header="Parent" IsExpanded="True" >
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<TextBlock Text="One" />
<TextBlock Text="Two" />
<TextBlock Text="Three" />
<TextBlock Text="Four" />
<TextBlock Text="Five" />
<TextBlock Text="Six" />
<TextBlock Text="Seven" />
<TextBlock Text="Eight" />
<TextBlock Text="Nine" />
<TextBlock Text="Ten" />
</ItemsControl.Items>
</ItemsControl>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<TextBlock Text="One" />
<TextBlock Text="Two" />
<TextBlock Text="Three" />
<TextBlock Text="Four" />
<TextBlock Text="Five" />
<TextBlock Text="Six" />
<TextBlock Text="Seven" />
<TextBlock Text="Eight" />
<TextBlock Text="Nine" />
<TextBlock Text="Ten" />
</ItemsControl.Items>
</ItemsControl>
</TreeViewItem>
</TreeView>
这是它目前产生的结果:
请测试此解决方法。您必须将 SizeChanged="TreeView_SizeChanged"
添加到 TreeView
项。
private void TreeView_SizeChanged(object sender, SizeChangedEventArgs e)
{
var treeView = (TreeView) sender;
foreach (TreeViewItem treeViewItem in treeView.Items)
{
foreach (ItemsControl ic in treeViewItem.Items)
{
Point relativeLocation = ic.TranslatePoint(new Point(0, 0), treeView);
var wpMaxWidth = treeView.ActualWidth - relativeLocation.X;
WrapPanel itemsWp = GetVisualChild<WrapPanel>(ic);
itemsWp.MaxWidth = wpMaxWidth;
}
}
}
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual) VisualTreeHelper.GetChild(parent, i);
var child = v as T;
return child ?? GetVisualChild<T>(v);
}
return null;
}
解决方案 2:使用 MultiBinding
和 IMultiValueConverter
。
...
<WrapPanel.MaxWidth>
<MultiBinding Converter="{StaticResource Converter1}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=TreeView}"/>
</MultiBinding.Bindings>
</MultiBinding>
</WrapPanel.MaxWidth>
...
IMultiValueConverter
Convert
方法实现:
...
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var wp = values[0] as WrapPanel;
var tv = values[1] as TreeView;
if (wp == null || tv == null)
return 0d;
Point relativeLocation = wp.TranslatePoint(new Point(0, 0), tv);
var wpMaxWidth = tv.ActualWidth - relativeLocation.X;
return wpMaxWidth;
}
...
注意:容器大小更改后必须刷新Binding
。
我修改了 dbvega's 解决方案 2 以考虑垂直滚动条宽度和垂直滚动条可见性。还添加了对树视图宽度更改的重新计算。 XAML:
...
<WrapPanel.Width>
<MultiBinding Converter="{StaticResource Converter1}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}" Path="ViewportWidth"/>
</MultiBinding.Bindings>
</MultiBinding>
</WrapPanel.Width>
...
转换器:
...
object IMultiValueConverter.Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
foreach (var v in values)
if (v is null)
return DependencyProperty.UnsetValue;
var panel = (Panel)values[0];
var scrollviewer = (ScrollViewer)values[1];
Point relativeLocation = panel.TranslatePoint(new Point(0, 0), scrollviewer);
var width = scrollviewer.ViewportWidth - relativeLocation.X;
return width;
}
...
我问这个问题已经有几年了,在此期间我学到了很多关于 WPF 布局的知识。事实上,这可以单独在 XAML 中完成,我现在将 post 用于将来遇到此问题的任何人。关键是模板化 TreeViewItem 并检查它用于填充树的每个级别的 XAML。
简而言之,TreeView 的每一层都由一个包含两行三列的网格组成。第一行是 TreeViewItem header,第二行是 children.
也有三列。第 0 列用于扩展器图标,第 1 列用于 header 内容,第 2 列用于所有剩余的 space。 children 都位于 ContentPresenter 中,该 ContentPresenter 占据第二行的第 1 列和第 2 列。此网格的列定义揭示了一些有趣的东西:
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
扩展器图标默认为 16x16,但第 0 列的 MinWidth 设置为 19 像素。因此,除非您更改扩展器图标,否则 19 像素将是 ContentPresenter 开头的缩进。因此,您需要做的就是将每个 child ItemsControl 绑定到其 TreeViewItem 的宽度,并在 right-手边:
<ItemsControl Width="{Binding RelativeSource={RelativeSource AncestorType= {x:Type TreeViewItem}, AncestorLevel=1}, Path=ActualWidth}" Padding="0 0 19 0">
结果是pixel-perfect换行:
(请注意,此解决方案还将适应出现和消失的滚动条,并且会在出现和消失时相应地 re-arrange WrapPanel 项目)。