You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WPF中实现符合特定规则的扁平化树形视图?

实现扁平化树形视图的WPF解决方案

需求概述

需要构建扁平化树形视图:长链节点横向连续排列,分支节点纵向展开(同级分支各自独立成行,链状节点显示在同一行)。

层级数据结构

- A1
  - B1
    - ...
      - X1
        - Y1
          - ...
            - AJ1
      - X2
        - Y2
          - Z2
- A2
  - B2
    - ...
- ...

数据以长链状为主,分支场景较少。

期望布局结构

<StackPanel>
  <!-- A1 -->
  <WrapPanel>
    <TextBlock>A1</TextBlock>
    <TextBlock>B1</TextBlock>
    <!-- ... -->
    <StackPanel>
      <WrapPanel>
        <TextBlock>X1</TextBlock>
        <!-- ... -->
        <TextBlock>AJ1</TextBlock>
      </WrapPanel>

      <WrapPanel>
        <TextBlock>X2</TextBlock>
        <TextBlock>Y2</TextBlock>
        <TextBlock>Z2</TextBlock>
      </WrapPanel>
    </StackPanel>
  </WrapPanel>

  <!-- A2 -->
  <WrapPanel>
    <TextBlock>A2</TextBlock>
    <TextBlock>...</TextBlock>
  </WrapPanel>

  <!-- ... -->
</StackPanel>

展示规则

  • 若节点有0个或1个子节点,与父节点一同显示在WrapPanel中
  • 若节点有多个子节点,每个子节点作为独立WrapPanel嵌套在StackPanel中展示

遇到的问题

常规TreeView强制子节点嵌套在父节点内,无法实现扁平化效果;查阅ItemsControl文档后,仍未明确具体实现方式及需重写的方法。

补充说明

已准备好示例数据仓库用于测试。


解决方案:自定义ItemsControl结合数据模板触发器

1. 定义节点数据模型

实现INotifyPropertyChanged以支持数据绑定:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

public class TreeNode : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get => _name;
        set { _name = value; OnPropertyChanged(); }
    }

    private ObservableCollection<TreeNode> _children = new();
    public ObservableCollection<TreeNode> Children
    {
        get => _children;
        set { _children = value; OnPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. 创建动态数据模板

通过HierarchicalDataTemplate和触发器,根据子节点数量自动切换布局:

<Window.Resources>
    <!-- 文本块基础样式 -->
    <Style TargetType="TextBlock">
        <Setter Property="Margin" Value="4,2"/>
        <Setter Property="Padding" Value="2"/>
        <Setter Property="BorderBrush" Value="#CCC"/>
        <Setter Property="BorderThickness" Value="1"/>
    </Style>

    <!-- 分支节点模板:多子节点时纵向展开每个分支 -->
    <HierarchicalDataTemplate x:Key="BranchNodeTemplate" ItemsSource="{Binding Children}">
        <StackPanel>
            <ItemsControl ItemsSource="{Binding Children}" ItemTemplate="{StaticResource NodeTemplate}"/>
        </StackPanel>
    </HierarchicalDataTemplate>

    <!-- 链状节点模板:单/无子节点时横向拼接 -->
    <HierarchicalDataTemplate x:Key="ChainNodeTemplate" ItemsSource="{Binding Children}">
        <WrapPanel>
            <TextBlock Text="{Binding Name}"/>
            <ContentControl Content="{Binding Children[0]}" ContentTemplate="{StaticResource NodeTemplate}"/>
        </WrapPanel>
    </HierarchicalDataTemplate>

    <!-- 动态选择模板的核心节点模板 -->
    <DataTemplate x:Key="NodeTemplate">
        <ContentControl>
            <ContentControl.Style>
                <Style TargetType="ContentControl">
                    <Setter Property="ContentTemplate" Value="{StaticResource ChainNodeTemplate}"/>
                    <Style.Triggers>
                        <!-- 无子节点时仅显示当前节点 -->
                        <DataTrigger Binding="{Binding Children.Count}" Value="0">
                            <Setter Property="ContentTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <TextBlock Text="{Binding Name}"/>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                        <!-- 子节点数量≥2时使用分支模板 -->
                        <DataTrigger Binding="{Binding Children.Count}" Value="2">
                            <Setter Property="ContentTemplate" Value="{StaticResource BranchNodeTemplate}"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Children.Count}" Value="3">
                            <Setter Property="ContentTemplate" Value="{StaticResource BranchNodeTemplate}"/>
                        </DataTrigger>
                        <!-- 可扩展更多数量判断,或使用MultiTrigger判断范围 -->
                    </Style.Triggers>
                </Style>
            </ContentControl.Style>
        </ContentControl>
    </DataTemplate>
</Window.Resources>

3. 主布局使用ItemsControl

<ItemsControl ItemsSource="{Binding RootNodes}" ItemTemplate="{StaticResource NodeTemplate}"/>

4. 初始化示例数据

在ViewModel中构建测试数据:

using System.Collections.ObjectModel;

public class MainViewModel
{
    public ObservableCollection<TreeNode> RootNodes { get; } = new();

    public MainViewModel()
    {
        // 构建A1分支
        var a1 = new TreeNode { Name = "A1" };
        var b1 = new TreeNode { Name = "B1" };
        var x1 = new TreeNode { Name = "X1" };
        var aj1 = new TreeNode { Name = "AJ1" };
        x1.Children.Add(aj1);
        
        var x2 = new TreeNode { Name = "X2" };
        var y2 = new TreeNode { Name = "Y2" };
        var z2 = new TreeNode { Name = "Z2" };
        y2.Children.Add(z2);
        x2.Children.Add(y2);
        
        b1.Children.Add(x1);
        b1.Children.Add(x2);
        a1.Children.Add(b1);

        // 构建A2分支
        var a2 = new TreeNode { Name = "A2" };
        var b2 = new TreeNode { Name = "B2" };
        a2.Children.Add(b2);

        RootNodes.Add(a1);
        RootNodes.Add(a2);
    }
}

核心逻辑说明

  • 通过DataTrigger判断子节点数量,动态切换模板:无子节点仅显示自身;单节点横向拼接;多节点纵向展开分支
  • 利用HierarchicalDataTemplate递归处理层级数据,无需手动遍历整棵树
  • ItemsControl无默认缩进和折叠逻辑,完全自定义布局,适配扁平化需求

内容的提问来源于stack exchange,提问作者Frederic Schönberger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 10:06:05