zoukankan      html  css  js  c++  java
  • 【WPF】树形结构TreeView的用法(MVVM)

    TreeView控件的用法还是有蛮多坑点的,最好记录一下。

    参考项目:


    静态的树形结构

    如果树形结构的所有子节点都已经确定且不会改动,可以直接在控制层用C#代码来生成这个TreeView。

                var rootItem = new OutlineTreeData
                {
                    outlineTypeName = "David Weatherbeam",
                    Children=
                    {
                        new OutlineTreeData
                        {
                            outlineTypeName="Alberto Weatherbeam",
                            Children=
                            {
                                new OutlineTreeData
                                {
                                    outlineTypeName="Zena Hairmonger",
                                    Children=
                                    {
                                        new OutlineTreeData
                                        {
                                            outlineTypeName="Sarah Applifunk",
                                        }
                                    }
                                },new OutlineTreeData
                                {
                                    outlineTypeName="Jenny van Machoqueen",
                                    Children=
                                    {
                                        new OutlineTreeData
                                        {
                                            outlineTypeName="Nick van Machoqueen",
                                        },
                                        new OutlineTreeData
                                        {
                                            outlineTypeName="Matilda Porcupinicus",
                                        },
                                        new OutlineTreeData
                                        {
                                            outlineTypeName="Bronco van Machoqueen",
                                        }
                                    }
                                }
                            }
                        },
                        new OutlineTreeData
                        {
                            outlineTypeName="Komrade Winkleford",
                            Children=
                            {
                                new OutlineTreeData
                                {
                                    outlineTypeName="Maurice Winkleford",
                                    Children=
                                    {
                                        new OutlineTreeData
                                        {
                                            outlineTypeName="Divinity W. Llamafoot",
                                        }
                                    }
                                },
                                new OutlineTreeData
                                {
                                    outlineTypeName="Komrade Winkleford, Jr.",
                                    Children=
                                    {
                                        new OutlineTreeData
                                        {
                                            outlineTypeName="Saratoga Z. Crankentoe",
                                        },
                                        new OutlineTreeData
                                        {
                                            outlineTypeName="Excaliber Winkleford",
                                        }
                                    }
                                }
                            }
                        }
                    }
                };    

    运行后能看到树形结构是下面的样子。


    获取TreeViewItem控件

    前台页面xaml:

     <!-- 树形结构 -->
     <TreeView x:Name="treeView" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,-10,0,0"
               ItemsSource="{Binding ItemTreeDataList}" BorderThickness="0" Width="215" Height="210">
         <TreeView.ItemContainerStyle>
             <Style TargetType="{x:Type TreeViewItem}">
                 <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                 <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                 <Setter Property="FontWeight" Value="Normal" />
                 <Style.Triggers>
                     <Trigger Property="IsSelected" Value="True">
                         <Setter Property="FontWeight" Value="Bold" />
                     </Trigger>
                 </Style.Triggers>
             </Style>
         </TreeView.ItemContainerStyle>
    
         <TreeView.ItemTemplate>
             <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                 <TextBlock x:Name="treeViewItemTB" Text="{Binding itemName}" Tag="{Binding itemId}"/>
             </HierarchicalDataTemplate>
         </TreeView.ItemTemplate>
     </TreeView>  

    尝试过在初始化时获取TreeViewItem,发现都是为Null。

    • TreeViewItem item= (TreeViewItem)(myWindow.treeView.ItemContainerGenerator.ContainerFromIndex(0)); // 无法获取,为Null!
    • VisualTreeHelper.GetChild(); // 无法获取,为Null!

    谷歌一下,看到这篇解答,下面这位跟我遇到的情况一样,用以上方法都无法获取TreeViewItem。

    不过他给出的答案是通过点击来获取到被选中的TreeViewItem。


    给TreeView默认选中一个TreeViewItem

    上面的办法通过点击TreeViewItem来从事件中获得这个控件,但是如果我们想生成TreeView后不靠手动点击,立马自动选中一个默认的TreeViewItem呢?注意此时控件还未渲染,通过ItemContainerGenerator无法获取到控件。

    此时只能考虑使用MVVM的绑定机制来获取控件!因为如果修改数据后,UI的更新是延迟的,无法立刻获取到前台控件。

    绑定:

    xaml中TreeView的ItemsSource绑定到ViewModel中的ItemTreeDataList列表,该列表中的元素类型是自定义ItemTreeData实体类。在样式中绑定好IsExpandedIsSelected属性TreeViewItem模板是绑定到Children属性,该属性在ItemTreeData实体类中。

    ViewModel:

    // Item的树形结构
    private ObservableCollection<ItemTreeData> itemTreeDataList;
    public ObservableCollection<ItemTreeData> ItemTreeDataList
    {
         get { return itemTreeDataList; }
         set { SetProperty(ref itemTreeDataList, value); }
    }

    ItemTreeData实体类

        public class ItemTreeData // 自定义Item的树形结构
        {
            public int itemId { get; set; }      // ID
            public string itemName { get; set; } // 名称
            public int itemStep { get; set; }    // 所属的层级
            public int itemParent { get; set; }  // 父级的ID
    
            private ObservableCollection<ItemTreeData> _children = new ObservableCollection<ItemTreeData >();
            public ObservableCollection<ItemTreeData> Children {  // 树形结构的下一级列表
                get
                {
                    return _children;
                }
            } 
              
            public bool IsExpanded { get; set; } // 节点是否展开
            public bool IsSelected { get; set; } // 节点是否选中
        }

    关于上面的层级/父级/下一级的概念,假设现在树形结构为以下结构,从上往下依此定义为根节点、零级节点、一级节点、二级节点。

    /*
    *  rootItem
    *      |----zeroTreeItem
    *                 |----firstTreeItem
    *                            |----secondTreeItem
    */

    控制层在生成TreeView时通过绑定的属性来设置默认选中的Item,预先将三个层级的Item分别装在不同的列表中使用。

    现在尝试把二级节点中的第一个Item作为默认选中的Item。关键代码如下:

            // 构造轮廓选择的树形结构
            private void InitTreeView()
            {
                // 添加树形结构
                ItemTreeData item = GetTreeData();
                myViewModel.ItemTreeDataList.Clear();
                myViewModel.ItemTreeDataList.Add(item);
            }
    
            // 构造树形结构
            private ItemTreeData GetTreeData()
            {
                /*
                 *  rootItem
                 *      |----zeroTreeItem
                 *                 |----firstTreeItem
                 *                            |----secondTreeItem
                 */
    
                // 根节点
                ItemTreeData rootItem = new ItemTreeData();
                rootItem.itemId = -1;
                rootItem.itemName = " -- 请选择轮廓 -- ";
                rootItem.itemStep = -1;
                rootItem.itemParent = -1;
                rootItem.IsExpanded = true; // 根节点默认展开
                rootItem.IsSelected = false;
    
                for (int i = 0; i < itemViewModel.ZeroStepList.Count; i++) // 零级分类
                {
                    Items zeroStepItem = itemViewModel.ZeroStepList[i];
                    ItemTreeData zeroTreeItem = new ItemTreeData();
                    zeroTreeItem.itemId = zeroStepItem.itemId;
                    zeroTreeItem.itemName = zeroStepItem.itemName;
                    zeroTreeItem.itemStep = zeroStepItem.itemSteps;
                    zeroTreeItem.itemParent = zeroStepItem.itemParent;
                    if (i == 0)
                    {
                        zeroTreeItem.IsExpanded = true; // 只有需要默认选中的第一个零级分类是展开的
                    }
                    zeroTreeItem.IsSelected = false;
                    rootItem.Children.Add(zeroTreeItem); // 零级节点无条件加入根节点
    
                    for (int j = 0; j < itemViewModel.FirstStepList.Count; j++) // 一级分类
                    {
                        Items firstStepItem = itemViewModel.FirstStepList[j];
                        if (firstStepItem.itemParent == zeroStepItem.itemId) //零级节点添加一级节点
                        {
                            ItemTreeData firstTreeItem = new ItemTreeData();
                            firstTreeItem.itemId = firstStepItem.itemId;
                            firstTreeItem.itemName = firstStepItem.itemName;
                            firstTreeItem.itemStep = firstStepItem.itemSteps;
                            firstTreeItem.itemParent = firstStepItem.itemParent;
                            if (j == 0)
                            {
                                firstTreeItem.IsExpanded = true; // 只有需要默认选中的第一个一级分类是展开的
                            }
                            firstTreeItem.IsSelected = false;
                            zeroTreeItem.Children.Add(firstTreeItem);
    
                            for (int k = 0; k < itemViewModel.SecondStepList.Count; k++) // 二级分类
                            {
                                Items secondStepItem = itemViewModel.SecondStepList[k];
                                if (secondStepItem.itemParent == firstStepItem.itemId) // 一级节点添加二级节点
                                {
                                    ItemTreeData secondTreeItem = new ItemTreeData();
                                    secondTreeItem.itemId = secondStepItem.itemId;
                                    secondTreeItem.itemName = secondStepItem.itemName;
                                    secondTreeItem.itemStep = secondStepItem.itemSteps;
                                    secondTreeItem.itemParent = secondStepItem.itemParent;
                                    if (k == 0)
                                    {
                                        // 默认选中第一个二级分类
                                        secondTreeItem.IsExpanded = true; // 已经没有下一级了,这个属性无所谓
                                        secondTreeItem.IsSelected = true;
                                    }
                                    firstTreeItem.Children.Add(secondTreeItem);
                                }
                            }
                        }
                    }
                }
    
                return rootItem;
            }

    通过初始化时给被绑定的属性赋值,使得TreeView默认选中了二级节点中的第一个TreeViewItem,效果如下图:

    注意点:

    • 只是修改目标节点的IsSelected = true还不够,还要把它的所有父节点(祖父节点)都设置IsExpanded = true才行!!!

    显示选中TreeViewItem的完整分类路径

    需求是如上图,要得到字符串"I户型_1.5-1_a2",即通过下划线连接得到“零级节点_一级节点_二级节点”,然后用一个TextBlock控件显示出来。注意这里不包含root根节点。

    该功能可以写在树形结构的选项改变事件中。因为初始化TreeView时就选中了其中一个Item,所以初始化时也会调用到选项改变事件。

            // 树形结构的选项改变事件
            private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
            {
                ItemTreeData selectedItem = (ItemTreeData)(myWindow.treeView.SelectedItem);
                if (selectedItem != null)
                {
                    // UI层找不到,只能在数据层找
                    List<string> nameList = new List<string>();
                    int step = selectedItem.itemStep;        // 可能值为:-1,0,1,2
                    int parentId = selectedItem.itemParent;  // 临时保存遍历中每次使用的Id
                    for (int i = 0; i < step; i++)     // 零级分类虽然有父节点root,但是数据层中没有相应的itemParent值(为-1)
                    {
                        ItemTreeData parent = this.GetTreeDataById(parentId);
                        if (parent != null)
                        {
                            nameList.Add(parent.itemName);
                            parentId = parent.itemParent;
                        }
                    }
    
                    // 组拼字符串 = 零级名称 + 一级名称 + 二级名称
                    string text = "";
                    for (int i = nameList.Count; i > 0; i--) // 倒序遍历
                    {
                        if (i == nameList.Count)
                        {
                            text = nameList[i - 1];
                        }
                        else
                        {
                            text = text + "_" + nameList[i - 1];
                        }
                    }
    
                    if (string.IsNullOrEmpty(text))
                    {
                        myWindow.itemSelectedTB.Text = selectedItem.itemName;
                    }
                    else
                    {
                        myWindow.itemSelectedTB.Text = text + "_" + selectedItem.itemName;
                    }
                }
            }
    
            // 根据Id,获得树形结构中指定的Item
            private ItemTreeData GetTreeDataById(int targetId)
            {
                ItemTreeData data = null;
    
                // 是否为根节点
                ItemTreeData root = myViewModel.ItemTreeDataList[0];
                if (root.itemId == targetId)
                {
                    data = root;
                    return data;
                }
                // 搜索零级大类
                foreach (ItemTreeData zeroStepData in root.Children)
                {
                    if (zeroStepData.itemId == targetId)
                    {
                        data = zeroStepData;
                        return data;
                    }
                    // 搜索一级分类
                    foreach (ItemTreeData firstStepData in zeroStepData.Children)
                    {
                        if (firstStepData.itemId == targetId)
                        {
                            data = firstStepData;
                            return data;
                        }
                        // 搜索二级分类
                        foreach (ItemTreeData secondStepData in firstStepData.Children)
                        {
                            if (secondStepData.itemId == targetId)
                            {
                                data = secondStepData;
                                return data;
                            }
                        }
                    }
                }
    
                return data;
            }

    注意点:

    • 同样是找不到TreeViewItem控件!UI层找不到,所以只能在数据层找它关联的ItemTreeData对象,从数据层中去获取itemName属性值。
    • 因为每一级ItemTreeData对象中记录了它的父级ID,所以往根节点方向遍历父节点、祖父节点时,先加入到List中的是上一级的节点名。而需求是“零级节点_一级节点_二级节点”的顺序,所以在使用List时需要倒序遍历!

    最后显示的完整分类如下:

  • 相关阅读:
    可惜CodeSmith不直接支持Oracle数据库对象...
    Windows Vista
    关于Web Service ...
    他赚了多少钱?
    关于CodeSmith中模板的属性编辑...
    关于Session超时...
    奇怪的CheckBoxList...
    Devdays 2006 China | 微软开发者日
    第二阶段冲刺第一次会议
    冲刺阶段第一天
  • 原文地址:https://www.cnblogs.com/guxin/p/wpf-how-to-use-treeview-by-mvvm.html
Copyright © 2011-2022 走看看