侧边栏壁纸
  • 累计撰写 102 篇文章
  • 累计创建 11 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
WPF

TreeView

祈安千
2026-09-08 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

基本数据绑定

基本的数据绑定其实是比较简单的,一般 TreeView 绑定的都是使用一个ObservableCollection ,以下是常用的示例。

public partial class NodeInfo : ObservableObject
{
    [ObservableProperty]
    private string _name = "Node";
    [ObservableProperty]
    private bool _isSelected = false;
    [ObservableProperty]
    private NodeType _nodeType = NodeType.None;
    [ObservableProperty]
    private string _description = "Node Description";

}
public enum NodeType
{
    None,
    Running,
    Skip,
    Success,
    Error,
}
//在ViewModel里面的类
 private ObservableCollection<NodeInfo> _nodes = new();
 public ObservableCollection<NodeInfo> Nodes
 {
     get { return _nodes; }
     set
     {
         OnPropertyChanged(ref _nodes, value);
     }
 }

完整样例

这是我为了模仿之前工步运行的时候写的简单示例,为了以免忘记。例如在不同的状态下显示不同的图标,只有在正在运行的时候才会有一个旋转的动画等。

Geometry

//需要用到的Geometry

    <GeometryGroup x:Key="RunningGeometry">
        <Geometry>M1011.264 431.573333c-19.008-117.973333-77.589333-222.208-163.989333-298.368-0.170667-0.149333-0.341333-0.298667-0.490667-0.448-6.933333-6.101333-14.122667-11.989333-21.44-17.728-1.173333-0.917333-2.261333-1.877333-3.434667-2.773333-6.464-4.970667-13.098667-9.728-19.818667-14.4-2.197333-1.514667-4.309333-3.114667-6.528-4.608-5.909333-3.989333-12.010667-7.744-18.154667-11.52-3.157333-1.962667-6.293333-3.989333-9.536-5.866667-2.666667-1.557333-5.354667-3.093333-8.064-4.586667l162.794667-26.218667c11.648-1.877333 19.541333-12.842667 17.664-24.469333-1.877333-11.626667-12.885333-19.541333-24.448-17.664L696.64 38.229333c-10.944 1.770667-20.522667 7.701333-27.008 16.661333-6.506667 9.002667-9.109333 19.989333-7.338667 30.912L697.6 304.981333c1.685333 10.474667 10.752 17.941333 21.034667 17.941333 1.130667 0 2.261333-0.085333 3.413333-0.277333 11.626667-1.877333 19.541333-12.821333 17.664-24.448L705.322667 91.52c4.949333 2.282667 9.962667 4.522667 14.784 6.976 3.413333 1.706667 6.698667 3.562667 10.048 5.333333 5.504 2.944 11.029333 5.866667 16.405333 9.002667 2.581333 1.514667 5.077333 3.157333 7.637333 4.714667 5.952 3.626667 11.904 7.296 17.642667 11.2 1.685333 1.130667 3.285333 2.346667 4.970667 3.52 6.485333 4.501333 12.906667 9.088 19.114667 13.888 0.725333 0.554667 1.386667 1.152 2.112 1.706667 90.602667 70.912 152.256 173.632 171.114667 290.581333 3.797333 23.616 5.738667 47.104 5.909333 70.314667 1.685333 224.597333-161.024 423.573333-389.418667 460.352-11.626667 1.877333-19.541333 12.821333-17.664 24.448 1.685333 10.474667 10.752 17.941333 21.034667 17.941333 1.130667 0 2.261333-0.085333 3.413333-0.277333 8.597333-1.386667 17.130667-2.986667 25.536-4.8 235.968-50.389333 401.536-261.141333 399.744-497.962667C1017.536 483.093333 1015.445333 457.408 1011.264 431.573333z</Geometry>
        <Geometry>M431.616 12.778667c-32.085333 5.162667-63.168 13.418667-93.056 24.277333-1.152 0.426667-2.389333 0.704-3.541333 1.130667-40.682667 15.104-78.72 35.797333-113.941333 60.650667-45.184 31.872-85.568 70.912-118.912 117.034667-64.277333 88.96-97.28 193.92-95.872 301.674667 0.32 24.874667 2.474667 49.898667 6.506667 74.922667 0.021333 0.149333 0.064 0.298667 0.085333 0.448 2.346667 14.549333 5.525333 28.821333 9.088 42.944 0.896 3.605333 1.792 7.168 2.773333 10.730667 3.776 13.781333 7.978667 27.349333 12.864 40.64 0.874667 2.368 1.898667 4.693333 2.816 7.04 4.437333 11.541333 9.301333 22.826667 14.528 33.941333 1.557333 3.306667 3.072 6.656 4.693333 9.92 6.250667 12.565333 12.992 24.832 20.245333 36.8 1.472 2.410667 3.072 4.8 4.586667 7.189333 7.104 11.285333 14.634667 22.272 22.613333 32.938667 1.024 1.344 1.962667 2.773333 3.008 4.117333 9.258667 12.096 19.136 23.722667 29.461333 34.965333 1.109333 1.216 2.218667 2.368 3.349333 3.562667 21.546667 22.954667 45.290667 43.946667 71.04 62.677333 0.661333 0.469333 1.237333 1.045333 1.898667 1.514667 0.192 0.149333 0.426667 0.192 0.64 0.32 15.317333 11.008 31.296 21.184 47.936 30.506667l-163.029333 26.261333c-11.626667 1.877333-19.541333 12.821333-17.664 24.448 1.685333 10.474667 10.752 17.941333 21.034667 17.941333 1.130667 0 2.261333-0.085333 3.434667-0.277333l219.157333-35.306667c10.922667-1.749333 20.522667-7.637333 27.029333-16.618667 6.506667-9.002667 9.109333-19.989333 7.36-30.933333l-35.306667-219.178667c-1.877333-11.626667-12.821333-19.456-24.469333-17.642667-11.626667 1.898667-19.541333 12.864-17.642667 24.469333l34.432 206.656c-28.416-13.034667-55.061333-28.714667-79.722667-46.677333-23.594667-17.237333-45.248-36.394667-64.853333-57.216-1.066667-1.152-2.176-2.282667-3.242667-3.434667-9.450667-10.24-18.410667-20.885333-26.858667-31.872-1.002667-1.322667-1.92-2.666667-2.922667-4.010667-7.253333-9.664-14.058667-19.669333-20.501333-29.845333-1.429333-2.261333-2.944-4.501333-4.330667-6.784-6.634667-10.922667-12.757333-22.122667-18.474667-33.557333-1.514667-3.050667-2.922667-6.165333-4.394667-9.258667-4.757333-10.090667-9.173333-20.330667-13.184-30.784-0.853333-2.218667-1.834667-4.416-2.666667-6.677333-4.437333-12.117333-8.298667-24.533333-11.733333-37.098667-0.896-3.285333-1.749333-6.613333-2.581333-9.92-3.242667-12.885333-6.144-25.941333-8.298667-39.232-0.021333-0.170667-0.064-0.341333-0.085333-0.490667-4.906667-30.528-6.784-61.034667-5.696-91.242667 3.264-90.666667 33.194667-178.325333 87.509333-253.504 54.314667-75.178667 128.170667-131.136 213.205333-162.709333 28.352-10.517333 57.941333-18.325333 88.469333-23.253333 11.626667-1.877333 19.541333-12.821333 17.664-24.448C454.208 18.816 443.264 10.794667 431.616 12.778667z</Geometry>
    </GeometryGroup>


    <Geometry x:Key="SkipGeometry">
        M409.414678 988.447492a488.761633 488.761633 0 0 1-123.092632-44.525979 30.480545 30.480545 0 0 1 28.285946-53.987141 421.606896 421.606896 0 0 0 104.024003 38.186026h0.682764l2.877364 0.682764a425.947326 425.947326 0 0 0 90.076106 9.753775 431.79959 431.79959 0 0 0 56.718198-3.706435 437.554317 437.554317 0 0 0 53.645759-10.729151 418.144306 418.144306 0 0 0 85.004143-32.675144 425.557175 425.557175 0 0 0 231.164452-379.324285 423.265038 423.265038 0 0 0-3.560128-55.74282 439.505072 439.505072 0 0 0-10.875458-54.767443 422.72858 422.72858 0 0 0-32.675144-85.004144 427.166548 427.166548 0 0 0-288.858027-221.654522 424.289184 424.289184 0 0 0-90.710102-9.607467 415.852169 415.852169 0 0 0-55.74282 3.608896 431.702053 431.702053 0 0 0-54.767443 10.875458 420.046292 420.046292 0 0 0-85.004144 32.675145 427.654236 427.654236 0 0 0-227.701862 434.042958 427.264085 427.264085 0 0 0 89.198267 210.876601 30.431776 30.431776 0 1 1-47.647188 37.893414 486.762109 486.762109 0 0 1-64.716293-107.291518c-0.292613-0.487689-0.487689-1.121684-0.780302-1.609373a488.371482 488.371482 0 0 1-29.261323-297.782731 487.688717 487.688717 0 0 1 960.01524 39.990475 487.054722 487.054722 0 0 1-421.314283 547.137972 483.738439 483.738439 0 0 1-62.277849 4.047817 476.618184 476.618184 0 0 1-102.707244-11.363148z m42.623994-293.003381a61.058627 61.058627 0 1 1 61.107396 61.058627 61.058627 61.058627 0 0 1-61.107396-61.058627z m0-183.224651V267.98495a61.058627 61.058627 0 0 1 121.92218 0v244.136972a61.058627 61.058627 0 0 1-121.92218 0z
    </Geometry>

    <Geometry x:Key="SuccessGeometry">M469.333333 640l0.384 0.384L469.333333 640z m-106.282666 0l-0.384 0.384 0.384-0.384z m48.512 106.666667a87.466667 87.466667 0 0 1-61.653334-24.874667l-179.52-173.632a67.797333 67.797333 0 0 1 0-98.24c28.032-27.157333 73.493333-27.157333 101.589334 0l139.584 134.997333 319.168-308.544c28.032-27.157333 73.493333-27.157333 101.589333 0a67.925333 67.925333 0 0 1 0 98.24L472.981333 722.069333A87.530667 87.530667 0 0 1 411.562667 746.666667z</Geometry>

    <Geometry x:Key="ErrorGeometry">
        M806.4 263.2l-45.6-45.6L512 467.2 263.2 217.6l-45.6 45.6L467.2 512 217.6 760.8l45.6 45.6L512 557.6l248.8 248.8 45.6-45.6L557.6 512z
    </Geometry>

XAML设计

 <TreeView
     x:Name="treeView"
     Grid.Column="1"
     ItemsSource="{Binding Nodes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
     SelectedItemChanged="TreeView_SelectedItemChanged">

     <TreeView.ItemContainerStyle>
         <Style TargetType="TreeViewItem">
             <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
         </Style>
     </TreeView.ItemContainerStyle>


     <TreeView.Resources>
         <HierarchicalDataTemplate DataType="{x:Type m:NodeInfo}">
             <StackPanel Orientation="Horizontal">
                 <Viewbox
                     Width="40"
                     Height="40"
                     Stretch="Uniform">
                     <Path x:Name="path" RenderTransformOrigin="0.5,0.5">

                         <Path.RenderTransform>
                             <RotateTransform x:Name="rotateTransform" Angle="0" />
                         </Path.RenderTransform>

                         <Path.Style>
                             <Style TargetType="Path">
                                 <Style.Triggers>


                                     <DataTrigger Binding="{Binding NodeType}" Value="{x:Static m:NodeType.Running}">
                                         <Setter Property="Fill" Value="Green" />
                                         <Setter Property="Data" Value="{StaticResource RunningGeometry}" />
                                         <DataTrigger.EnterActions>
                                             <BeginStoryboard x:Name="storyboard">
                                                 <Storyboard RepeatBehavior="Forever">
                                                     <DoubleAnimation
                                                         Storyboard.TargetProperty="RenderTransform.Angle"
                                                         From="0"
                                                         To="360"
                                                         Duration="0:0:1" />
                                                 </Storyboard>
                                             </BeginStoryboard>

                                         </DataTrigger.EnterActions>
                                         <DataTrigger.ExitActions>
                                             <StopStoryboard BeginStoryboardName="storyboard" />
                                         </DataTrigger.ExitActions>
                                     </DataTrigger>
                                     <DataTrigger Binding="{Binding NodeType}" Value="{x:Static m:NodeType.Skip}">
                                         <Setter Property="Fill" Value="Yellow" />
                                         <Setter Property="Data" Value="{StaticResource SkipGeometry}" />
                                     </DataTrigger>
                                     <DataTrigger Binding="{Binding NodeType}" Value="{x:Static m:NodeType.Success}">
                                         <Setter Property="Fill" Value="Green" />
                                         <Setter Property="Data" Value="{StaticResource SuccessGeometry}" />
                                     </DataTrigger>
                                     <DataTrigger Binding="{Binding NodeType}" Value="{x:Static m:NodeType.Error}">
                                         <Setter Property="Fill" Value="Red" />
                                         <Setter Property="Data" Value="{StaticResource ErrorGeometry}" />
                                     </DataTrigger>
                                 </Style.Triggers>
                             </Style>
                         </Path.Style>
                     </Path>
                 </Viewbox>
                 <TextBlock
                     Margin="5"
                     HorizontalAlignment="Center"
                     VerticalAlignment="Center"
                     Text="{Binding Name}" />
             </StackPanel>
         </HierarchicalDataTemplate>
     </TreeView.Resources>
 </TreeView>

VM

 private ObservableCollection<NodeInfo> _nodes = new();
 public ObservableCollection<NodeInfo> Nodes
 {
     get { return _nodes; }
     set
     {
         OnPropertyChanged(ref _nodes, value);
     }
 }

 private void InitNodes()
 {
     Nodes.Clear();
     for (int i = 0; i < 100; i++)
     {
         var node = new NodeInfo()
         {
             Name = $"Node{i}",
             NodeType = (NodeType)random.Next(0, 5),
             Description = $"Node{i} Description"
         };
         Nodes.Add(node);
     }
 }


 public ICommand StartChangeCommand => new RelayCommand(StartChange);
 private DispatcherTimer timer2 = new();
 private void StartChange(object obj)
 {
     //var random = new Random();
     //foreach (var node in Nodes)
     //{
     //    node.NodeType = (NodeType)random.Next(0, 5);
     //}
     foreach (var subNode in Nodes)
     {
         subNode.NodeType = NodeType.None;
     }
     CurNodeInfo = Nodes.First();
     timer2.Interval = TimeSpan.FromSeconds(3);
     timer2.Tick -= Timer2_Tick;
     timer2.Tick += Timer2_Tick;
     timer2.Start();

 }
 private NodeInfo _curNodeInfo = null;
 public NodeInfo CurNodeInfo
 {
     get => _curNodeInfo;
     set
     {
         OnPropertyChanged(ref _curNodeInfo, value);
     }
 }

 private async void Timer2_Tick(object? sender, EventArgs e)
 {
     CurNodeInfo.NodeType = NodeType.Running;
     CurNodeInfo.Description = "Running";
     var index = Nodes.IndexOf(CurNodeInfo);
     if (index == Nodes.Count - 1)
     {
         timer2.Stop();
     }

     CurNodeInfo.IsSelected = false;
     //Task.Delay(1000).ConfigureAwait(false);
     await Task.Delay(2000);
     //curNodeInfo.NodeType = NodeType.Success;
     CurNodeInfo.NodeType = (NodeType)(random.Next(0, 5));


     CurNodeInfo = Nodes[index + 1];

     CurNodeInfo.IsSelected = true;
 }

XAML后台

private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    if (e.NewValue == null)
        return;

    var item = FindTreeViewItem((sender as TreeView), e.NewValue);
    item?.BringIntoView();
}

private TreeViewItem FindTreeViewItem(ItemsControl parent, object data)
{
    foreach (var item in parent.Items)
    {
        if (item == data)
        {
            return parent.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
        }
        if (parent.ItemContainerGenerator.ContainerFromItem(item) is TreeViewItem treeViewItem)
        {
            if (treeViewItem.IsExpanded)
            {
                var result = FindTreeViewItem(treeViewItem, data);
                if (result != null)
                {
                    return result;
                }
            }
        }
    }
    return null;

}

重点

  • 其中XAML前台主要提供数据模板的绑定,当前测试树只有一层比较简单,多的其实也就是再写个HierarchicalDataTemplate
  • VM是绑定的ViewModel,主要提供数据,例如在加载的时候填充一百条数据。提供一个开始定时器的命令(StartChangeCommand) ,注意在定时器内部,一定需要使用异步来改变状态,不然正在运行的动画不会显示的。
  • XAML后台主要作用是在树选中的当前行变化的时候,如果当前行不可见的时候,例如在上面或者下面,这个时候将滚动条滚到当前行使得当前可见窗口包含这一行。

截图

treeview_test.png

备注

  • 需要注意的是:TreeViewSelectedItem是没办法双向绑定的,所以需要自己维护一个IsSelected来提供绑定到TreeViewItemIsSelected

0

评论区