課程內容
Ø圖片的讀寫
Ø序列化
Ø雙向資料繫結
Baby Milestones將嬰兒從出生到2歲之間的發展關鍵裡程碑通知給父母。該應用程式使得父母能夠跟蹤發展裡程碑,並確保他們的寶寶正常成長。它會把嬰兒每個階段可以完成的技能按照月份的列表顯示出來,使得父母能夠記錄寶寶擷取該技能的日期。該應用程式的首頁面顯示寶寶當前每個月的成長資料榜。
該應用的額外特色正是將其安排在本章講述的主要原因。它展示了如何在隔離儲存空間中儲存、擷取並顯示圖片。該應用中每個月的列表(從1到24)支援自訂圖片作為頁面背景,其主要思想是父母能夠在合適的時間給寶寶拍攝照片,為每個列表提供一些懷舊的內容。
The Main Page
首頁面23.1所示,它包含了一個list box控制項,通過它可以連結到24個月份的列表。List中的每個label伴隨一個progress bar,它展示當前每個月的發展程度。完成的月份以照片的前景色彩顯示,而未完成的月份則以照片的強調色顯示。
圖23.1 進度條將簡單的list box變成了一個有用的面板視圖
注意:
➔ 該應用程式利用了以下兩個Settings.cs中定義的設定,Data.Ages展示了24個包含一系列技能的階段列表。
➔ 在該頁面的XAML代碼中,資料範本中的進度條直接與每個Age執行個體的PercentComplete屬性進行綁定。但是,為了使每個text block控制項有合適的前景色彩,這裡使用了自訂值轉換器。本應用程式使用了3個值轉換器,在下一節中詳述。
➔ 在背後代碼中,MainPage_Loaded方法確保選擇視圖中顯示最近的階段,特別是一旦寶寶超過了9各月,讓使用者每次都通過捲軸來查看會顯讓他們覺得很懊惱。這通過BeginInvoke調用來完成,因為在設定資料內容以後立刻操作list box的捲軸,這樣可能不行。我們需要在這種方法操作list box之前完成資料繫結。
➔ 在Windows Phone應用程式中,list box最常用的SelectionChanged事件(只有在選定的內容改變以後才會觸發,而非點擊操作就可以)在這裡是不希望出現的。因此,這裡使用ListBox_SelectionChanged方法清除剛剛選擇的內容,在同一個記錄上進行連續點擊也是一樣。
Age and Skill
➔ Age 和 Skill這兩個類都實現了INotifyPropertyChanged介面,在屬性改變時,會觸發PropertyChanged事件,如同資料繫結中的資料來源。這就使得記錄可以顯示在首頁面上,並且使得details頁面(下一節講述)保持更新,而不用手動進行操作。
➔ 由於Age類中的PercentComplete屬性是以Skill列表的每個Date欄位為基礎的(null意味著未完成,而存在任何日期就表明已經完成),所以,在合適的時間為PercentComplete來觸發PropertyChanged事件就顯得比較合適。Age類本來可以為每個Skill執行個體訂閱PropertyChanged事件,並且在日期發生改變時,為PercentComplete來觸發事件。相反,Age類只需要使用者在相關的日期改變時,調用RefreshPercentComplete就可以了。
➔ Skill類具有一個顯式預設建構函式,因為它需要為隔離儲存空間進行序列化。一般情況下,C#編譯器會產生隱式預設建構函式。但是,在定義非預設的建構函式時,我們必須顯式地定義一個預設建構函式(如果需要的話)。
Serialization and Isolated Storage Application Settings
放置於IsolatedStorageSettings.ApplicationSettings中的每個對象(或者是分配給本書中使用的Settings類的執行個體)-包括它所有成員的transitive closure-必須要序列化。正如前一章所述,該字典下的內容在ApplicationSettings檔案中被序列化為XML。如果存在不可序列化的資料,那麼字典中的所有資料將都無法儲存。這種錯誤可能發生於無形,除非我們在調式器中捕獲未處理的異常。
大多數情況下,滿足這個需求並不需要額外的工作。這本書中至今沒有一個應用需要做特殊的處理來確保它們的設定是可序列化的,包括所有的基礎資料型別 (Elementary Data Type)(string, numeric values, DateTime等等),包括使用了這些基礎資料型別 (Elementary Data Type)的List,以及使用這些資料類型的類,它們都是可序列化的。
但有的時候,我們需要用自己的方式確儲存儲的資料是用可序列化的資料類型來描述的。我們可以簡單地加入顯式預設建構函式來實現,否則的話,我們可能需要花費更多的時間來改變資料類型或者對其進行自訂屬性(比如DataMember和IgnoreDataMember,它們使得我們可以自訂類的序列化),我們可以使用IgnoreDataMember屬性對其進行標記,從而對其進行排除。
避免儲存相同對象的多個引用!
對於隔離儲存空間應用設定字典中的相同對象,雖然我們可以儲存它的多個引用,但是在應用程式下一次運行時,這些引用不會指向同一個執行個體。那是因為當每個應用被序列化的時候,他的資料被儲存為獨立的備份。在還原序列化時,每個資料的備份變成了不同對象的執行個體。
這個正是Baby Milestones使用CurrentAgeIndex設定、而不使用儲存Age執行個體引用設定的原因。在序列化與還原序列化後,滾動list box的邏輯再也不起任何作用了,因為Age執行個體已經不在list box之中。
我們可以通過對System.Runtime中的一些自訂屬性進行標記的方法,在序列化和還原序列化中加入使用者自訂邏輯。Serialization命名空間:OnSerializing, OnSerialized, OnDeserializing, and OnDeserialized。為了使得我們標記的方法在合適的時間調用,它們必須是public類型的(或者包含一個合適的InternalsVisibleTo屬性),並且具有一個StreamingContext參數。
The Details Page
Details頁面23.2所示,它在使用者點擊首頁面上的一個age時出現。該頁面顯示與age相關的技能列表,點擊它能夠記錄擷取技能的日期。點擊以後,會彈出一個初始化為當天的date picker,23.3所示。
圖23.2 顯示第一個月列表的Details頁面
圖23.3 點擊第一條記錄以後的Details頁面
注意
➔ 每條記錄中date picker的可見度和text block是基於Skill執行個體中的Date屬性值。這是通過兩個值轉換器來完成的。
➔ Date picker的值使用雙向資料繫結,這對於那些使用者控制屬性值的方式非常有用。Skill類執行個體中Date屬性的改變不僅自動在date picker中顯示出來,而且使用者通過UI對date picker作的改變也會自動回饋給Date屬性。
➔該列表使用了自訂的IsolatedStorageHelper類來進行圖片檔案的載入、儲存和刪除。23.4所示,圖片由photo chooser來選擇,它將選擇的圖片以資料流的方式返回。
圖23.4 Photo chooser支援從媒體庫中選擇圖片或者通過網路攝影機來拍攝新的圖片
IsolatedStorageHelper的注意點
➔ DeleteFile方法與前一章中刪除檔案的代碼相同,SaveFile方法並不指定圖片,而是將輸入的二進位流儲存為一個新的檔案流。與圖片相關的部分在LoadFile中,它調用PictureDecoder.DecodeJpeg(在Microsoft.Phone命名空間中)將流轉換為ImageSource,從而可以將其設定為Image或ImageBrush的源。
➔ DecodeJpeg方法的速度相當慢,並且它必須在UI線程中調用,所以,這個類會緩衝所有它建立的ImageSource,使得下次其檔案名稱被傳遞給LoadFile時,能夠快速返回(相同的ImageSource執行個體可以被多個UI元素共用,所以複用它並不會帶來危險)。
除了PictureDecoder.DecodeJpeg,可以考慮使用WriteableBitmap.LoadJpeg。後者可以在後台線程中調用,避免在解碼一個大的圖片時所帶來的響應遲滯。WriteableBitmap會在第42章的“Jigsaw Puzzle”中進行介紹。
LoadFile可以使用一個替代的方法來使用隔離儲存空間中的圖片構造一個ImageSource。它可以用預設的構造方法來構造BitmapImage,然後調用SetSource方法接收一個IsolatedStorageFileStream執行個體的流。
如果我們的應用程式允許從網路攝影機中儲存圖片,那麼就讓使用者把它儲存到媒體庫中,這是一個不錯的主意。這樣一來,即使應用程式卸載了,拍攝的圖片仍舊保留在裝置中。而且,一旦圖片進入媒體庫,使用者就可以將其同步到電腦或者使用多種方法來共用(比如上傳到Facebook或者SkyDrive)。我們可以簡單得調用MediaLibrary.SavePicture來實現。
重載的DecodeJpeg具有maxPixelWidth與maxPixelHeight參數,他們可以根據效能需求來進行圖片的剪裁。但是,當JPEG類型圖片的寬度大於高度時,DecodeJpeg會將這兩個參數混淆。它會使用maxPixelWidth限制高度,使用maxPixelHeight限制寬度。