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 { get; set; }
        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.附加源码:点击下载     

转载于:https://www.cnblogs.com/smlAnt/archive/2011/08/09/2130334.html

初步体验数据驱动之美---TreeView相关推荐

  1. 用计算机写作文主题,体验自然之美 感受田园欢乐——用计算机写作文教案

    体验自然之美 感受田园欢乐--用计算机写作文教案 体验自然之美 感受田园欢乐--用计算机写作文 一.教学目标1.知识与技能(1)认识 Word的工作窗口.(2)知道输入法的切换方式.(3)掌握用拼音输 ...

  2. mysql connector api_mysql connector c++ 1.1 API初步体验

    mysql connector c++ 1.1 API初步体验 1,常用的头文件 #include #include #include #include #include #include 2,创建连 ...

  3. roads 构筑极致用户体验_智美双极 引领旗舰 亚洲龙探索革新的高品质体验

    当冬日的暖阳穿过颐和园的十七孔桥,日光之下,白砖绿瓦均染上温暖的金黄色,匠人的鬼斧神工成就景色之美.千百年来,我们总在不断的探索美的定义,关于东方之美,源自艺术,源自智慧,也源自生活. 从颐和园十七孔 ...

  4. Docker Compose 简介、安装、初步体验

    [Docker那些事]系列文章 docker 安装 与 卸载 centos Dockerfile 文件结构.docker镜像构建过程详细介绍 Dockerfile文件中CMD指令与ENTRYPOINT ...

  5. 踏上Silverlight的征程 体验Silverlight之美

    (<Silverlight 2 完美征程>的作者序) 从2006年开始,微软推出了代码名为"WPF/E"的项目,该项目从诞生到正式命名为"Silverligh ...

  6. 百度无线音乐盒刷打印服务器,百度 M-100 无线音乐盒 初步体验+简单拆解

    百度 M-100 无线音乐盒 初步体验+简单拆解 2014-01-23 13:26:55 18点赞 26收藏 15评论 前段时间京东上面这个盒子做活动,虽然手头蓝牙音箱啥子的也有几个,不过受到这个盒子 ...

  7. 霍尼韦尔和陕西西咸新区签订投资战略协议;雀巢专业餐饮成都客户交流体验中心落成 | 美通企业日报...

    今日看点 霍尼韦尔宣布和陕西西咸新区签订投资战略协议.根据协议,霍尼韦尔计划将在陕西西咸新区投资开展以旗下智能建筑科技业务为主的运营,研发.市场和销售,整体投资规划将分阶段实施,一期项目将重点围绕消防 ...

  8. SEE Conf 2021 如期而至,体验科技极致美

    SEE Conf 2021将于2021年1月9日在杭州蚂蚁Z空间举办.大会网站:https://seeconf.antfin.com/ SEE Conf 2021 ▲ SEE Conf(支付宝体验科技 ...

  9. ShardingSphere UI 初步体验

    简介 在上两篇文章中,尝试了ShardingSphere JDBC和Proxy的相关功能,本篇进行探索ShardingSphere的UI组件部分 示例运行 这个应该是一个管理配置之类的东西,国际惯例, ...

最新文章

  1. 北工大计算机学院教授,北工大计算机学院计算机科学与技术导师介绍:杨宇光...
  2. fwrite视频写入帧率测试(不用测了。。)
  3. 笔记-信息系统安全管理-信息系统安全等级保护基本要求
  4. 程序员的十层楼(第11层)
  5. php 中国标准时间,linux 系统时间与硬件时间与中国标准时间
  6. LCD显示屏原理与应用
  7. python实例 85,86
  8. mysql5.7 innodb myisam 区别_InnoDB与MyISAM的区别(高性能MySQL笔记)
  9. LeetCode : Word Pattern
  10. 我的WCF4 Rest Service及Entity Framework with POCO之旅(三)——用Entity Framework和POCO Template实现数据模型及存储...
  11. springsecurity原理执行流程_3. Spark原理-执行流程解析
  12. 4米乘以12米CAD图_这篇文章解决了我多年CAD制图比例问题!
  13. Ubuntu 下网易云音乐出现“加载失败,网络错误,可以在设置中发送反馈”问题及解决
  14. 我常用的几个软件的注册码
  15. oracle 递归用法,oracle递归用法
  16. nano板载电脑连接无线时断时续
  17. python 小甲鱼小游戏_Python 小甲鱼教程 Easygui 篇
  18. cf 830 D1. Balance (Easy version)
  19. Matlab中grid函数的用法
  20. 随机过程之一——关于条件数学期望

热门文章

  1. ImageField,FileField上传文件命名问题
  2. postgresql 比较两个时间差大于 N个小时
  3. VS2012 颜色配置成黑色
  4. J2ME开发环境配置(MyEclipse插件+WTK+jdk)
  5. 计算机专业必须读的经典书籍
  6. Linux 命令简单介绍第一课笔记
  7. 简述SAS逻辑库的概念及建立方法。什么是临时库和永久库?
  8. 统计各个函数的耗时_分享一次CMS GC耗时狠高优化过程全记录
  9. linux下md5sum的使用
  10. 汽车电子嵌入式技术篇(一) -CRC-8和CRC-16算法