【问题标题】:How to wrap WrapPanel children in a TreeView?如何将 WrapPanel 子项包装在 TreeView 中?
【发布时间】:2021-03-12 17:06:16
【问题描述】:

我有一个包含父母和几个孩子的 TreeView。孩子依次由一个带有自己的孩子(“一”、“二”、“三”等)的 WrapPanel 组成。当父窗口不足以容纳它们时,如何让这些最后的元素换行?

这是我的代码:

<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>

这是它目前产生的结果:

【问题讨论】:

  • 通过将宽度设置为ItemsControl/WrapPanel来实现换行

标签: c# wpf xaml


【解决方案1】:

请测试此解决方法。您必须将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:使用 MultiBindingIMultiValueConverter

...
<WrapPanel.MaxWidth>
    <MultiBinding Converter="{StaticResource Converter1}">
        <MultiBinding.Bindings>
            <Binding RelativeSource="{RelativeSource Self}"/>
            <Binding RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=TreeView}"/>
        </MultiBinding.Bindings>
    </MultiBinding>
</WrapPanel.MaxWidth>
...

IMultiValueConverterConvert方法实现:

...
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。我正在使用 MVVM,所以我尝试了第二种方法,它工作正常。不过,我不必刷新绑定,如果我将 MultiBinding 修改为也绑定到 TreeView 的“ActualWidth”,那么只要大小发生变化,就会调用它。我还必须添加额外的几行来考虑垂直滚动条的宽度(如果可见)。我希望有一种方法可以使用常规的 WPF 布局属性来解决这个问题,但是如果没有人在第二天左右发布这样的解决方案,那么我会将这个标记为答案。
【解决方案2】:

我修改了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;
}
...

【讨论】:

    【解决方案3】:

    我问这个问题已经有几年了,在此期间我学到了很多关于 WPF 布局的知识。事实上,这可以仅在 XAML 中完成,我现在将其发布给将来遇到此问题的任何人。关键是模板化 TreeViewItem 并检查它用于填充树的每个级别的 XAML。

    简而言之,TreeView 的每一层都由一个包含两行三列的网格组成。第一行是 TreeViewItem 标头,第二行是子项。

    还有三列。第 0 列是扩展器图标,第 1 列是标题内容,第 2 列是所有剩余空间。孩子们都坐在 ContentPresenter 中,它占据了第二行的第 1 列和第 2 列。这个网格的列定义揭示了一些有趣的东西:

    <ColumnDefinition MinWidth="19" Width="Auto"/>
    <ColumnDefinition Width="Auto"/>
    <ColumnDefinition Width="*"/>
    

    默认情况下,扩展器图标为 16x16,但第 0 列的 MinWidth 设置为 19 像素。因此,除非您更改扩展器图标,否则 19 像素将是 ContentPresenter 开头的缩进。因此,您需要做的就是将每个子 ItemsControl 绑定到其 TreeViewItem 的宽度,并在 右侧 手侧为其设置 19 像素的填充(或任何扩展器图标的宽度) :

    <ItemsControl Width="{Binding RelativeSource={RelativeSource AncestorType= {x:Type TreeViewItem}, AncestorLevel=1}, Path=ActualWidth}" Padding="0 0 19 0">
    

    结果是像素完美的包装:

    (请注意,此解决方案还将适应滚动条的出现和消失,并且会在它们出现时相应地重新排列 WrapPanel 项目。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-04
      相关资源
      最近更新 更多