zoukankan      html  css  js  c++  java
  • wpf中TreeView的使用

    1,前端代码,在前端主要是为了将数据源绑定到控件上,主要用的就是HierarchicalDataTemplate类

    <TreeView Name="TreeView_NodeList">
    <TreeView.Resources>
    <HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Nodes,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
    <StackPanel Orientation="Horizontal" Margin="0,2,0,2">
    <TextBlock Text="{Binding NodeName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ToolTip="{Binding NodeName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>
    </HierarchicalDataTemplate>
    </TreeView.Resources>
    </TreeView>

    其中还得定义Node类,Node对象使用Nodeld作为主键进行区分,Nodeid为32位的GUID。Node的属性Nodes保存节点的子节点集合。属性IsDeleted表及节点是否被删除。

    public class Node
    {
    public Node()
    {
    this.NodeId = Guid.NewGuid().ToString();
    this.IsDeleted = false;
    this.Nodes = new List<Node>();
    }

    /// <summary>
    /// 节点ID
    /// </summary>
    public string NodeId { get; set; }

    /// <summary>
    /// 节点名称
    /// </summary>
    public string NodeName { get; set; }

    /// <summary>
    /// 节点携带的内容
    /// </summary>
    public string NodeContent { get; set; }

    /// <summary>
    /// 被删除
    /// </summary>
    public bool IsDeleted { get; set; }

    /// <summary>
    /// 节点类型
    /// </summary>
    public NodeType NodeType { get; set; }

    /// <summary>
    /// 子节点集合
    /// </summary>
    public List<Node> Nodes { get; set; }
    }

    然后我们的用枚举类型的NodeType属性表示节点的类型,节点具有根节点、叶子结点和结构节点三种类别。叶子结点不支持增加节点操作、飞叶子结点不支持删除节点的操作。

    public enum NodeType

    {

    RootNode,//根节点

    LeafNode,//叶子节点

    StructureNode//结构节点,仅起到组织配置文件结构的作用,不参与修改

    }

    下面就是我们从.cs里面想xaml里面添加数据。做项目的时候可能会通过解析xml或者ini等文件或者访问数据库等方式获取数据源。

    private List<Node> GetNodeList()
    {
    Node leafOneNode = new Node();
    leafOneNode.NodeName = "叶子节点一";
    leafOneNode.NodeContent = "我是叶子节点一";
    leafOneNode.NodeType = NodeType.LeafNode;
    leafOneNode.Nodes = new List<Node>();

    Node leafTwoNode = new Node();
    leafTwoNode.NodeName = "叶子节点二";
    leafTwoNode.NodeContent = "我是叶子节点二";
    leafTwoNode.NodeType = NodeType.LeafNode;
    leafTwoNode.Nodes = new List<Node>();

    Node leafThreeNode = new Node();
    leafThreeNode.NodeName = "叶子节点三";
    leafThreeNode.NodeContent = "我是叶子节点三";
    leafThreeNode.NodeType = NodeType.LeafNode;
    leafThreeNode.Nodes = new List<Node>();

    Node secondLevelNode = new Node();
    secondLevelNode.NodeName = "二级节点";
    secondLevelNode.NodeContent = "我是二级节点";
    secondLevelNode.NodeType = NodeType.StructureNode;
    secondLevelNode.Nodes = new List<Node>() { leafOneNode, leafTwoNode, leafThreeNode };

    Node firstLevelNode = new Node();
    firstLevelNode.NodeName = "一级节点";
    firstLevelNode.NodeContent = "我是一级节点";
    firstLevelNode.NodeType = NodeType.StructureNode;
    firstLevelNode.Nodes = new List<Node>() { secondLevelNode };

    return new List<Node>()
    {
    new Node(){NodeName="根节点",NodeContent="我是根节点",NodeType=NodeType.RootNode,Nodes=new List<Node>(){firstLevelNode}}
    };
    }

    程序使用nodeList接收返回的测试数据,并在页面的Loaded事件中进行数据绑定。

    public List<Node> nodeList { get; set; }
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
    nodeList = GetNodeList();
    this.TreeView_NodeList.ItemsSource = nodeList;
    ExpandTree();
    }

    2、展开所有节点
    其中使用到的ExpandTree()方法实现展开TreeView的所有节点。

    private void ExpandTree()
    {
    if (this.TreeView_NodeList.Items != null && this.TreeView_NodeList.Items.Count > 0)
    {
    foreach (var item in this.TreeView_NodeList.Items)
    {
    DependencyObject dependencyObject = this.TreeView_NodeList.ItemContainerGenerator.ContainerFromItem(item);
    if (dependencyObject != null)//第一次打开程序,dependencyObject为null,会出错
    {
    ((TreeViewItem)dependencyObject).ExpandSubtree();
    }
    }
    }
    }
    执行程序,可以得到下面的执行结果。


    3、增加和删除节点
    下面给TreeView增加右键弹出菜单,实现“增加节点”和“删除节点”的操作。
    画面代码中设置TreeView的ContextMenu属性,增加弹出菜单项。

    <TreeView.ContextMenu>
    <ContextMenu x:Name="ContextMenu_EditNode">
    <MenuItem Header="新增节点" Name="MenuItem_AddNode" Click="MenuItem_AddNode_Click"/>
    <MenuItem Header="删除节点" Name="MenuItem_DeleteNode" Click="MenuItem_DeleteNode_Click"/>
    </ContextMenu>
    </TreeView.ContextMenu>
    下面是增加节点和删除节点的具体逻辑代码。

    /// <summary>
    /// 新增节点
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void MenuItem_AddNode_Click(object sender, RoutedEventArgs e)
    {
    var currentNode = FindTheNode(nodeList, selectedNode.NodeId);
    if (currentNode != null)
    {
    if (currentNode.NodeType == NodeType.LeafNode)
    {
    MessageBox.Show("叶子节点不支持新增节点操作!");
    }
    else
    {
    MessageBox.Show("开始新增节点操作!");
    }
    }
    }

    /// <summary>
    /// 删除节点
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void MenuItem_DeleteNode_Click(object sender, RoutedEventArgs e)
    {
    var currentNode = FindTheNode(nodeList, selectedNode.NodeId);
    if (currentNode != null)
    {
    if (currentNode.NodeType != NodeType.LeafNode)
    {
    MessageBox.Show("非叶子节点不支持删除操作!");
    }
    else
    {
    MessageBoxResult dr = MessageBox.Show("确定要删除这个节点吗?", "提示", MessageBoxButton.OKCancel);
    if (dr == MessageBoxResult.OK)
    {
    DeleteTheNode(nodeList, currentNode);
    MessageBox.Show("成功删除节点!");
    }
    else
    {
    return;
    }
    }
    }
    }

    其中删除节点需要使用下面的DeleteTheNode()方法,该方法使用递归思想实现删除指定节点的逻辑。

    private void DeleteTheNode(List<Node> nodeList, Node deleteNode)
    {
    foreach (Node node in nodeList)
    {
    if (node.Nodes != null && node.Nodes.Count > 0)
    {
    DeleteTheNode(node.Nodes, deleteNode);
    }
    if (node == deleteNode)
    {
    node.IsDeleted = true;
    }
    }
    }


    不管是删除节点还是新增节点,都需要获取当前选中的节点,使用FindTheNode()方法实现。

    private Node FindTheNode(List<Node> nodeList, string nodeId)
    {
    Node findedNode = null;
    foreach (Node node in nodeList)
    {
    if (node.Nodes != null && node.Nodes.Count > 0)
    {
    if ((findedNode = FindTheNode(node.Nodes, nodeId)) != null)
    {
    return findedNode;
    }
    }
    if (node.NodeId == nodeId)
    {
    return node;
    }
    }
    return findedNode;
    }
    4、处理PreviewMouseRightButtonDown事件
    因为叶子节点不支持增加节点操作,非叶子结点不支持删除节点操作,所以在点击鼠标右键时,需要检测选中节点的类型并进行控制。
    为了实现这个功能,可以为TreeView绑定事件PreviewMouseRightButtonDown,在事件处理器中进行相应的控制即可。

    <TreeView.ItemContainerStyle>
    <Style TargetType="{x:Type TreeViewItem}">
    <EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
    </Style>
    </TreeView.ItemContainerStyle>

    下面是PreviewMouseRightButtonDown事件的事件处理器代码。

    private void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
    var treeViewItem = VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
    if (treeViewItem != null)
    {
    Node currentNode = treeViewItem.Header as Node;
    if (currentNode.NodeType != NodeType.LeafNode)
    {
    MenuItem_AddNode.IsEnabled = true;
    MenuItem_DeleteNode.IsEnabled = false;
    }
    else
    {
    MenuItem_AddNode.IsEnabled = false;
    MenuItem_DeleteNode.IsEnabled = true;
    }
    treeViewItem.Focus();
    e.Handled = true;
    }
    }

    代码中使用到VisualUpwardSearch()方法,该方法用于寻找控件的最外层父控件。

    private DependencyObject VisualUpwardSearch<T>(DependencyObject source)
    {
    while (source != null && source.GetType() != typeof(T))
    {
    source = VisualTreeHelper.GetParent(source);
    }
    return source;
    }

    5、查找下一个节点
    有时候,还需要实现循环“查找下一个”的功能,在画面中增加输入框和按钮。

    <TextBox x:Name="TextBox_FindNextNode" Grid.Column="1" HorizontalAlignment="Left" Height="23" Margin="49,20,-160,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" LostFocus="TextBox_FindNextNode_LostFocus"/>
    <Button x:Name="Button_FindNextNode" Content="查找下一个" Grid.Column="1" HorizontalAlignment="Left" Margin="188,20,-254,0" VerticalAlignment="Top" Width="75" Click="Button_FindNextNode_Click"/>

    为了实现循环“查找下一个”,定义了下面两个对象。

    private int nextSearchedNodeIndex = -1;
    public List<Node> searchedNodeList { get; set; }

    searchedNodeList 记录根据关键字查找到的所有匹配的节点,nextSearchedNodeIndex 记录当前查看节点的位置。
    搜索框失去焦点时,查找匹配的节点并保存至searchedNodeList ,同时将索引置为-1。

    private void TextBox_FindNextNode_LostFocus(object sender, RoutedEventArgs e)
    {
    if (!string.IsNullOrEmpty(this.TextBox_FindNextNode.Text.Trim()))
    {
    nextSearchedNodeIndex = -1;
    searchedNodeList = FindMatchedNodes(nodeList, this.TextBox_FindNextNode.Text.Trim());
    }
    }

    点击“查找下一个”按钮,新增nextSearchedNodeIndex ,再使用nextSearchedNodeIndex 从searchedNodeList拿到当前展示的节点。这里需要注意的是一轮循环结束后,nextSearchedNodeIndex 再次置为-1,循环重新开始。

    private void Button_FindNextNode_Click(object sender, RoutedEventArgs e)
    {
    if (string.IsNullOrEmpty(this.TextBox_FindNextNode.Text.Trim()))
    {
    MessageBox.Show("请输入查找条件!");
    return;
    }
    if (searchedNodeList != null && searchedNodeList.Count > 0)
    {
    //循环查找下一个
    if (nextSearchedNodeIndex >= searchedNodeList.Count - 1)
    {
    nextSearchedNodeIndex = -1;
    }
    nextSearchedNodeIndex += 1;
    selectedNode = searchedNodeList[nextSearchedNodeIndex];
    SelectTheCurrentNode();
    }
    else
    {
    MessageBox.Show("没有找到满足条件的节点");
    }
    }

    其中,查找匹配节点的逻辑由FindMatchedNodes()方法完成,该方法同样使用递归思想来实现。

    private List<Node> FindMatchedNodes(List<Node> srcNodeList, string filterString)
    {
    List<Node> destNodeList = new List<Node>();
    foreach (Node node in srcNodeList)
    {
    if (node.NodeName.Contains(filterString))
    {
    destNodeList.Add(node);
    }
    if (node.Nodes != null && node.Nodes.Count > 0)
    {
    destNodeList.AddRange(FindMatchedNodes(node.Nodes, filterString));
    }
    }
    return destNodeList;
    }

    其中,SelectTheCurrentNode()方法实现选中当前节点,并将焦点置于当前选中节点。这里同样离不开递归的功劳。

    private void SelectTheCurrentNode()
    {
    if (this.TreeView_NodeList.Items != null && this.TreeView_NodeList.Items.Count > 0)
    {
    foreach (var item in this.TreeView_NodeList.Items)
    {
    DependencyObject dependencyObject = this.TreeView_NodeList.ItemContainerGenerator.ContainerFromItem(item);
    if (dependencyObject != null)//第一次打开程序,dependencyObject为null,会出错
    {
    TreeViewItem tvi = (TreeViewItem)dependencyObject;
    if ((tvi.Header as Node).NodeId == selectedNode.NodeId)
    {
    tvi.IsSelected = true;
    tvi.Focus();
    }
    else
    {
    SetNodeSelected(tvi);
    }
    }
    }
    }
    }
    private void SetNodeSelected(TreeViewItem Item)
    {
    foreach (var item in Item.Items)
    {
    DependencyObject dependencyObject = Item.ItemContainerGenerator.ContainerFromItem(item);
    if (dependencyObject != null)
    {
    TreeViewItem treeViewItem = (TreeViewItem)dependencyObject;
    if ((treeViewItem.Header as Node).NodeId == selectedNode.NodeId)
    {
    treeViewItem.IsSelected = true;
    treeViewItem.Focus();
    }
    else
    {
    treeViewItem.IsSelected = false;
    if (treeViewItem.HasItems)
    {
    SetNodeSelected(treeViewItem);
    }
    }
    }
    }
    }

    6、处理SelectedItemChanged事件
    操作TreeView,不然会选中节点,触发SelectedItemChanged事件,下面是SelectedItemChanged事件的处理代码。

    private void TreeView_NodeList_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
    Node node = this.TreeView_NodeList.SelectedItem as Node;
    if (node != null)
    {
    selectedNode = node;
    MessageBox.Show("当前选中的节点是:" + selectedNode.NodeName);
    }
    }

    本文结合shil介绍了TreeView层级数据源的绑定,选中事件的处理,展开节点、新增节点、删除节点、查找节点等常见操作。
    普遍使用到递归思想,是学习递归的完美练兵场。

  • 相关阅读:
    docker安装rabbitmq
    ios 崩溃日志揭秘
    Ubuntu apache2下建立django开发环境
    Ubuntu tomcat
    MySQLdb插入后获得插入id
    使用终端实现DMG、ISO格式互转
    ios学习笔记:jison(转)
    ios开发应用内实现多语言自由切换 (转)
    获取项目的名称及版本号
    工具收集
  • 原文地址:https://www.cnblogs.com/xiehaha123/p/11759236.html
Copyright © 2011-2022 走看看