初步体验数据驱动之美---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具体代码实现两态树:

ExpandedBlockStart.gifView 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具体代码实现两态树:

  

ExpandedBlockStart.gifView 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的控件上。

  

  数据结构实体代码:

  

ExpandedBlockStart.gifView 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具体实现代码:

  

ExpandedBlockStart.gifView 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/380333.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不可以!

描述 判断&#xff1a;两个数x、y的正负性。 要求&#xff1a;不可以使用比较运算符&#xff0c;即”<”,”>”,”<”,”>”,””,”!”。 输入 有多组数据&#xff0c;每组数据占一行&#xff0c;每一行两个数x&#xff0c;y。 x、y保证在int范围内。 输出 …

树的基本概念

0x01 树 树&#xff1a;n个结点的有限集合&#xff0c;n0&#xff0c;空树任何非空树只有一个根结点n个结点的树只有n-1条边&#xff08;除根结点&#xff0c;每个结点只有一个前驱&#xff0c;一个前驱一条边&#xff0c;根据这个算的&#xff09;有序树与无序树&#xff1a;…

小学计算机教学教师培训,例谈小学信息技术课堂的有效教学

例谈小学信息技术课堂的有效教学在社会的各个领域&#xff0c;大家都不可避免地会接触到论文吧&#xff0c;论文可以推广经验&#xff0c;交流认识。为了让您在写论文时更加简单方便&#xff0c;以下是小编整理的例谈小学信息技术课堂的有效教学的论文相关内容&#xff0c;供大…

C和汇编---数组

0x01 初始化数组 1、没有初始化数组 #include "stdio.h" int main(void) {int data[4];for (int i0;i<4;i){printf("%d\t",data[i]);}return 0; }不同系统&#xff0c;输出结果可能不一样&#xff1a; 反汇编&#xff1a; 4: int data[4]; 5…

操作系统 系统开销比率_操作系统中的最高响应比率下一个(HRRN)调度

操作系统 系统开销比率操作系统中的HRRN调度是什么&#xff1f; (What is HRRN Scheduling in Operating System?) HRRN is the abbreviation of Highest Response Ratio Next Scheduling. It is an optimal scheduling algorithm. HRRN是最高响应率下一个调度的缩写 。 这是…

利用堆栈做循环

程序&#xff1a; #include "stdio.h" int main(int argc,char *argv[]) {char *str"%d";printf("hello world");__asm{ log:lea eax,logpush eaxlea ebx,strpush ebxpush eaxcall printfret 8}return 0;}运行&#xff1a;一直死循环运行下去 …

c# 多线程异步demo

一个 c# winform 多线程异步demo&#xff0c;分享下。 因为例子都很简单&#xff0c;所以不多说明&#xff0c;自己下载吧。转载于:https://www.cnblogs.com/chaobao/archive/2011/08/18/CSharpSync.html

计算机编程要哪方面天赋,编程要哪门子天赋

开局一张图写代码真的需要天赋吗&#xff1f;有句话是这样说的&#xff1a;论大家的努力程度&#xff0c;远不到拼天赋的时候。我认为所谓的天赋&#xff0c;应该是行业内Top10%水平才需要天赋&#xff0c;比如Linux缔造者Linus Torvalds&#xff0c;苹果发明者斯蒂夫沃兹尼亚克…

C和汇编----字符串

字符串是以空字符&#xff08;\0&#xff09;结尾的char类型数组。 0x01 定义字符串和初始化 用双引号括起来的内容称为字符串字面量&#xff0c;也叫字符串常量&#xff0c;双引号中的字符串和编译器自动加入\0字符&#xff0c;都作为字符串存储在内存中 #include "st…

远控免杀专题2---msfvenom的隐藏参数

0x01 msfvenom简介 msfvenom是msfpayload和msfencode的结合体&#xff0c;与2015年6月8日取代了msfpayload和msfencode。在此之后&#xff0c;metasploit-framwork下面的msfpayload&#xff08;载荷生成器&#xff09;&#xff0c;msfencoder&#xff08;编码器&#xff09;&a…

转载CSDN - 从程序员到HR——面试经验分享

CSDN博客一周热文推荐&#xff0c;为您总结回顾过去一周的CSDN博客热门文章&#xff0c;推荐优质的博客作者&#xff0c;分享精华文章和优质博客。 [1] 谭海燕&#xff1a;北漂之惠普H3C面试经历 上一篇讲到了《北漂之百度面试》&#xff0c;今天跟大家分享我在H3C的面试经历。…

ai系统架构_人工智能中的模糊逻辑系统架构

ai系统架构The Fuzzy Logic System is a system which uses Fuzzy logic for reasoning. Fuzzy Logic is a very efficient method for performing human-like reasoning in conditions with uncertainty. 模糊逻辑系统是使用模糊逻辑进行推理的系统。 模糊逻辑是一种在不确定条…

Firefox中国即将成立,希望在华推行Web标准

Mozilla的首席技术官(vp engineering)Mike Schroepfer给Firefox的粉丝们带来一个好消息:Firefox在中国的负责人李宫昨日接到总部正式通知,将在中国成立公司.Mike称:“公司将设立在清华科技园,在Google、微软的旁边,不过一开始规模不会大,只有几个人.李宫将负责招兵买马.实际上,…

远控免杀专题3---msf自免杀

0x01 免杀能力一览表 上面表中标识 √ 说明相应杀毒软件未检测出病毒&#xff0c;也就是代表了Bypass。为了更好的对比效果&#xff0c;大部分测试payload均使用msf的windows/meterperter/reverse_tcp模块生成。由于本机测试时只是安装了360全家桶和火绒&#xff0c;所以默认情…

苹果手机的计算机删除了怎么恢复,苹果手机电话删除了怎么恢复

在清理手机通讯录的时候&#xff0c;万一不小心误删了手机通讯录&#xff0c;怎么办。那么被删除的手机通讯录还能找回吗?答案是可以的&#xff0c;苹果手机通讯录删除了怎么恢复呢。苹果手机电话删除了怎么恢复一、从iCloud恢复步骤1、打开【设置】-【Apple ID】-【iCloud】&…

远控免杀4---Evasion免杀

0x01 免杀能力一览表 1、下表中标识 √ 说明相应杀毒软件未检测出病毒&#xff0c;也就是代表了Bypass。2、为了更好的对比效果&#xff0c;大部分测试payload均使用msf的windows/meterperter/reverse_tcp模块生成。3、由于本机测试时只是安装了360全家桶和火绒&#xff0c;所以…

远控免杀5---Veil免杀

0x01 免杀能力一览表 1、下表中标识 √ 说明相应杀毒软件未检测出病毒&#xff0c;也就是代表了Bypass。2、为了更好的对比效果&#xff0c;大部分测试payload均使用msf的windows/meterperter/reverse_tcp模块生成。3、由于本机测试时只是安装了360全家桶和火绒&#xff0c;所以…

远控免杀专题6---Venom免杀

0x01 免杀能力一览表 几点说明&#xff1a; 1、上表中标识 √ 说明相应杀毒软件未检测出病毒&#xff0c;也就是代表了Bypass。 2、为了更好的对比效果&#xff0c;大部分测试payload均使用msf的windows/meterperter/reverse_tcp模块生成。 3、由于本机测试时只是安装了360全…

查看ajax传来的数据,jQuery AJAX 方法 success()后台传来的4种数据

1.后台返回一个页面js代码/**(1)用$("#content-wrapper").html(data);显示页面*/$.ajax({async : false,cache : false,type : POST,url : area/prepareCreate,error : function() {alert(smx失败 );},success : function(data) {$("#content-wrapper").ht…

远控免杀专题7 ---shellter免杀

0x01 免杀能力一览表 几点说明&#xff1a; 1、上表中标识 √ 说明相应杀毒软件未检测出病毒&#xff0c;也就是代表了Bypass。 2、为了更好的对比效果&#xff0c;大部分测试payload均使用msf的windows/meterperter/reverse_tcp模块生成。 3、由于本机测试时只是安装了360全…