繼上一篇《WPF應(yīng)用基礎(chǔ)篇---TreeView》的發(fā)布之后,有部分朋問我關(guān)于里面一些基礎(chǔ)應(yīng)用的問題,可能是我寫得不夠詳細(xì),所以在這里,我想再次那文章中的案例來談?wù)劤醪襟w驗(yàn)數(shù)據(jù)驅(qū)動之美,擺脫舊WinForm編程習(xí)慣(靠觸發(fā)事件來實(shí)現(xiàn)界面的變化)。
1.背景
我們看看以下案例圖片的功能如何實(shí)現(xiàn):

圖1-1(WinForm兩態(tài)樹) 圖1-2(WPF三態(tài)樹)
如果我們還處在習(xí)慣于WinForm開發(fā)的時候,我們首先關(guān)注的是,我們需要重寫Tree控件,在上一篇文章中有提到過,這里就不再重復(fù)。然后當(dāng)我們布局和設(shè)計(jì)好數(shù)據(jù)結(jié)構(gòu)后,我們關(guān)心的自然就是選中的時候要做什么,我們首先會考慮到為樹節(jié)點(diǎn)添加事件來處理相應(yīng)的邏輯處理。大致實(shí)現(xiàn)以下幾個步驟(簡單的分析)
- 把sender或者e參數(shù)轉(zhuǎn)換為TreeNode
- 從TreeNode中的Tag數(shù)據(jù)
- 根據(jù)Tag的類型轉(zhuǎn)換為具體數(shù)據(jù)
- 判斷TreeNode選中的狀態(tài),更改Tag實(shí)例的屬性的狀態(tài)如(IsSelected)
- 根據(jù)需求比如:
全部選中-->父節(jié)點(diǎn)CheckBox打鉤 同時修改父節(jié)點(diǎn)數(shù)據(jù),根據(jù)當(dāng)前修改所有子節(jié)點(diǎn)狀態(tài)
全部未選中-->父節(jié)點(diǎn)CheckBox為空 同時修改父節(jié)點(diǎn)數(shù)據(jù),根據(jù)當(dāng)前修改所有子節(jié)點(diǎn)狀態(tài)
WinForm具體代碼實(shí)現(xiàn)兩態(tài)樹:
/// <summary>
/// 設(shè)置父節(jié)點(diǎn)狀態(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>
/// 設(shè)置孩子節(jié)點(diǎn)狀態(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é)點(diǎn)被選中后 觸發(fā)的事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
//isClick是全局變量
//是為了解決無限遞歸而是用的一個標(biāo)志
if (!isClick)
{
return;
}
isClick = false;
TreeNode node = e.Node;
if (node.Parent != null)
{
SetParentNodeStatus(e.Node);
}
if (node.Nodes != null)
{
SetChildNodeStatus(node);
}
isClick = true;
}
而當(dāng)我們開始慢慢采用WPF之后,我們的編程習(xí)慣會發(fā)生了很大的變化,我們開始有點(diǎn)對觸發(fā)事件來改變邏輯和界面變化(事件驅(qū)動)的做法感到反感。解決上面的問題,我們只需要靠一個接口的幫助,就能實(shí)現(xiàn)兩態(tài)樹的功能。
- 實(shí)現(xiàn)INotifyPropertyChanged解口
- 當(dāng)數(shù)據(jù)改變時修改父節(jié)點(diǎn)和相應(yīng)子節(jié)點(diǎn)的狀態(tài),然后把數(shù)據(jù)綁定到界面上去。
WPF具體代碼實(shí)現(xiàn)兩態(tài)樹:
//是否被選中
private bool? isSelected;
public bool? IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
ChangeChildNodes(this);
ChangedParentNodes(this);
NotifyPropertyChanged("IsSelected");
}
}
}
/// <summary>
/// 向下遍歷,更改孩子節(jié)點(diǎn)狀態(tài)
/// 注意:這里的父節(jié)點(diǎn)不是屬性而是字段
/// 采用字段的原因是因?yàn)椴幌胱尭腹?jié)點(diǎn)觸發(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é)點(diǎn)狀態(tài)
/// 注意:這里的父節(jié)點(diǎn)不是屬性而是字段
/// 采用字段的原因是因?yàn)椴幌胱尭腹?jié)點(diǎn)觸發(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實(shí)現(xiàn)代碼是事件驅(qū)動,首先觸發(fā)一個事件,然后進(jìn)行一些邏輯判斷,而且還需要借助全部變量IsClick來防止代碼無限遞 歸。而WPF的實(shí)現(xiàn)則是靠數(shù)據(jù)驅(qū)動,數(shù)據(jù)變化了,然后才調(diào)用方法來更改數(shù)據(jù)的相應(yīng)狀態(tài)。最后才通知界面刷新數(shù)據(jù)。其實(shí)可以看出現(xiàn)在的需求很簡單就是,根據(jù) 節(jié)點(diǎn)選中狀態(tài)操作樹,但是如果我的需求變化了,例如圖1-2的需求一樣,如果我需要打鉤的時候,操作按鈕的狀態(tài),比如打鉤就連接,不打鉤則斷開。 WinForm的話又要在代碼中做一些邏輯判斷,這很容易實(shí)現(xiàn),但是如果我斷開按鈕按下的時候,只能點(diǎn)擊連接,這時候WinForm的事件就要做很多邏輯 處理,如果需求要求的功能多的話,事件的后臺代碼將越來越復(fù)雜,最后導(dǎo)致邏輯混亂。而WPF實(shí)現(xiàn)的話,則是根據(jù)數(shù)據(jù)變化而且在界面上顯示,當(dāng)我點(diǎn)擊的時 候,修改下數(shù)據(jù)的狀態(tài)則可以。后臺無需要做太多的處理,這樣代碼結(jié)構(gòu)和邏輯會變得相對清晰。
2.三態(tài)樹具體實(shí)現(xiàn)
這里將為大家介紹下三態(tài)樹在WPF中的實(shí)現(xiàn),也是對上一篇的補(bǔ)充。本案例是在基于MVVM的基礎(chǔ)上實(shí)現(xiàn)的。要實(shí)現(xiàn)圖1-2(三態(tài)樹)只需要做以下兩個步驟。
- 定義好數(shù)據(jù)結(jié)構(gòu),并在數(shù)據(jù)上通過實(shí)現(xiàn)INotifyPropertyChanged接口,來屬性變化后通知View刷新數(shù)據(jù)。
- 把想對應(yīng)的屬性Binding到View的控件上。
數(shù)據(jù)結(jié)構(gòu)實(shí)體代碼:
/// <summary>
/// 設(shè)備基類
/// </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é)點(diǎn)狀態(tài)
/// 注意:這里的父節(jié)點(diǎn)不是屬性而是字段
/// 采用字段的原因是因?yàn)椴幌胱尭腹?jié)點(diǎn)觸發(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é)點(diǎn)狀態(tài)
/// 注意:這里的父節(jié)點(diǎn)不是屬性而是字段
/// 采用字段的原因是因?yàn)椴幌胱尭腹?jié)點(diǎn)觸發(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é)點(diǎn)為選中
if (selectedCount ==
CurrentNode.ParentNode.ChildNodes.Count)
{
parentNodeState = true;
}
//如果全部不被選中,則修改父節(jié)點(diǎn)為不被選中
else if (noSelectedCount ==
CurrentNode.ParentNode.ChildNodes.Count)
{
parentNodeState = false;
}
//否則標(biāo)記父節(jié)點(diǎn)(例如用實(shí)體矩形填滿)
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具體實(shí)現(xiàn)代碼:
<CheckBox IsChecked="{Binding IsSelected,Mode=TwoWay}"
Margin="2" VerticalAlignment="Center"/>
這里只需要把實(shí)體的IsSelected屬性Bingding到View上,Mode是雙向的就可以了,具體的邏輯有實(shí)體內(nèi)部做處理,這樣更能體現(xiàn)出 View中代碼的干凈,而且更能讓View和ViewModel耦合性降到最低。實(shí)現(xiàn)三態(tài)樹的時候有一個小技巧,讓代碼避開了無限遞歸的問題,這里采用屬 性如IsSelected,屬性有setter和gettter訪問器,當(dāng)我們向上、下遍歷的時候,改變的是數(shù)據(jù)中的字段isSelected,這樣就不 會觸發(fā)了屬性的setter。這也是數(shù)據(jù)驅(qū)動的一個優(yōu)點(diǎn)之一。
3.總結(jié)
WPF的主要思想是用數(shù)據(jù)驅(qū)動來代替事件驅(qū)動。當(dāng)數(shù)據(jù)發(fā)生變化的時候才做出一些相應(yīng)的處理。這樣的好處就是:
- 使得代碼邏輯更加清晰。
- 可以讓數(shù)據(jù)發(fā)生變化,通過屬性訪問器來控制相應(yīng)的邏輯變化(其實(shí)也是數(shù)據(jù)變化),最后通知View。這樣簡化了邏輯處理而且減少了邏輯混亂的局面。
- 有利于降低View和ViewModel(或后臺具體實(shí)現(xiàn)代碼)之間的耦合度,也就是說有利于把強(qiáng)依賴關(guān)系轉(zhuǎn)為弱依賴甚至沒依賴關(guān)系。
標(biāo)簽:
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:博客園