前一篇中瞭解了 ListView / ListBox 控制項的基本用法,不過如果要實際開發 app 其實是不太夠用的,所以這篇再做深入一點 (但也不會太困難) 的介紹。

處理不單純的資料結構

前一篇裡我們舉的例子都是顯示簡單的字串資料,但很多時候都要處理更複雜的資料結構,比方說像 twitter 類型的 app 那樣顯示 tweet 列表,面對這樣的狀況,我們通常會寫一個物件類別來表示資料結構,像是這樣:(我過份簡化了)

我們用這個資料結構表示一則訊息的資料結構,包括 id、顯示名稱、訊息、大頭照 URL 以及發文時間。接著就是要設計一下 ItemTemplate 的呈現方式,至少要呈現大頭照、名字、訊息還有發文時間,所以我們修改了 XAML 檔案成像是下面這樣:

跟前一篇 ListView / ListBox 裡的 ItemTemplate 有很大的不同,這裡放入了一個 <Image /> 以及三個 <TextBlock /> 控制項用來顯示訊息資料,而最大的不同是,這些控制項的內容綁定資料的方式也有稍稍不同,這裡的 Binding 語法都加了參數,先簡單地想,它就是對應到資料結構的成員,所以 {Binding Message} 就是把資料中的 Message 成員內容作綁定,所以若填入的資料如下:

那顯示的畫面會是:

Windows Store App


顯示訊息資料後的 ListView (Windows Store App)

Windows Phone App


顯示訊息資料後的 ListBox (Windows Phone App)

只要簡單地使用 {Binding} 的語法就能輕鬆處理複雜的資料結構,要注意的是,被綁定的成員若不是字串型別,就會自動對該成員呼叫 .ToString() 取得字串內容,以這裡的示範為例,PostTime 雖然是一個 DateTime 的資料型別,但依然能做綁定,而且透過 ToString() 方法取得顯示的字串內容。

選取 ListView / ListBox 的項目

通常使用 ListView / ListBox 時,不僅會顯示資料,有時可能會搭配一些操作,最典型的例子就是選取其中一筆資料,可能是進入下一層畫面看更詳細的內容,或者是其它的操作。要完成這個動作很簡單,只要在 ListView / ListBox 的 XAML 中加入 SelectionChanged 的事件處理即可。

這樣只是完成在 XAML 中設定了 SelectionChanged 事件的處理函式名稱,所以在 XAML 的 code-behind 中就必須宣告一下這個函式:

在這個事件處理函式中,我們可以從 sender 取得 ListView 的實體,然後透過它來取得選取到的資料,由於已經做了 ItemsSource 的綁定,所以選取的資料型別就會是容器內資料的型別 (以這裡為例,選取到的資料就是 MessageModel 的資料型別),所以只要透過 ListView 實體的 SelectedIndex 或是 SelectedItem 就可以取得被選取的資料實體。

上面這段程式碼還多做了一件事,那就是把 SelectedIndex 設成 -1 用來移開選取的焦點,這個作法是為了:讓已經被選取的資料還有被選取的機會。為什麼要這樣做呢?假設今天要做的使用情境是:使用者點選了其中一筆訊息資料,此時換到另一頁顯示更詳細的內容,閱讀完畢可以按下 back 的按鈕回到列表頁,若沒有把選取的焦點移開,此時 ListView / ListBox 的選取焦點還在剛才選取的項目上,這樣點選同一個項目就不會觸發 SelectionChanged 的事件 (顧名思義),所以才多做了一個把 SelectedIndex 設成 -1 的動作,而若是做了這個步驟,就要注意它依然會觸發一次 SelectionChanged 事件,而 SelectedIndex 是 -1 時並無意義,於是用一個判斷式避開它。

主動通知改變

現在我們已經會使用 ListView / ListBox 顯示各種資料列表,也知道如何簡單地處理選取列表中單一項目的事件,看起來好像一切都很美好,但其實還有一個小問題:如果這個資料列表在綁定後,內容被修改後,其實不會要求 ListView / ListBox 去重新顯示被修改的資料,我們可以先做個小實驗,先在畫面中加入一個按鈕,按下去後就修改資料列表的內容:

照著上面的程式碼修改,畫面上多了一個按鈕,在程式啟動後,你可以試著按按看按鈕,原本預期第一筆資料的名稱欄位應該會改變,但其實並不會發生。這都是因為:

  1. 資料雖然改變了,但 List 資料結構不知道內容有改。
  2. 既然 List 不覺得有什麼改變,ListView 當然也不知道要做什麼事。

所以解決這個問題最簡單的方式,就是讓資料結構會根據內容改變而發出通知,並且選用會有所反應的容器。根據這個原則,首先修改 MessageModel.cs 這個檔案:

為了讓 MessageModel 的資料被修改後會發出通知,我們讓它實作 System.ComponentModel.INotifyPropertyChanged 介面,讓它發出通知的方式符合 .NET 的設計,並且在每一個成員 (Property) 被修改 (set) 時,產生一個 PropertyChanged 事件,這樣就完成了「發出通知」的任務。

但光是這樣還不夠,因為 List 容器並不會處理 PropertyChanged 事件,所以我們必須選用另一個容器 -- System.Collections.ObjectModel.ObservableCollection,所以在 ListView / ListBox 的資料綁定程式碼便改寫成:

不要忘了將原本用 List<MessageModel> 容器的部份都改為使用 ObservableCollection<MessageModel>,這時你就會發現按鈕的行為符合預期了。

待續

透過這篇文章的介紹,瞭解如何處理複雜的資料結構、選取資料的方式以及讓 ListView / ListBox 能夠感受到資料內容的改變,這樣的寫法雖然已經可以活用 ListView / ListBox 控制項了,不過都要在程式執行後才能看到資料顯示的樣子,沒有好好利用 Visual Studio 提供的 XAML designer 編輯畫面,甚至是開發工具內附的 Blend for Visual Studio 來設計像是 ItemTemplate 的樣式。下一篇文章我們將會介紹,這樣的架構設計如何讓程式開發與畫面設計可以整合地更好更方便。