初步体验数据驱动之美---TreeView
1.前言
繼上一篇《WPF應用基礎篇---TreeView》的發(fā)布之后,有部分朋問我關(guān)于里面一些基礎應用的問題,可能是我寫得不夠詳細,所以在這里,我想再次那文章中的案例來談談初步體驗數(shù)據(jù)驅(qū)動之美,擺脫舊WinForm編程習慣(靠觸發(fā)事件來實現(xiàn)界面的變化)。
2.背景
? 我們看看以下案例圖片的功能如何實現(xiàn):
?
圖1-1(WinForm兩態(tài)樹) ???? 圖1-2(WPF三態(tài)樹)
如果我們還處在習慣于WinForm開發(fā)的時候,我們首先關(guān)注的是,我們需要重寫Tree控件,在上一篇文章中有提到過,這里就不再重復。然后當我們布局和設計好數(shù)據(jù)結(jié)構(gòu)后,我們關(guān)心的自然就是選中的時候要做什么,我們首先會考慮到為樹節(jié)點添加事件來處理相應的邏輯處理。大致實現(xiàn)以下幾個步驟(簡單的分析)
- 把sender或者e參數(shù)轉(zhuǎn)換為TreeNode
- 從TreeNode中的Tag數(shù)據(jù)
- 根據(jù)Tag的類型轉(zhuǎn)換為具體數(shù)據(jù)
- 判斷TreeNode選中的狀態(tài),更改Tag實例的屬性的狀態(tài)如(IsSelected)
- 根據(jù)需求比如:
全部選中-->父節(jié)點CheckBox打鉤 同時修改父節(jié)點數(shù)據(jù),根據(jù)當前修改所有子節(jié)點狀態(tài)
全部未選中-->父節(jié)點CheckBox為空 同時修改父節(jié)點數(shù)據(jù),根據(jù)當前修改所有子節(jié)點狀態(tài)
WinForm具體代碼實現(xiàn)兩態(tài)樹:
View Code /// <summary>??????? /// 設置父節(jié)點狀態(tài)
??????? /// </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>
??????? /// 設置孩子節(jié)點狀態(tài)
??????? /// </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>
??????? /// 樹節(jié)點被選中后 觸發(fā)的事件
??????? /// </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之后,我們的編程習慣會發(fā)生了很大的變化,我們開始有點對觸發(fā)事件來改變邏輯和界面變化(事件驅(qū)動)的做法感到反感。解決上面的問題,我們只需要靠一個接口的幫助,就能實現(xiàn)兩態(tài)樹的功能。
- 實現(xiàn)INotifyPropertyChanged解口
- 當數(shù)據(jù)改變時修改父節(jié)點和相應子節(jié)點的狀態(tài),然后把數(shù)據(jù)綁定到界面上去。?
WPF具體代碼實現(xiàn)兩態(tài)樹:
View Code //是否被選中
????????private?bool??isSelected;
????????public?bool??IsSelected?
????????{
????????????get?{?return?isSelected;?}
????????????set
????????????{
????????????????if?(isSelected?!=?value)
????????????????{
????????????????????isSelected?=?value;???
????????????????????ChangeChildNodes(this);
????????????????????ChangedParentNodes(this);
????????????????????NotifyPropertyChanged("IsSelected");
????????????????}
????????????}
????????}
///?<summary>
????????///?向下遍歷,更改孩子節(jié)點狀態(tài)
????????///?注意:這里的父節(jié)點不是屬性而是字段
????????///?采用字段的原因是因為不想讓父節(jié)點觸發(fā)訪問器而觸發(fā)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>
????????///?向上遍歷,更改父節(jié)點狀態(tài)
????????///?注意:這里的父節(jié)點不是屬性而是字段
????????///?采用字段的原因是因為不想讓父節(jié)點觸發(fā)訪問器而觸發(fā)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實現(xiàn)代碼是事件驅(qū)動,首先觸發(fā)一個事件,然后進行一些邏輯判斷,而且還需要借助全部變量IsClick來防止代碼無限遞歸。而WPF的實現(xiàn)則是靠數(shù)據(jù)驅(qū)動,數(shù)據(jù)變化了,然后才調(diào)用方法來更改數(shù)據(jù)的相應狀態(tài)。最后才通知界面刷新數(shù)據(jù)。其實可以看出現(xiàn)在的需求很簡單就是,根據(jù)節(jié)點選中狀態(tài)操作樹,但是如果我的需求變化了,例如圖1-2的需求一樣,如果我需要打鉤的時候,操作按鈕的狀態(tài),比如打鉤就連接,不打鉤則斷開。WinForm的話又要在代碼中做一些邏輯判斷,這很容易實現(xiàn),但是如果我斷開按鈕按下的時候,只能點擊連接,這時候WinForm的事件就要做很多邏輯處理,如果需求要求的功能多的話,事件的后臺代碼將越來越復雜,最后導致邏輯混亂。而WPF實現(xiàn)的話,則是根據(jù)數(shù)據(jù)變化而且在界面上顯示,當我點擊的時候,修改下數(shù)據(jù)的狀態(tài)則可以。后臺無需要做太多的處理,這樣代碼結(jié)構(gòu)和邏輯會變得相對清晰。
3.三態(tài)樹具體實現(xiàn)
這里將為大家介紹下三態(tài)樹在WPF中的實現(xiàn),也是對上一篇文章的補充。本案例是在基于MVVM的基礎上實現(xiàn)的。要實現(xiàn)圖1-2(三態(tài)樹)只需要做以下兩個步驟。
- 定義好數(shù)據(jù)結(jié)構(gòu),并在數(shù)據(jù)上通過實現(xiàn)INotifyPropertyChanged接口,來屬性變化后通知View刷新數(shù)據(jù)。
- 把想對應的屬性Binding到View的控件上。
數(shù)據(jù)結(jié)構(gòu)實體代碼:
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>
????????///?向下遍歷,更改孩子節(jié)點狀態(tài)
????????///?注意:這里的父節(jié)點不是屬性而是字段
????????///?采用字段的原因是因為不想讓父節(jié)點觸發(fā)訪問器而觸發(fā)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>
????????///?向上遍歷,更改父節(jié)點狀態(tài)
????????///?注意:這里的父節(jié)點不是屬性而是字段
????????///?采用字段的原因是因為不想讓父節(jié)點觸發(fā)訪問器而觸發(fā)Setter
????????///?</summary>
????????///?<param?name="CurrentNode"></param>
????????public?void?ChangedParentNodes(Device?CurrentNode)
????????{
????????????if?(CurrentNode.ParentNode?!=?null)
????????????{
????????????????bool??parentNodeState?=?true;
????????????????int?selectedCount?=?0;??//被選中的個數(shù)
????????????????int?noSelectedCount?=?0;????//不被選中的個數(shù)
????????????????foreach?(var?data?in?CurrentNode.ParentNode.ChildNodes)
????????????????{
????????????????????if?(data.IsSelected?==?true)
????????????????????{
????????????????????????selectedCount++;
????????????????????}
????????????????????else?if?(data.IsSelected?==?false)
????????????????????{
????????????????????????noSelectedCount++;
????????????????????}
????????????????}
????????????????//如果全部被選中,則修改父節(jié)點為選中
????????????????if?(selectedCount?==?
????????????????????CurrentNode.ParentNode.ChildNodes.Count)
????????????????{
????????????????????parentNodeState?=?true;
????????????????}
????????????????//如果全部不被選中,則修改父節(jié)點為不被選中
????????????????else?if?(noSelectedCount?==?
????????????????????CurrentNode.ParentNode.ChildNodes.Count)
????????????????{
????????????????????parentNodeState?=?false;
????????????????}
????????????????//否則標記父節(jié)點(例如用實體矩形填滿)
????????????????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具體實現(xiàn)代碼:
View Code <CheckBox?IsChecked="{Binding?IsSelected,Mode=TwoWay}"?Margin="2"?VerticalAlignment="Center"/>
??? 這里只需要把實體的IsSelected屬性Bingding到View上,Mode是雙向的就可以了,具體的邏輯有實體內(nèi)部做處理,這樣更能體現(xiàn)出View中代碼的干凈,而且更能讓View和ViewModel耦合性降到最低。實現(xiàn)三態(tài)樹的時候有一個小技巧,讓代碼避開了無限遞歸的問題,這里采用屬性如IsSelected,屬性有setter和gettter訪問器,當我們向上、下遍歷的時候,改變的是數(shù)據(jù)中的字段isSelected,這樣就不會觸發(fā)了屬性的setter。這也是數(shù)據(jù)驅(qū)動的一個優(yōu)點之一。
4.總結(jié)
WPF的主要思想是用數(shù)據(jù)驅(qū)動來代替事件驅(qū)動。當數(shù)據(jù)發(fā)生變化的時候才做出一些相應的處理。這樣的好處就是:
- 使得代碼邏輯更加清晰。
- 可以讓數(shù)據(jù)發(fā)生變化,通過屬性訪問器來控制相應的邏輯變化(其實也是數(shù)據(jù)變化),最后通知View。這樣簡化了邏輯處理而且減少了邏輯混亂的局面。
- 有利于降低View和ViewModel(或后臺具體實現(xiàn)代碼)之間的耦合度,也就是說有利于把強依賴關(guān)系轉(zhuǎn)為弱依賴甚至沒依賴關(guān)系。
5.附加源碼:點擊下載 ?
轉(zhuǎn)載于:https://www.cnblogs.com/smlAnt/archive/2011/08/09/2130334.html
總結(jié)
以上是生活随笔為你收集整理的初步体验数据驱动之美---TreeView的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做输卵管导丝大概要多少钱?
- 下一篇: 石英石台面多少钱一米啊?