如何在TreeView中包装WrapPanel子项?

时间:2015-03-05 03:18:22

标签: c# wpf xaml

我有一个包含父项和几个孩子的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>

这就是它目前产生的东西:

enter image description here

3 个答案:

答案 0 :(得分: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>
...

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

答案 1 :(得分:1)

我修改了 dbvega's 解决方案 2 以考虑垂直滚动条宽度和垂直滚动条可见性。还添加了对树视图宽度变化的重新计算。 XAML:

  Widget time(String value) {
    Widget w;
    if (value == "مرة واحدة في اليوم"){
      w = SizedBox(
        height: 1,
      );}
    else if (value == 'مرتان في اليوم') {
      w = AddContainer(
          text: DateFormat('hh:mm').format(updatedTime2), // ("وقت الدواء"),
          onPressed: () {
            showModalBottomSheet(
                context: context, builder: buildShowModalBottomSheetMedTime2);
          });
    } else if (value == "ثلاث مرات في اليوم") {
      w = Column(
        children: [
          AddContainer(
              text: DateFormat('hh:mm').format(updatedTime2), // ("وقت الدواء"),
              onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: buildShowModalBottomSheetMedTime2);
              }),
          SizedBox(height: 20),
          AddContainer(
              text: DateFormat('hh:mm').format(updatedTime3), // ("وقت الدواء"),
              onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: buildShowModalBottomSheetMedTime3);
              }),
          SizedBox(height: 20),
        ],
      );
    } else if (value == "اربعة مرات في اليوم") {
      w = Column(
        children: [
          AddContainer(
              text: DateFormat('hh:mm').format(updatedTime2), // ("وقت الدواء"),
              onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: buildShowModalBottomSheetMedTime2);
              }),
          SizedBox(height: 20),
          AddContainer(
              text: DateFormat('hh:mm').format(updatedTime3), // ("وقت الدواء"),
              onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: buildShowModalBottomSheetMedTime3);
              }),
          SizedBox(height: 20),
          AddContainer(
              text: DateFormat('hh:mm').format(updatedTime4), // ("وقت الدواء"),
              onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: buildShowModalBottomSheetMedTime4);
              }),
          SizedBox(height: 20),
        ],
      );
    }
    return w;
  }

转换器:

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

答案 2 :(得分:0)

我问这个问题已经有几年了,在此期间我学到了很多关于 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 的宽度,并在 右侧-hand 侧给它一个 19 像素的填充(或任何扩展器图标的宽度) :

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

结果是像素完美的包装:

enter image description here

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