河间网站建设公司,四川网站建设公司,缅甸网站建设,怎么建设一个音乐网站1.前言继上一篇《WPF应用基础篇---TreeView》的发布之后#xff0c;有部分朋问我关于里面一些基础应用的问题#xff0c;可能是我写得不够详细#xff0c;所以在这里#xff0c;我想再次那文章中的案例来谈谈初步体验数据驱动之美#xff0c;摆脱旧WinForm编程习惯(靠触发… 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 namenode/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 namenode/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 namesender/param /// param namee/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 nameCurrentNode/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 nameCurrentNode/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 ListDevice childNodes; public ListDevice 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 nameCurrentNode/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 nameCurrentNode/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,ModeTwoWay} Margin2 VerticalAlignmentCenter/ 这里只需要把实体的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