1. 程式人生 > >使用ViewModel模式來簡化WPF的TreeView(用正確的方法使用TreeView)(轉)

使用ViewModel模式來簡化WPF的TreeView(用正確的方法使用TreeView)(轉)

英文原文地址:Simplifying the WPF TreeView by Using the ViewModel Pattern

作者:Josh Smith

譯者按:WPF中對TreeView的操作同WinForm中有很大的不同。這篇文章講述瞭如何用ViewModel模式來簡化WPF的TreeView,個人感覺非常有價值,尤其是在WPF中思維模式跟以往有很大不同的情況下。希望能對大家有所幫助並且觸類旁通。第一次做翻譯,有錯誤的地方敬請諒解。

介紹

 這篇文章探討了如何通過使用ViewModel模式來更容易的使用WPF中的TreeView控制元件。在此過程中,我們會看到為何人們經常在使用WPF的TreeView時遇到困難,什麼是ViewModel,以及兩個例項程式,這兩個程式展現瞭如何結合TreeView和ViewModel。其中一個例項展示瞭如何建立一個具有搜尋功能的TreeView,另一個則說明了如何實現延遲載入(lazy-loading)。

TreeView的背景

 WPF中的TreeView控制元件揹負了一個名不副實的壞名聲。很多人嘗試著使用它,卻發現非常難用。其實問題在於人們經常試著按照Windows Forms的TreeView控制元件的使用方式來使用它。為了發揮(原文是leverage,意為槓桿作用,不好翻譯,呵呵)WPF TreeView的豐富特性,你不能再使用跟Windows Forms一樣的程式設計技術了。這也是WPF要求你為了更好恰當地使用它(指WPF)而轉換思維方式的另一個例子。畢竟我們已經走到這一步了。(原文是We aren't in Kansas anymore, Toto。來自電影《綠野仙蹤》:Toto,I've got a feeling we're not in Kansas anymore)     



 在Windows Forms中,使用TreeView非常容易,因為它非常簡單。這種簡單建立在Windows Forms的TreeView完全不靈活的事實上,比如不提供UI的虛擬化(UI virtualization),不提供外觀的個性化,同時由於它不支援資料繫結,你必須將資料存到它的節點中。WinForm的TreeView根本不夠好(原文是The WinForms TreeView is "good enough for government work","good enough for government work"是說不夠好)。     



 對比之下,WPF的TreeView非常的靈活,天生支援UI虛擬化(比如,TreeViewItems是按需建立created on-demand),完全允許個性化外觀,同時完全支援資料繫結。這些優秀的特性是需要代價的。他們讓WPF的TreeView比WinForm的TreeView更加複雜。一旦你學會了如何正確地使用WPF的TreeView,這些複雜性將不在話下,同時發揮WPF TreeView的全部能力將變得非常容易。



 如果你對如何個性化WPF的TreeView感興趣,可以檢視這篇文章和這篇文章。

ViewModel的背景

 早在2005年,John Gossman寫了一篇關於Model-View-ViewModel模式的博文,這種模式被他所在的微軟的專案組用來建立Expression Blend(即'Sparkle')。它跟Martin Fowler的Presentation Model非常相似,唯一不同的是,它填平了presentation model和使用了WPF的豐富的資料繫結的view之間的溝壑。在Dan Crevier發表了神作DataModel-View-ViewModel series博文系列之後,(D)MVVM模式開始變得流行起來。





 (Data)Model-View-ViewModel模式跟經典的Model-View-Presenter模式很相似,除了你需要一個為View量身定製的model,這個model就是ViewModel。ViewModel包含所有由UI特定的介面和屬性,它們是輕鬆構建UI的必要元素。View繫結到ViewModel,然後執行一些命令在向它請求一個動作。而反過來,ViewModel跟Model通訊,告訴它更新來響應UI。



 這使得為應用構建UI非常的容易。往一個應用程式上貼一個介面越容易,外觀設計師就越容易使用Blend來建立一個漂亮的介面。同時,當UI和功能越來越鬆耦合的時候,功能的可測試性就越來越強。為什麼不想要一個漂亮的介面同時又有一套乾淨有效的單元測試呢?

究竟是什麼讓TreeView這麼難用?

 如果你的使用方法正確的話,其實TreeView是很好用的。正確的使用方法,反過來說,是根本不要直接的使用它!一般地,你需要直接地對一個TreeView設定屬性,不時地呼叫方法。這是無法逃避的,同時這麼做也沒什麼錯。不過,如果你發現你正深陷於對控制元件的呼叫(原文是the guts of the control,不好翻譯),那也許你並沒有採取最佳的方式。如果你的TreeView是資料繫結的,然後你發現你正試圖通過程式來折騰這些項,那麼你就是沒有使用正確的方法。如果你發現監聽ItemContainerGenerator的StatusChanged事件來訪問TreeViewItem的子節點,那你簡直就是脫軌了!相信我,根本沒必要這麼醜陋和困難。有更好的方法!

按照WinForm TreeView的方式來使用WPF TreeView的根本問題在於,正如我前面提到的,它們是非常不同的控制元件。WPF的TreeView允許你通過資料繫結來生成它的Items。這意味著它會為你建立TreeViewItems。由於TreeViewItems是被控制元件建立的,而不是你,因此它不能保證當你需要時,某個資料項對應的TreeViewItem還存在。你必須問問TreeView的ItemContainerGenerator是否已經為你建立了TreeViewItem。如果沒有,你必須監聽它的StatusChanged事件,當它建立了自己的子元素之後通知你。

 有趣的不止這些!如果你想獲得樹中一個非常複雜,非常深的TreeViewItem,你必須問問他的父TreeViewItem,而不是TreeView控制元件,是否它的ItemContainerGenerator已經建立了該項。但是,你要怎樣才能拿到它的父節點的引用當這個父節點還沒有被建立呢?當父節點的父節點還沒有被建立的時候又是怎樣的呢?子子孫孫,無窮潰也。這是相當痛苦的。



 正如你所見的,WPF的TreeView是一個複雜的野獸。如果你試著用錯誤的方式來使用它,將不是那麼容易的。幸運的是,如果你用正確的方式使用它,那就是小事一樁。那麼,讓我們來看看怎麼樣通過正確的方式來使用它……

ViewModel是解救的辦法

 WPF是偉大的,因為它基本上要求你分離應用程式的資料和UI。前面擊節中列出的問題都是應為違背了這個原則並且把UI當作資料儲存的地方。 一旦你不再把TreeView當成一個儲存資料的地方,而是看做一個展現資料的地方,那麼一切都將水到渠成。這就是ViewModel這個想法的由來。



 比起寫程式碼去折騰TreeView裡面的項,更好的方法是寫一個被TreeView繫結的ViewModel,然後寫程式碼來操作你的ViewModel。這不僅僅能讓你無視TreeView的複雜性,還能讓你寫出能夠很容易進行單元測試的程式碼。要為那些緊密依賴於TreeView執行時行為的類寫有意義的單元測試程式碼幾乎是不可能的,但是要為那些對這些無關行為一無所知的類寫單元測試程式碼卻很容易。



 現在,我們來看看如何實現這些概念。

示例解決方案

 這篇文章附帶了兩個示例程式,能從頁面頂部下載。該Solution包含了兩個工程。BusinessLib類庫工程包含了簡單的描述地域的類,這些類被當作純粹的資料傳輸物件。同時它還包含了一個Database類,這個類初始化資料,並且返回這些資料傳輸物件(譯者:說白了就是模擬的資料來源)。另外的一個工程,TreeViewWithViewModelDemo,包含一些示例程式。這些程式使用BusinessLib程式集中給出的資料物件,並且在放到TreeView中展示之前,把它們包裝到一個ViewModel中去。



 下面是這個Solution的一個工程樹截圖:

Demo1 - 帶文字搜尋的Family Tree

 第一個示例程式中我們構建了一個展示Family Tree的TreeView。它在介面的底部提供了搜尋的功能。截圖如下:







 當用戶輸入一些關鍵字,敲回車,或者是點選了“Find”按鈕之後,第一個匹配的專案將會被展示出來。繼續搜尋將在所有匹配項之間迴圈。所有的這些邏輯都在ViewModel中。在深入ViewModel的工作方式之前,我們先看看相關程式碼。下面是TextSearchDemoControl的後臺程式碼。

public partial class TextSearchDemoControl : UserControl
{
readonly FamilyTreeViewModel _familyTree;

public TextSearchDemoControl()
{
    InitializeComponent();

    // Get raw family tree data from a database.
    Person rootPerson = Database.GetFamilyTree();

    // Create UI-friendly wrappers around the 
    // raw data objects (i.e. the view-model).
    _familyTree = new FamilyTreeViewModel(rootPerson);

    // Let the UI bind to the view-model.
    base.DataContext = _familyTree;
}

void searchTextBox_KeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Enter)
        _familyTree.SearchCommand.Execute(null);
}

}

 建構函式展示了我們如何把原始資料轉換到ViewModel中,然後把它設為UserControl的DataContext。在BusinessLib程式集中定義的Person類,非常簡單:

///
/// A simple data transfer object (DTO) that contains raw data about a person.
///
public class Person
{
readonly List _children = new List();
public IList Children
{
get { return _children; }
}

public string Name { get; set; }

}

PersonViewModel

 由於Person類是應用的資料訪問層返回的東西,它絕對不適合直接被UI使用。每個Person物件最終被包裝到一個PersonViewModel類的例項中,這樣就讓它擁有了額外的能力,比如被展開和選中。由此看出,FamilyTreeViewModel類,完成了把Person物件包裝到PersonViewModel物件中的過程,下面是它的建構函式:

public FamilyTreeViewModel(Person rootPerson)
{
_rootPerson = new PersonViewModel(rootPerson);

_firstGeneration = new ReadOnlyCollection<PersonViewModel>(
    new PersonViewModel[] 
    { 
        _rootPerson 
    });

_searchCommand = new SearchFamilyTreeCommand(this);

}

 私有的PersonViewModel的建構函式通過遞迴的方式遍歷Family Tree,把每一個Person包裝到PersonViewModel中。下面是程式碼:

public PersonViewModel(Person person)
: this(person, null)
{
}

private PersonViewModel(Person person, PersonViewModel parent)
{
_person = person;
_parent = parent;

_children = new ReadOnlyCollection<PersonViewModel>(
        (from child in _person.Children
         select new PersonViewModel(child, this))
         .ToList<PersonViewModel>());

}

 PersonViewModel有兩種成員:一種關聯到展現,另外一種關聯到Person的狀態。展現相關的屬性將會被TreeViewItem所繫結,而狀態相關的屬性繫結到TreeViewItem的內容。其中一個展現相關的屬性IsSelected,如下:

///
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
///
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged(“IsSelected”);
}
}
}

 這個屬性跟一個"person"沒有任何關係,而是一個簡單的用於同步View和ViewModel的狀態標識。注意屬性的setter呼叫了OnPropertyChanged方法,該方法會激發PropertyChanged事件。該事件是INotifyPropertyChanged介面的唯一成員。INotifyPropertyChanged是一個UI相關的介面,這就是為什麼PersonViewModel類實現該介面,而Person類不實現。



 一個展現相關屬性的更有趣的例子是PersonViewModel的IsExpaned屬性。這個屬性解決了怎麼保證在必要的時候,跟某個資料物件對應的TreeViewItem的展開問題。記住,如果直接用TreeView來程式設計解決這些問題,可是痛苦不堪的。

Code

 正如我前面所提到的,PersonViewModel同時還有跟Person狀態相關的屬性,比如:

Code

介面部分

 把一個TreeView繫結到PersonViewModel的程式碼是非常簡潔的。注意TreeViewItem和PersonViewModel之間的聯絡依靠的是控制元件的ItemContainerStyle:

相關推薦

使用ViewModel模式簡化WPF的TreeView(正確方法使用TreeView)()

英文原文地址:Simplifying the WPF TreeView by Using the ViewModel Pattern 作者:Josh Smith 譯者按:WPF中對TreeView的操作同WinForm中有很大的不同。這篇文章講述瞭如

工廠方法模式下不同訂單

現在假設我們有兩種型別的訂單,汽車服務訂單和商城配件訂單 我們的抽象訂單介面為 public interface Order

Python全棧day21(調模塊路徑BASEDIR的正確方法

變化 一個 pre dirname 發生 文件 導入 復制 sys 正常寫python程序會有一個可執行的bin.py文件,假如這個文件需要導入my_module裏面定義的模塊,應該怎麽設置sys.path 文件夾目錄結構如下,因為bin不在與my_module同級目錄下,

不使用SpringBoot如何將原生Feign集成到Spring中簡化http調

getname anr fig ssp sin int all process 代碼 在微服務架構中,如果使用得是SpringCloud,那麽只需要集成SpringFeign就可以了,SpringFeign可以很友好的幫我們進行服務請求,對象解析等工作。 然而SpingC

學不會Linux?看看正確方法是什麼!

2018年裡,Linux運維的職位數量和平均薪資水平仍然持續了去年的強勁增幅,比很多開發崗位漲的都快。從研究機構的資料來看,Linux職位數量和工資水平漲幅均在IT行業的前五之列,比去年的表現還要好一點。 在這樣的前提下,很多人加入Linux運維的學習行列並不奇怪。不過由於初學者不能得法,認為Linux學起

學不會Linux?看看正確方法是什麽!

擴大 強勁 cat 標準 電子書 中文名 solar 文件 方式 2018年裏,Linux運維的職位數量和平均薪資水平仍然持續了去年的強勁增幅,比很多開發崗位漲的都快。從研究機構的數據來看,Linux職位數量和工資水平漲幅均在IT行業的前五之列,比去年的表現還要好一點。 在

表達異常的分支時,少 if-else 方式(可以使用衛語句、策略模式或者狀態模式實現)

避免後續程式碼維護困難, if-else儘量不要超過3層, 可以嘗試使用衛語句、策略模式或者狀態模式 衛語句: 衛語句就是把複雜的條件表示式拆分成多個條件表示式,比如一個很複雜的表示式,嵌套了好幾層的i

為什麼“IP矩陣”的模式操作萌界專案

時代卡通武力:萌界採用IP矩陣來規劃主要考慮三個方面。 第一、是目前品牌人格化的市場藍海還比較大,很多角色IP座位都是空缺的,沒有多少優質競爭對手,在目前階段很適合佔座佈局; 第二、是時代卡通研究院最大程度發揮自身品牌管理體系的輕度、快速、創意優勢,以輕I

通過裝飾者模式增強request物件的getParameter()方法處理GET和POST的中文亂碼問題

在實際JAVA開發中會經常遇到中文亂碼,由於通過瀏覽器傳遞的引數是通過ISO8859-1進行編碼,在Controller接受之後,不處理直接輸出則顯示是亂碼。 處理POST方式提交的中文 通過設定request物件的字符集來處理。 request.setC

設計模式——模板方法模式(模板方法模式寫作文)

本文首發於cdream的個人部落格,點選獲得更好的閱讀體驗! 歡迎轉載,轉載請註明出處。 本文簡單講述了模板方法模式,例子為如何使作文模板來寫作文。如果想進一步,瞭解模板方法,建議讀完後閱讀一下spring中AbstractApplicationContext類的refresh方法或HttpS

Beyond Compare對比原始碼差異的方法

Beyond Compare是一款經典的檔案對比工具,面對紛繁複雜的程式原始檔和資料夾,Beyond Compare可能有效地提高程式設計師的工作效率,軟體支援原始碼檔案的對比、修改、合併,程式碼目錄對比以及程式碼更新定位,是一款IT程式工程師的必備原始碼管理工具。 1、雙

正確的姿勢說說Android上的記憶體洩漏問題

相信大家對App的記憶體管理都是相當關心的,在專案上線前的幾天時間也會藉助相關工具突擊下嚴重的洩漏問題; 針對記憶體洩漏先提出幾個疑問: 什麼是記憶體洩漏? 記憶體洩漏帶來的危害又是什麼? 哪些程

Android之 MVP模式 實現webview 歷史記錄儲存與顯示

MVP在android上是常見的一種設計模式,在Launhcer,手機瀏覽器裡面經常會看到,觀摩了下其他大神寫的,現在總結下,操刀起來寫個demo 有什麼問題的地方請提出來。 大家一起研究討論。 demo 下載地址;http://download.csdn.net/deta

Python:通過執行100萬次列印比較C和python的效能,以及C和python結合解決效能問題的方法

  python作為動態語言,開發效率相當高,但如我們所知,動態語言的執行效率往往是比較低的,請看下面簡單的測試過程:  一、 C語言實現100萬次列印:   程式碼: #include<stdio.h> #include <time.h> int

PHP裡邊Static關鍵字定義靜態屬性和方法

<?php class person{        static$name="ajax123";//static宣告靜態屬性        static$age=25;//static宣告靜態屬性        static$address="北京";//

設計模式與多執行緒——命令模式設計多執行緒架構

下載原始碼   圖一 圖二 毫無疑問,多執行緒會增加寫程式碼的難度。在有併發處理的情況下,debug變得更困難,程式碼中的臨界區必須得到保護,共享的資源也得管理好。當然,通過增加程式執行的執行緒數帶來的結果是:效率可以大大提高。從一個程式設計師的角度來說,處理執行緒是

設計模式代替臃腫的ifelse層層判斷

--------------------------------<程式碼優化之避免使用過多ifelse>--------------------------------- 在www.infoq.com/cn網站上看了一本書叫《ThoughtWorks文集》,裡邊

JS中可以儲存使用者資料的方法或方式

     sessionStorage,localStorage和cookie都是用來儲存使用者資料的方式,sessionStorage和localStorage是HTML5 Web StorageAPI中提供的,可以方便的在web請求之間儲存資料,有了本地資料,就可以避免

微信發個朋友圈沒有想到也可以設計模式實現——觀察者模式

觀察者模式的概念         觀察者模式(Observer Pattern):定義物件之間的一種一對多依賴關係,使得每當一個物件狀態發生改變時,其相關依賴物件皆得到通知並被自動更新。觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-檢視(M

Angular--工廠方法或值物件定義提供器

前言 上一篇部落格介紹了注入器和提供器,同時也簡單的講解了一下控制反轉,這篇部落格是使用了工廠方法來定義提供器,所以可以過來了解一下。 內容 這個例子是在提供器方法的例子上建立的: 1.首先刪除product2.component.ts中的程式碼: p