初步体验数据驱动之美---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?{?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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做输卵管导丝大概要多少钱?
- 下一篇: 关于varchar2在pl/sql和sc