天天躁日日躁狠狠躁AV麻豆-天天躁人人躁人人躁狂躁-天天澡夜夜澡人人澡-天天影视香色欲综合网-国产成人女人在线视频观看-国产成人女人视频在线观看

WP7有約(三):課堂重點

  記筆記

      俗話說:好記性不如爛筆頭。當然,這并不是說我們的腦子不好使,也不是叫我們不要用腦子記東西,而是提醒我們解放腦力,讓大腦從事更有價值的思考。因此,這節課我們將會創建一個筆記本,用來記錄課堂重點,但是,我們需要什么樣的筆記本呢?我曾經在《你的燈亮著嗎?》里讀到這樣一句話:如果某人能夠解決這個問題,但是他本人卻不會遇到這一問題時,那么你們首先要做的就是讓他也感受到這個問題。最近公司來了一批校招生,我找了個機會混進去聽了一節入職前的技術培訓,我想知道在課堂上把手機掏出來記筆記是一種什么樣的感覺。

      在課堂上,每當我想記點什么時,就會不自覺地拿起紙筆而不是手機,而且,用手機記筆記遠沒用紙筆來得隨意自如。隨后,我找了一些大學生和中學生,分別了解一下他們記筆記的情況,結果發現,他們記筆記的方式真是多種多樣,有的直接記在書上,有的記在專門的筆記本上,有的記在練習冊上,有的記在卷子上,有的甚至用手機把老師的板書直接拍下來……不難看出,他們的做法是怎么方便就怎么記,就目前而言,企圖用一個手機應用來取代他們現有的做法顯然是不現實的,也沒必要,用戶有權選擇他們認為適合的做法,而我們的職責只是提供必要的幫助和支持。

      那么,我們可以提供什么樣的幫助和支持呢?想想看,現有的自由零散的做法會導致什么問題呢?最直接的影響是很難快速找到想要的內容,因為它們可能遍布各處,這種時候要是有個索引或者目錄什么的就好了……Bingo!我們可以創建一個應用,幫助用戶建立這個索引,雖然用戶也可以另外找本小冊子建立索引,但我們可以通過一個標簽系統幫助用戶快速找到相關的內容。這樣,用戶既可以保留現有的自由的記筆記習慣,又可以獲得新的有序的管理效果。那么,用戶應該在何時以及如何建立這個索引呢?

      當然是越早越好!比如說,用戶可以在每晚做完作業之后稍稍整理一下筆記,然后為它們創建一些條目并貼上標簽。用戶不必為所有筆記創建條目,可以挑選重要的來創建,這個過程本身就可以加深對知識的理解和鞏固對知識的記憶。至于條目的內容,用戶可以引用課本或者老師板書的原話,也可以用自己的話來概括復述,還可以直接引用課本或者練習冊的頁碼和位置(段落、題號或者標記)等等,這個過程可以幫助用戶熟悉如何根據條目的內容找到對應的筆記。

      現在,用Visual Studio打開項目,在Models文件夾里創建一個Note類,并讓它繼承NotificationObject類:

代碼 1

  根據前面的討論,Note類應該包含以下三個屬性:

屬性名字

屬性類型

屬性描述

Id

Guid

唯一標識

Course

string

課程名稱

Content

string

筆記內容

Tags

string

筆記標簽

      那么,我們如何觸發這些動畫?前面說過,當用戶單擊Application Bar上的按鈕時,ListBox將會顯示,這個比較簡單,只需在按鈕的事件處理程序里調用ShowTagsStoryboard的Begin方法就可以了:

代碼 4

  而當用戶選好標簽之后,ListBox將會隱藏,這個可以通過Expression Blend提供的ControlStoryboardAction來實現。打開Assets面板,選擇Behaviors,然后把ControlStoryboardAction拖到Objects and Timeline面板的ListBox上:

圖 12

  此時,Objects and Timeline面板將會變成這樣:

圖 13

  確保ControlStoryboardAction處于選中狀態,在Properties面板上把EventName和Storyboard兩個屬性的值分別改為MouseLeftButtonUp和HideTagsStoryboard:

圖 14

  這樣,當用戶在ListBox里選好標簽并松開手時就會觸發HideTagsStoryboard??吹竭@里,你可能會問,為什么前面不直接在Application Bar的按鈕上使用ControlStoryboardAction?這是因為Application Bar并非Silverlight的一部分,你不可以把它和我們平時接觸到的Silverlight對象等同起來。事實上,如果你試圖把ControlStoryboardAction拖到ApplicationBarIconButton上,Expression Blend會提示你"Not a valid drop target":

圖 15

  接下來,我們將會為兩個ListBox定制數據模板。

      首先,通過Data面板導入以下兩個XML文件:

代碼 5

代碼 6

  此時,Data面板將會變成這樣:

圖 16

  把Data面板上的NoteCollection拖到顯示筆記的ListBox上,然后進入列表項模板的編輯狀態,把StackPanel的Margin屬性、TextBlock的FontSize屬性和TextBlock的TextWrapping屬性分別設為PhoNETouchTargetOverhang、PhoneFontSizeNormal和Wrap:

圖 17

  好了之后退出列表項模板的編輯狀態。接著,把TagCollection拖到顯示標簽的ListBox上,然后進入列表項模板的編輯狀態,把StackPanel的Margin屬性、TextBlock的FontSize屬性和TextBlock的TextWrapping屬性分別設為PhoNETouchTargetOverhang、PhoneFontSizeLarge和Wrap:

圖 18

  接下來干嘛?你懂的!

      打開MainPage.xaml,添加一個菜單項,并讓它導航至NoteBookPage頁:

圖 19

  好了,按F5吧:

圖 20

  單擊筆記本

圖 21

  單擊Application Bar上的按鈕:

圖 22

  啊!忘記把ListBox的Background設成不透明了!

      退出應用程序。把ListBox的Background改為PhoneBackgroundBrush。而動畫方面,0.5秒感覺有點長,我們可以把它改為0.25秒:

代碼 7

  此外,還有一個地方值得改善的,我希望ListBox出來的時候能夠有一種逐漸慢下來的感覺,而離開的時候則相反,逐漸快起來。這可以通過緩動函數(easing function)來實現。單擊Objects and Timeline面板上的Open a Storyboard按鈕,然后選擇ShowTagsStoryboard:

圖 23

  展開ListBox節點,確保下面的RenderTransform處于選中狀態:

圖 24

  然后在Properties面板上把EasingFunction設為Cubic Out:

圖 25

  好了之后仿照上述步驟把HideTagsStoryboard的EasingFunction設為Cubic In?,F在,你可以按F5看看修改后的效果了。

      接下來,是時候考慮一下筆記本的相關操作了。我們知道,課程表通常都是整個創建的,而作業通常也一次過把一門課當天要做的都記下來,對于這種集中式批量操作來說,提供保存所有更改和撤銷所有更改是有必要的,但筆記本就不同了,里面的內容很可能分散在不同的時間點記錄,沒有太明顯的集中式批量操作,如果我們遵循課程表和作業本的套路,要么用戶不得不在每次記筆記時都額外執行一次保存操作,要么用戶不得不承擔最后可能忘記統一保存而丟失數據的風險。因此,我打算把五項操作簡化為新建、編輯和刪除三項,并在用戶執行每項操作之后自動保存數據,其中,新建將會以ApplicationBarIconButton的方式放在Application Bar上:

圖 26

  而編輯和刪除則放在上下文菜單里:

代碼 8

  根據之前的經驗,我們需要一個新的頁面來處理新建和編輯操作:

圖 27

  那么,我們應該如何設計這個頁面呢?

      想想看,Note類的哪些屬性和用戶無關?Id。哪些屬性無需勞煩用戶處理?CourseName。那么剩下的Content和Tags兩個屬性就應該出現在這里了。毫無疑問,TextBox完全能夠勝任顯示和編輯這兩個屬性的工作:

圖 28

  值得注意的是,這里不再通過普通的Button控件來提供"確定"和"取消"兩項功能,而是通過Application Bar上的按鈕來提供,為什么呢?當用戶輸入完畢之后,軟鍵盤可能處于開啟狀態,它會遮蓋普通的Button控件,這意味著用戶就不得不先單擊一下頁面上的空白處關閉軟鍵盤,再單擊Button控件關閉頁面,而Application Bar不會被軟鍵盤遮蓋,這意味著用戶可以在輸入完畢之后直接單擊上面的按鈕關閉頁面。你知道嗎,這個小小的簡化可以極大地提升用戶體驗,之前測試課程表和作業本的時候,這個不必要的步驟曾多次讓我誤觸TimePicker/DatePicker控件的有效范圍,導致新的頁面被打開,我對此深感厭惡,你知道,當用戶對軟件的操作感到厭惡時,后果將會很嚴重!

      現在,回到NoteBookPage頁,為Application Bar上的新建按鈕添加一個事件處理程序:

代碼 9

  然后按F5:

圖 29

  從上圖可以看出,軟鍵盤和Application Bar是并列一起的,所以我可以在任何時候單擊Application Bar上的按鈕。此外,當我在TextBox里輸入較長的內容時,TextBox還會自動調整自身的高度,以便完整顯示我輸入的內容。當我單擊第二個TextBox進行輸入時,整個頁面將會稍稍向上平移,這樣做的好處非常明顯——避免軟鍵盤遮蓋TextBox!從這里不難看出,WP7在用戶體驗上的設計確實很體貼!

  連接前端和后端

      接下來,我們要為這些用戶界面創建對應的ViewModel類。我們知道,整個筆記本就是一個Pivot控件,而每門課程的筆記則對應一個Pivot項,這個結構和上節課的作業本類似,于是,我們可以仿效上節課的做法,分別創建NoteBookViewModel和NoteListViewModel兩個類:

代碼 10

代碼 11

  由于每個Pivot項都包含了一個標題和一組筆記,NoteListViewModel類自然需要提供兩個對應的屬性:

代碼 12

  值得注意的是,這里不直接使用ObservableCollection集合,而是和第一節課的課程表一樣,通過CollectionViewSource來提供集合視圖,這樣做的好處是我們只需指定過濾條件,剩下的事情CollectionViewSource會代為處理,而無需我們親自出手:

代碼 13

  而NoteBookViewModel類則和上節課的作業本一樣,直接使用ObservableCollection集合:

代碼 14

  并根據課程表里的數據創建對應的NoteListViewModel對象:

代碼 15

  有了ViewModel類,我們就可以著手處理數據綁定了。

      現在,打開NoteBookPage.xaml文件,切換到XAML模式,在頁面的資源字典里添加兩個數據模板:

代碼 16

  接著,把它們應用到Pivot控件上:

代碼 17

  把LayoutRoot的DataContext屬性去掉,然后在代碼隱藏文件的構造函數里設置DataContext屬性:

代碼 18

  好了,按F5吧。在打開筆記本之前,我們得先新建一些課程,否則Pivot控件不會創建任何Pivot項的:

圖 30

  好了之后就可以打開筆記本了:

圖 31

  當然,現在的筆記本既沒有筆記也不能創建筆記,因為這部分功能還沒實現呢!

      新建和編輯筆記的工作是由NewOrEditNotePage頁負責的,根據上兩節課的經驗,我們需要創建NewOrEditNoteViewModel、NewNoteViewModel和EditNoteViewModel三個類,但我已經厭倦了每次都要手工創建這么多類,而且還有這么多重復的代碼,所以這次我要對這部分進行重構。在ViewModels文件夾里創建一個NewOrEditItemViewModel泛型類,并讓它繼承NotificationObject類:

代碼 19

  仔細觀察NewOrEditCourseViewModel和NewOrEditAssignmentViewModel兩個抽象類,不難發現,它們的有效成分是頁面標題、Model類的實例和提交數據的方法。頁面標題很好處理,一個普通的類型為string的Title屬性:

代碼 20

  Model類的實例有點棘手,因為我們有3個不同的Model類,怎么處理?我們有兩個選擇,一個是把屬性的類型聲明為object,另一個就是這里采用的做法——泛型:

代碼 21

  這也正是我把NewOrEditItemViewModel類聲明為泛型類的緣由。我們知道,每個Model類的數據都會提交到不同的地方,我們顯然不能把這部分代碼固化在NewOrEditItemViewModel類里,所以我決定通過委托把代碼外包出去:

代碼 22

  最后,我們需要在構造函數里初始化這三個成分:

代碼 23

  那么,我們如何使用這個類呢?

      打開NewOrEditNotePage.xaml.cs文件,重寫OnNavigatedTo方法:

代碼 24

  這個是我們根據查詢字符串初始化DataContext屬性的基本套路。當action的值是new時,初始化DataContext屬性的代碼應該是這樣的:

代碼 25

  而當action的值是edit時,初始化DataContext屬性的代碼應該是這樣的:

代碼 26

  這樣,下次我們再有新的Model類就可以直接創建ViewModel類的實例了。

      看到這里,你可能會問,代碼24那個套路每次都是一樣的,應該可以處理一下吧?嗯,可以的。我們有兩種處理方案,第一種方案是創建一個NewOrEditItemViewModelFactoryBase抽象類,并在里面使用模板方法模式(Template Method Pattern)處理那個套路,這樣做的代價是我們需要為每個Model類創建一個對應的工廠類。如果你不喜歡這種做法,你可以選擇第二種方案,創建一個NewOrEditItemPage類,按照代碼24重寫OnNavigatedTo方法,然后應用模板方法模式,這樣做的代價是我們需要讓每個新建/編輯頁面繼承這個類。無論我們選擇哪種方案,有一點是可以肯定的,那就是NewOrEditItemViewModel類的三個成分似乎無法避免,因為這些工作始終要做,而我們從一種方案改成另一種方案只不過是把這些工作從一個地方挪到另一個地方罷了。如果你確實希望減輕這些工作,(理論上)也不是不可能,不過你得做好心理準備,因為你需要創建一個足夠靈活的子系統,并且提供充足的元數據,這些數據包括每個Model類在不同狀態下分別對應的頁面標題、新建Model類的實例需要初始化哪些屬性以及這些屬性的數據來自哪里或者如何計算、克隆現有Model類的實例的方法是哪個、數據提交到哪里以及調用哪個方法、提交數據的是否需要同時保存到獨立存儲區等等等等。當我寫到這里的時候,我已經隱約感覺得出這將是個很復雜的子系統,如果你真的打算實現這個子系統,那么請你先把右手抬起來,捂在左邊胸口,然后問問自己:

  1. 會有人愿意負責提供這些數據嗎?如果有,會是誰呢?
  2. 會有人愿意負責維護這個子系統嗎?如果有,會是誰呢?
  3. 當你的程序用上這個子系統之后,你能得到什么實質的好處?
  4. 這些好處能否抵消提供數據和維護子系統的付出?

  如果你沒有自欺,你的心將會告訴你這個決定是否值得。

      創建好ViewModel類之后,我們就可以著手處理它和頁面之間的關聯了。首先是設置數據綁定,需要設置的控件以及對應的綁定表達式如下表所示:

描述

類型

屬性

綁定表達式

頁面標題

TextBlock

Text

{Binding Title}

筆記內容

TextBox

Text

{Binding Item.Content, Mode=TwoWay}

筆記標簽

TextBox

Text

{Binding Item.Tags, Mode=TwoWay}

      我們知道,NewOrEditNotePage頁的入口點有兩個,而且都在NoteBookPage頁上,一個是Application Bar上的新建按鈕,另一個是上下文菜單里的編輯菜單項。當用戶單擊新建按鈕時,我們需要告訴NewOrEditNotePage頁當前的課程是什么,但是,我們從哪里獲取這個信息?辦法有很多種,其中一種是在NoteBookViewModel類里創建一個SelectedNoteList屬性:

代碼 28

  并把它綁到Pivot控件的SelectedItem屬性:

代碼 29

  然后在新建按鈕的事件處理程序里通過SelectedNoteList的Header屬性獲取課程名稱:

代碼 30

  當用戶單擊編輯菜單項時,我們需要告訴NewOrEditNotePage頁筆記的Id是什么。我們知道,上下文菜單是嵌在列表項里的,這意味著它能從列表項那里繼承DataContext屬性的值,而這個值正是當前選中的筆記,所以我們可以通過菜單項的DataContext屬性獲取筆記的Id:

代碼 31

  類似地,刪除操作也是通過相同的方式獲取當前選中的筆記,然后把它刪除:

代碼 32

      好了,不知不覺又到看效果的時候了!打開筆記本

圖 32

  單擊新建按鈕:

圖 33

  輸入筆記內容和筆記標簽,然后單擊確定返回:

圖 34

  接著,長按筆記打開上下文菜單:

圖 35

  選擇編輯:

圖 36

  嗯?我剛才沒有輸入標簽?還是我現在眼花看錯?為什么標簽沒有保存?現在,重新輸入標簽,單擊頁面上的空白處,單擊確定返回,然后再次編輯筆記:

圖 37

  哈!這次有了!怎么回事?!

      這個時候,我的腦子里突然蹦出一個問題,當TextBox處于編輯狀態時,單擊確定按鈕會不會觸發TextBox的LostFocus事件?為了回答這個問題,我做了一個試驗,結果發現,當TextBox處于編輯狀態時,單擊頁面上的按鈕或者空白處都能觸發TextBox的LostFocus事件,而單擊Application Bar上的按鈕卻不會,在這種情況下,TextBox的內容不會提交!這個問題不難解決,我們只需在調用Submit方法之前讓TextBox失去焦點就行了,要實現這個效果,最簡單的辦法是讓頁面獲得焦點:

代碼 33

  這等效于單擊頁面上的空白處。不幸的是,這個辦法只在新建筆記時有效,我不知道為什么,看來我們只能走別的路子了。在Silverlight里,TextBox的Text屬性在兩種情況下會更新綁定源,第一種情況我們剛才試過了,結果你也知道了,第二種是手動更新,這里的手動更新并不是指把數據從Text屬性手動復制到Note對象的對應屬性,而是告訴Text屬性的綁定表達式把當前數據更新回綁定源。我們知道,當用戶單擊Application Bar上的確定按鈕時,最多只有一個TextBox獲得焦點,其它TextBox會因為失去焦點自動更新,因而沒有必要對每個TextBox進行手動更新。那么,如何才能得到獲得焦點的控件?FocusManager類提供了一個GetFocusedElement靜態方法,它返回獲得焦點的控件,如果這個控件是TextBox,我們就告訴Text屬性的綁定表達式把當前數據更新回綁定源:

代碼 34

  重新運行應用程序,這次沒問題了。雖然這個問題我們自己也可以解決,但我個人認為這是系統應該考慮到的問題,即使普通按鈕和Application Bar上的按鈕有著本質的區別,容許這種行為上的不一致會為開發者帶來不便和困惑。

  標簽

      到目前為止,我們的筆記本只不過是一個很普通的筆記本,雖然我們提供了與標簽相關的用戶界面,但這部分功能還沒真正實現出來。那么,如何實現這部分功能?

      我們知道,當用戶單擊Application Bar上的顯示標簽按鈕時,顯示標簽的ListBox將會滑出來,里面列出當前課程的標簽,用戶可以從中選擇一個標簽,此時,ListBox將會滑出去,筆記本的內容也將根據選中的標簽進行篩選。從這里不難看出,我們需要在NoteListViewModel類里添加兩個屬性,一個用于存放當前課程的標簽,另一個用于存放當前選中的標簽:

代碼 35

  它們分別綁到ListBox的ItemsSource和SelectedItem兩個屬性:

代碼 36

  那么,當用戶選好標簽之后,我們如何篩選筆記?還記得我們是如何根據課程名稱篩選筆記的嗎?我們直接把課程篩選條件告訴CollectionViewSource,當數據源發生改變時,CollectionViewSource將會自動篩選,因此,我們不妨考慮把標簽篩選條件整合進去,讓CollectionViewSource一并處理:

代碼 37

  需要說明的是,當用戶還沒選擇任何標簽時,SelectedTag屬性的值為null,此時,我們應該按照不做標簽篩選的情況處理,否則看看筆記的標簽是否包含用戶選中的標簽??吹竭@里,你可能會問,當用戶選擇一個標簽時,數據源并未發生任何改變啊,CollectionViewSource應該不會自動篩選吧?這個問題問得好!事實上,它不會自動篩選,我們需要手動刷新一下它生成的視圖:

代碼 38

  那么,如何初始化標簽列表?

      想想看,什么時候需要初始化標簽列表?每次打開筆記本的時候肯定需要初始化標簽列表,但除此之外呢?當用戶新建或編輯筆記時,可能引入新的標簽;當用戶編輯或刪除筆記時,可能刪除現有標簽,這些都會導致重新計算標簽列表。既然計算標簽列表的代碼需要在這么多地方使用,我們當然應該把它提取到一個單獨的方法里:

代碼 39

  計算標簽列表的思路非常簡單,首先,選取當前課程的筆記(忽略沒有標簽的),接著,從中提取標簽,為了避免前/后空格的影響,這里使用Trim方法做了處理,然后,去掉空字符串以及重復的標簽,最后,把標簽添加到Tags屬性。

      現在,請思考一下,我們應該在哪調用ComputeTags方法?有些同學可能會說,這還不簡單,分別在NoteListViewModel類的構造函數和三個操作的事件處理程序里調用不就行了?在NoteListViewModel類的構造函數和刪除操作的事件處理程序里調用是沒問題的,但在新建和編輯兩個操作的事件處理程序里調用就有問題了,為什么呢?舉個例子吧:

代碼 40

  你覺得上面代碼的最后一行是在NewOrEditNotePage頁顯示之前還是之后執行呢?答案是之前,這意味著標簽列表的計算在用戶編輯筆記之前就完成了,這顯然不是我們期望的效果。怎么辦?

      想想看,每次從NewOrEditNotePage頁返回都會發生什么事呢?觸發NoteBookPage頁的Loaded事件!于是,我們可以把代碼39里的最后一行放在Loaded事件處理程序里,不過,這樣做會導致一個問題,每次從主菜單打開NoteBookPage頁時,當前課程的標簽列表會被計算兩次,第一次是在NoteListViewModel類的構造函數里,第二次是在Loaded事件處理程序里,因為NoteBookPage頁每次顯示都會觸發Loaded事件。怎么處理這個問題?最簡單的辦法是通過一個bool變量區分NoteBookPage頁是否已經打開過。不過,既然我們的目的只是為了在標簽發生改變時做些事情,為什么不直接監聽Note對象的PropertyChanged事件呢?要實現這個效果,有三個事兒需要我們做的:

  1. 監聽現有Note對象的PropertyChanged事件。
  2. 監聽App.NoteStore.Items的CollectionChanged事件,一旦有新的Note對象添加進來就監聽它的PropertyChanged事件。
  3. 監聽App.NoteStore.Items的CollectionChanged事件,一旦現有的Note對象被刪除就移除PropertyChanged事件的監聽。

  我們可以在PropertyChanged事件處理程序里調用ComputeTags方法。

      雖然這種做法聽起來有點復雜,但它避免了第一種做法的問題。本質上,這兩種做法是等效的,只是一個在前臺處理,另一個在后臺處理,而正是這個角度的轉變讓我們對它們有了更進一步的了解。想想看,用戶并非每次新建/編輯筆記之后都會單擊Application Bar上的顯示標簽按鈕,一個比較常見的使用情景用戶把當天的筆記都輸入了,然后通過標簽的篩選來復習特定的內容,這樣的話,在用戶每次新建/編輯筆記之后重新計算標簽列表顯然沒有必要。事實上,如果用戶沒有單擊Application Bar上的顯示標簽按鈕,我們根本沒有必要計算標簽列表,換句話說,我們可以把代碼39里的最后一行放在顯示標簽按鈕的Click事件處理程序里,從而實現按需計算:

代碼 41

  需要注意的是,當用戶單擊Application Bar上的顯示標簽按鈕時,如果用戶還沒創建任何課程,直接調用ComputeTags方法將會引發異常,所以我們需要在調用之前判斷一下。不過,這種做法也有個問題,試想一下,如果用戶多次單擊Application Bar上的顯示標簽按鈕,其間沒有新建/編輯任何筆記,那么,除了第一次計算標簽內容是必要的,后面幾次都是多余的。那么,如何才能避免多余的計算?看到這里,你可能會說,為什么不把后兩種做法結合起來試一下呢,比如說,我們可以通過一個bool變量標識是否需要計算,然后在PropertyChanged事件處理程序里把它的值設為true,而在ComputeTags方法里,僅當這個變量的值為true時才執行計算,執行完畢之后把它的值設為false。嗯,這個主意不錯,我就把它留給你當課后作業吧。

      好了,不知不覺又到看效果的時候了!按F5運行應用程序,新建一條筆記:

圖 38

  單擊Application Bar上的顯示標簽按鈕:

圖 39

  單擊頁面空白處收回標簽列表。再新建一條筆記:

圖 40

  這次,我們給它兩個標簽,其中一個標簽是現有的,另一個是新的,并且分隔符后面有個空格。單擊確定返回,然后單擊Application Bar上的顯示標簽按鈕:

圖 41

  再新建一條筆記:

圖 42

  現在,單擊Application Bar上的顯示標簽按鈕:

圖 43

  選擇buying behavior:

圖 44

  再次單擊Application Bar上的顯示標簽按鈕,選擇sales strategy:

圖 45

  非常好!不過,現在有個問題,我想顯示所有筆記怎么辦?

      沒問題,我們可以在計算標簽列表的時候加插一個"特殊"的標簽:

代碼 42

  并在篩選的時候進行"特殊"處理:

代碼 43

  好了,重新運行應用程序,分別為兩個課程新建一些筆記:

圖 46

圖 47

  現在,單擊Application Bar上的顯示標簽按鈕:

圖 48

    選擇disposition effect:

圖 49

  現在,切換到sales psychology課程,單擊Application Bar上的顯示標簽按鈕,選擇sales strategy:

圖 50

  再次單擊Application Bar上的顯示標簽按鈕,選擇(全部):

圖 51

  現在,切換回behavioral finance課程:

圖 52

  怎么回事?!我們剛才已經做了篩選?。?/p>

      原來,當我們切換課程時,ListBox的ItemsSource屬性發生改變,導致ListBox的SelectedItem屬性被重設為null,而NoteListViewModel對象的SelectedTag屬性和ListBox的SelectedItem屬性是雙向綁定的,因而也被重設為null了。ListBox的SelectedItem屬性被重設為null是對的,因為新的數據源不一定包含SelectedItems屬性的值,但把NoteListViewModel對象的SelectedTag屬性也重設為null就不對了,因為同一個NotelistViewModel對象的Tags屬性肯定包含SelectedTags屬性的值,因此,SelectedTag屬性的set訪問器應該忽略這個重設:

代碼 44

  重新運行應用程序,重新執行一次上面的測試,嗯,這次沒問題了。

  命令與行為

      我們知道,WPF和最新的Silverlight 4都支持命令綁定,比如說,Button控件有一個Command屬性和一個CommandParameter屬性,前者用于綁定實現ICommand接口的對象,后者用于綁定傳給前者的參數,但SL for WP卻只有一個ICommand接口,這意味著我們無法為按鈕設置命令,而Application Bar上的按鈕這種異類就更不用說了。幸虧Prism為我們帶來了ApplicationBarButtonCommand(使用之前請先引用Microsoft.Practices.Prism.Interactivity.dll類庫),它能讓我們為Application Bar上的按鈕設置命令。下面,我們拿NewOrEditNotePage頁來示范它的用法。

      在設置命令之前,我們得先有個命令,而命令通常是由ViewModel類提供的。打開NewOrEditItemViewModel.cs,在NewOrEditItemViewModel類里添加一個SubmitCommand屬性:

代碼 45

  那么,我們應該如何初始化它?一般的做法是創建一個SubmitCommand類,并讓它實現ICommand接口,然后在NewOrEditItemViewModel類的構造函數里把SubmitCommand類的實例賦給SubmitCommand屬性。如果你不嫌麻煩的話,你可以這樣做,不過,由于創建命令對象的需求非常普遍,Prism為我們帶來了DelegateCommand泛型類,我們只需把提交數據的代碼傳給它的構造函數就可以了:

代碼 46

  接著,把_submit私有字段以及在構造函數里初始化它的代碼刪除,因為我們不再需要它了。刪除之后,把Submit方法改成這樣:

代碼 47

  換句話說,原來的_submit私有字段被現在的SubmitCommand屬性取代了。

      現在,打開NewOrEditNotePage頁,從Assets面板上把ApplicationBarButtonCommand拖到Objects and Timeline面板的PhoneApplicationPage上:

圖 53

  此時,Objects and Timeline面板將會變成這樣:

圖 54

  接著,在Properties面板上把ButtonText屬性的值設為"確定":

圖 55

  單擊CommandBinding右邊的小正方形,并選擇Custom Expression:

圖 56

  在彈出的Custom expression對話框里輸入"{Binding SubmitCommand}"并按回車:

圖 57

  用同樣的辦法把CommandParameterBinding設為"{Binding Item}"。

      那么,用戶提交數據之后如何返回?這個時候就輪到ApplicationBarButtonNavigation上場了。從Assets面板上把ApplicationBarButtonCommand拖到Objects and Timeline面板的PhoneApplicationPage上,此時,Objects and Timeline面板將會變成這樣:

圖 58

  接著,在Properties面板上把ButtonText和NavigateTo兩個屬性的值分別設為"確定"和"#GoBack":

圖 59

  需要說明的是,"#GoBack"是一個硬性規定的特殊值,當我們把NavigateTo屬性的值設為"#GoBack"時,ApplicationBarButtonNavigation會調用NavigationService.GoBack方法返回,而當我們把NavigateTo屬性設為XXX.xaml時,它會調用NavigationService.Navigate方法導航至對應的頁面。

      那么,Text屬性更新綁定源的問題呢?難道Prism也提供了相應的組件?沒有,這次我們得親自出手了。右擊Utils文件夾,然后選擇Add New Items,在彈出的New Item對話框里選擇Behavior,并把它命名為AppBarButtonUpdateSource:

圖 60

  我們知道,Application Bar上的按鈕并非Silverlight的一部分,因此Behavior無法直接作用于它,而Silverlight里只有PhoneApplicationPage類提供了訪問Application Bar的方法,因此我們需要把AppBarButtonUpdateSource的目標類型改為PhoneApplicationPage:

代碼 48

  這也正是我們把ApplicationBarButtonCommand和ApplicationBarButtonNavigation拖到PhoneApplicationPage上的原因。那么,如何才能找到Application Bar上的按鈕?Prism為我們帶來了FindButton擴展方法,可以通過按鈕的文字來查找,因此,我們需要創建一個ButtonText屬性和一個_button私有字段,前者用于指定待查找按鈕的文字,后者用于保存找到的按鈕:

代碼 49

  FindButton擴展方法需要一個實現IApplicationBar接口的對象,而能夠提供這個對象的只有PhoneApplicationPage對象,后者可以通過Behavior的AssociatedObject屬性訪問。于是,我們可以在OnAttached方法里初始化_button私有字段:

代碼 50

  找到按鈕之后,我們需要把更新綁定源的代碼關聯到按鈕上,而做到這點的唯一辦法就是為按鈕創建一個Click事件處理程序:

代碼 51

  最后,在OnDetaching方法里解除它們之間的關聯:

代碼 52

  現在,重新編譯項目,然后從Assets面板上把AppBarButtonUpdateSource拖到Objects and Timeline面板的PhoneApplicationPage上,并把ButtonText屬性的值設為"確定"。此時,Objects and Timeline面板將會變成這樣:

圖 61

  不過,這個順序是不對的,AppBarButtonUpdateSource應該排在其它兩個的前面,但這個順序在Expression Blend里無法調整,因此我們需要手動修改XAML。

      至于取消按鈕,由于它只是簡單地返回,我們只需為它添置一個ApplicationBarButtonNavigation就行了。添置好后,Objects and Timeline面板將會變成這樣:

圖 62

  現在,我們可以把這兩個按鈕的Click事件處理程序去掉了。

      命令綁定是MVVM模式的重要組成部分,它不但可以進一步降低View和ViewModel之間的耦合度,還可以簡化單元測試的工作。目前我們通過Behavior來實現命令綁定只是權宜之計,希望微軟可以在將來的版本里把這部分功能補完了。還有的就是希望微軟能夠進一步完善Application Bar,包括處理焦點問題以及提供更豐富的訪問方式。

  下課了……

it知識庫WP7有約(三):課堂重點,轉載需保留來源!

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。

主站蜘蛛池模板: 色欲AV精品人妻一二三区 | 成人AV精品视频 | 国产在线观看码高清视频 | 快播最新电影网站 | 无码国产精品高潮久久9 | 牛牛在线国产精品 | 国产又黄又粗又爽又色的视频软件 | 我的好妈妈BD免费观看 | 久久精品免视看国产 | 中文字幕AV亚洲精品影视 | 国产精品一区二区激情 | 国产成人精品免费视频下载 | 国产精品九九九久久九九 | 九九精品视频一区二区三区 | 双手绑在床头调教乳尖 | 人妻体内射精一区二区 | 国产免费毛片在线观看 | 老汉老太bbbbbxxxxx | 久久99国产精品一区二区 | 精品第一国产综合精品蜜芽 | 久久99热成人精品国产 | 色欲色香天天天综合 | 久久综合色超碰人人 | 青柠在线观看免费播放电影 | 天龙八部慕容属性加点 | 嫩草影院久久精品 | TUBE19UP老师学生 | 内射人妻无码色AV麻豆去百度搜 | 午夜福利影院私人爽爽 | 久久影院午夜理论片无码 | 国产精品自在在线午夜精品 | 秋霞电影网视频一区二区三区 | 乡村教师电影完整版在线观看 | 99精品热视频30在线热视频 | 使劲别停好大好深好爽动态图 | 日本免费xxx| 伊人不卡久久大香线蕉综合影院 | 亚洲欧美日韩精品久久奇米色影视 | 国产色精品VR一区二区 | adc免费观看 | 日本夜夜夜 |