zoukankan      html  css  js  c++  java
  • 初步体验数据驱动之美TreeView

      

      1.前言

      继上一篇《WPF应用基础篇---TreeView》的发布之后,有部分朋问我关于里面一些基础应用的问题,可能是我写得不够详细,所以在这里,我想再次那文章中的案例来谈谈初步体验数据驱动之美,摆脱旧WinForm编程习惯(靠触发事件来实现界面的变化)。

     2.背景

      

       我们看看以下案例图片的功能如何实现:

       

        图1-1(WinForm两态树)           图1-2(WPF三态树)

      如果我们还处在习惯于WinForm开发的时候,我们首先关注的是,我们需要重写Tree控件,在上一篇文章中有提到过,这里就不再重复。然后当我们布局和设计好数据结构后,我们关心的自然就是选中的时候要做什么,我们首先会考虑到为树节点添加事件来处理相应的逻辑处理。大致实现以下几个步骤(简单的分析)

    • 把sender或者e参数转换为TreeNode
    • 从TreeNode中的Tag数据
    • 根据Tag的类型转换为具体数据
    • 判断TreeNode选中的状态,更改Tag实例的属性的状态如(IsSelected)
    • 根据需求比如:

              全部选中-->父节点CheckBox打钩 同时修改父节点数据,根据当前修改所有子节点状态

              全部未选中-->父节点CheckBox为空 同时修改父节点数据,根据当前修改所有子节点状态

      WinForm具体代码实现两态树:

    View Code
    /// <summary>
           
    /// 设置父节点状态
           
    /// </summary>
           
    /// <param name="node"></param>
            public void SetParentNodeStatus(TreeNode node)
            {
               
    if (node.Parent != null)
                {
                   
    bool isChecked = true;
                   
    foreach (TreeNode data in node.Parent.Nodes)
                    {
                       
    if (!data.Checked)
                        {
                            isChecked
    = false;
                           
    break;
                        }
                    }

                   
    if (isChecked)
                    {
                        node.Parent.Checked
    = true;
                       
    if(node.Parent.Parent!=null)
                        {
                            SetParentNodeStatus(node.Parent);
                        }
                    }
                   
    else
                    {
                        node.Parent.Checked
    = false;
                    }
                }
            }

           
    /// <summary>
           
    /// 设置孩子节点状态
           
    /// </summary>
           
    /// <param name="node"></param>
            public void SetChildNodeStatus(TreeNode node)
            {
               
    if (node.Nodes!=null)
                {
                   
    foreach (TreeNode data in node.Nodes)
                    {
                        data.Checked
    = node.Checked;
                       
    if (data.Nodes!=null)
                        {
                            SetChildNodeStatus(data);
                        }
                    }
                }
            }

           
    /// <summary>
           
    /// 树节点被选中后 触发的事件
           
    /// </summary>
           
    /// <param name="sender"></param>
           
    /// <param name="e"></param>
            private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
            {
              
    //isClick是全局变量
                
    //是为了解决无限递归而是用的一个标志
                if (!isClick)              
                  {
                   
    return;
                }

                isClick
    = false;
                TreeNode node
    = e.Node;           
               
    if (node.Parent != null)
                {
                    SetParentNodeStatus(e.Node);
                }
               
    if (node.Nodes != null)
                {
                    SetChildNodeStatus(node);
                }
                isClick
    = true;
            }

       而当我们开始慢慢采用WPF之后,我们的编程习惯会发生了很大的变化,我们开始有点对触发事件来改变逻辑和界面变化(事件驱动)的做法感到反感。解决上面的问题,我们只需要靠一个接口的帮助,就能实现两态树的功能。

    • 实现INotifyPropertyChanged解口
    • 当数据改变时修改父节点和相应子节点的状态,然后把数据绑定到界面上去。 

      WPF具体代码实现两态树:

      

    View Code
    //是否被选中
            private bool? isSelected;
            
    public bool? IsSelected 
            {
                
    get { return isSelected; }
                
    set
                {
                    
    if (isSelected != value)
                    {
                        isSelected 
    = value;   
                        ChangeChildNodes(
    this);
                        ChangedParentNodes(
    this);
                        NotifyPropertyChanged(
    "IsSelected");
                    }
                }
            }

    /// <summary>
            
    /// 向下遍历,更改孩子节点状态
            
    /// 注意:这里的父节点不是属性而是字段
            
    /// 采用字段的原因是因为不想让父节点触发访问器而触发Setter
            
    /// </summary>
            
    /// <param name="CurrentNode"></param>
            public void ChangeChildNodes(Device CurrentNode)
            {
                
    if (CurrentNode.ChildNodes != null)
                {
                    
    foreach (var data in CurrentNode.ChildNodes)
                    {
                        data.isSelected 
    = CurrentNode.IsSelected;
                        data.NotifyPropertyChanged(
    "IsSelected");
                        
    if (data.ChildNodes != null)
                        {
                            data.ChangeChildNodes(data);
                        }
                    }
                }
            }

            
    /// <summary>
            
    /// 向上遍历,更改父节点状态
            
    /// 注意:这里的父节点不是属性而是字段
            
    /// 采用字段的原因是因为不想让父节点触发访问器而触发Setter
            
    /// </summary>
            
    /// <param name="CurrentNode"></param>
            public void ChangedParentNode(Device CurrentNode)
            {
                
    if (CurrentNode.ParentNode != null)
                {
                    
    bool isCheck = true;
                    
    foreach (var data in CurrentNode.ParentNode.ChildNodes)
                    {
                        
    if (data.IsSelected != true)
                        {
                            isCheck 
    = false;
                            
    break;
                        }
                    }
                    CurrentNode.parentNode.isSelected 
    = isCheck;
                    CurrentNode.parentNode.NotifyPropertyChanged(
    "IsSelected");
                }
            }

      从两段代码可以看出,WinForm实现代码是事件驱动,首先触发一个事件,然后进行一些逻辑判断,而且还需要借助全部变量IsClick来防止代码无限递归。而WPF的实现则是靠数据驱动,数据变化了,然后才调用方法来更改数据的相应状态。最后才通知界面刷新数据。其实可以看出现在的需求很简单就是,根据节点选中状态操作树,但是如果我的需求变化了,例如图1-2的需求一样,如果我需要打钩的时候,操作按钮的状态,比如打钩就连接,不打钩则断开。WinForm的话又要在代码中做一些逻辑判断,这很容易实现,但是如果我断开按钮按下的时候,只能点击连接,这时候WinForm的事件就要做很多逻辑处理,如果需求要求的功能多的话,事件的后台代码将越来越复杂,最后导致逻辑混乱。而WPF实现的话,则是根据数据变化而且在界面上显示,当我点击的时候,修改下数据的状态则可以。后台无需要做太多的处理,这样代码结构和逻辑会变得相对清晰。

     3.三态树具体实现

      这里将为大家介绍下三态树在WPF中的实现,也是对上一篇文章的补充。本案例是在基于MVVM的基础上实现的。要实现图1-2(三态树)只需要做以下两个步骤。

    • 定义好数据结构,并在数据上通过实现INotifyPropertyChanged接口,来属性变化后通知View刷新数据。
    • 把想对应的属性Binding到View的控件上。

      

      数据结构实体代码:

      

    View Code
    /// <summary>
        
    /// 设备基类
        
    /// </summary>
        public class Device:INotifyPropertyChanged
        {
            
    //是否被选中
            private bool? isSelected;
            
    public bool? IsSelected 
            {
                
    get { return isSelected; }
                
    set
                {
                    
    if (isSelected != value)
                    {
                        isSelected 
    = value;   
                        ChangeChildNodes(
    this);
                        ChangedParentNodes(
    this);
                        NotifyPropertyChanged(
    "IsSelected");
                    }
                }
            }
            
            
    private DeviceStatus status;
            
    public DeviceStatus Status
            {
                
    get { return status; }
                
    set
                {
                    
    if (status != value)
                    {
                        status 
    = value;
                        NotifyPropertyChanged(
    "Status");
                    }
                }
            }

            
    public string Name { getset; }
            
    public string ImageUrl{get;set;}

            
    private List<Device> childNodes;
            
    public List<Device> ChildNodes
            {
                
    get { return childNodes; }
                
    set
                {
                    
    if (childNodes != value)
                    {
                        childNodes 
    = value;
                        NotifyPropertyChanged(
    "ChildNodes");
                    }
                }
            }

            
    private Device parentNode;
            
    public Device ParentNode
            {
                
    get { return parentNode; }
                
    set
                {
                    
    if (parentNode != value)
                    {
                        parentNode 
    = value;
                        NotifyPropertyChanged(
    "ParentNode");
                    }
                }
            }

            
    /// <summary>
            
    /// 向下遍历,更改孩子节点状态
            
    /// 注意:这里的父节点不是属性而是字段
            
    /// 采用字段的原因是因为不想让父节点触发访问器而触发Setter
            
    /// </summary>
            
    /// <param name="CurrentNode"></param>
            public void ChangeChildNodes(Device CurrentNode)
            {
                
    if (CurrentNode.ChildNodes != null)
                {
                    
    foreach (var data in CurrentNode.ChildNodes)
                    {
                        data.isSelected 
    = CurrentNode.IsSelected;
                        data.NotifyPropertyChanged(
    "IsSelected");
                        
    if (data.ChildNodes != null)
                        {
                            data.ChangeChildNodes(data);
                        }
                    }
                }
            }

            
    /// <summary>
            
    /// 向上遍历,更改父节点状态
            
    /// 注意:这里的父节点不是属性而是字段
            
    /// 采用字段的原因是因为不想让父节点触发访问器而触发Setter
            
    /// </summary>
            
    /// <param name="CurrentNode"></param>
            public void ChangedParentNodes(Device CurrentNode)
            {
                
    if (CurrentNode.ParentNode != null)
                {
                    
    bool? parentNodeState = true;
                    
    int selectedCount = 0;  //被选中的个数
                    int noSelectedCount = 0;    //不被选中的个数

                    
    foreach (var data in CurrentNode.ParentNode.ChildNodes)
                    {
                        
    if (data.IsSelected == true)
                        {
                            selectedCount
    ++;
                        }
                        
    else if (data.IsSelected == false)
                        {
                            noSelectedCount
    ++;
                        }
                    }

                    
    //如果全部被选中,则修改父节点为选中
                    if (selectedCount == 
                        CurrentNode.ParentNode.ChildNodes.Count)
                    {
                        parentNodeState 
    = true;
                    }
                    
    //如果全部不被选中,则修改父节点为不被选中
                    else if (noSelectedCount == 
                        CurrentNode.ParentNode.ChildNodes.Count)
                    {
                        parentNodeState 
    = false;
                    }
                    
    //否则标记父节点(例如用实体矩形填满)
                    else
                    {
                        parentNodeState 
    = null;
                    }

                    CurrentNode.parentNode.isSelected 
    = parentNodeState;
                    CurrentNode.parentNode.NotifyPropertyChanged(
    "IsSelected");

                    
    if (CurrentNode.ParentNode.ParentNode != null)
                    {
                        ChangedParentNodes(CurrentNode.parentNode);
                    }
                }
            }

            
    public void NotifyPropertyChanged(string name)
            {
                
    if(PropertyChanged!=null)
                PropertyChanged(
    this,new PropertyChangedEventArgs(name));
            }
            
    public event PropertyChangedEventHandler PropertyChanged;
        }

      

      View具体实现代码:

      

    View Code
    <CheckBox IsChecked="{Binding IsSelected,Mode=TwoWay}" Margin="2" VerticalAlignment="Center"/>

        这里只需要把实体的IsSelected属性Bingding到View上,Mode是双向的就可以了,具体的逻辑有实体内部做处理,这样更能体现出View中代码的干净,而且更能让View和ViewModel耦合性降到最低。实现三态树的时候有一个小技巧,让代码避开了无限递归的问题,这里采用属性如IsSelected,属性有setter和gettter访问器,当我们向上、下遍历的时候,改变的是数据中的字段isSelected,这样就不会触发了属性的setter。这也是数据驱动的一个优点之一。

      4.总结     

      WPF的主要思想是用数据驱动来代替事件驱动。当数据发生变化的时候才做出一些相应的处理。这样的好处就是:

    • 使得代码逻辑更加清晰。
    • 可以让数据发生变化,通过属性访问器来控制相应的逻辑变化(其实也是数据变化),最后通知View。这样简化了逻辑处理而且减少了逻辑混乱的局面。
    • 有利于降低View和ViewModel(或后台具体实现代码)之间的耦合度,也就是说有利于把强依赖关系转为弱依赖甚至没依赖关系。  

      5.附加源码:点击下载      


      作者:SmlAnt

      出处:http://www.cnblogs.com/smlAnt

      注意:转载请保留以上内容,并标作者和出处。


  • 相关阅读:
    js判断空对象
    浅析css布局模型2
    Python 绘图
    我的第一个 Kaggle 比赛学习
    写代码 Log 也要认真点么?
    Python 线性回归(Linear Regression)
    Python
    Git
    算法4:插入排序和选择排序算法的比较
    《算法4》2.1
  • 原文地址:https://www.cnblogs.com/smlAnt/p/2130334.html
Copyright © 2011-2022 走看看