|
你好,老七!
WP7終于發(fā)布了,到目前為止,有關(guān)它的新聞和介紹我相信你已經(jīng)看過不少了,所以這里將會直接跳過,不過在開始之前,我認(rèn)為還是有必要提醒你做好相關(guān)的準(zhǔn)備:
- Expression Blend 4 for Windows Phone和Visual Studio 2010 Express for Windows Phone,你并不需要完整的Expression Studio 4 Ultimate和Visual Studio 2010 Ultimate,不過如果你有的話*可能*會更好。
- 白開水,大量白開水,接下來你將會與我一起進(jìn)行大量腦力活動(dòng),你需要補(bǔ)充足夠的水分才能讓大腦更好地工作。
- 零食,最好是堅(jiān)果類,薯片也可以,人無法長時(shí)間集中精力,也不該迫使自己長時(shí)間集中精力,當(dāng)你感到注意力開始渙散時(shí),不妨抓一把零食放到嘴里嚼,注意別弄到鍵盤上哦。
- 最后,也是最重要的,你,沒錯(cuò),是你,僅當(dāng)你準(zhǔn)備好接受新的知識時(shí),你的大腦才會對它們進(jìn)行積極的處理,否則就會把它們擋在外面。
那么,你準(zhǔn)備好了嗎?
首先,打開Expression Blend,創(chuàng)建一個(gè)Windows Phone Panorama Application項(xiàng)目:
圖 1
項(xiàng)目創(chuàng)建好之后,你會看到一個(gè)充滿整個(gè)頁面的Panorama控件,里面有兩個(gè)Panorama項(xiàng),每個(gè)Panorama項(xiàng)里面有一個(gè)ListBox,而ListBox里也有了示例數(shù)據(jù)。你可以調(diào)整Artboard的縮放比例,以便顯示整個(gè)UI:
圖 2
注意,這里所說的整個(gè)UI是指手機(jī)屏幕所能顯示的部分,而Panorama控件具有延伸到屏幕以外區(qū)域的特性,所以我們無法一次過把整個(gè)Panorama控件盡收眼底,這確實(shí)是一件憾事。
接著,我們來看看Panorama控件,如果你對它的效果沒有感性認(rèn)識,不妨到先看看WP7的6個(gè)內(nèi)置Hub。認(rèn)識Panorama控件的最簡單方法是結(jié)合Objects and Timeline面板和Artboard來體驗(yàn)一下:
圖 3
如上圖所示,每個(gè)Panorama控件都是由一個(gè)標(biāo)題和若干Panorama項(xiàng)構(gòu)成的,而每個(gè)Panorama項(xiàng)又會包含一個(gè)標(biāo)題和一些內(nèi)容,在這里,這些內(nèi)容是通過ListBox來展示的,你可以根據(jù)實(shí)際的需要把它換成任何其它控件。此外,需要說明的是,Panorama控件和Panorama項(xiàng)的標(biāo)題都已經(jīng)內(nèi)化成自身的屬性,只需通過Properties面板設(shè)置就可以了,無需額外添加TextBlock或者其它控件。現(xiàn)在,我們的Panorama控件包含了兩個(gè)Panorama項(xiàng),但從上圖可以看到,只有第一個(gè)能完全顯示出來(由于截圖的關(guān)系,Artboard的一部分隱藏在滾動(dòng)條下面),而第二個(gè)只能看到一小部分,那么,如何才能顯示第二個(gè)Panorama項(xiàng),以便操作上面的控件呢?答案非常簡單,只需在Objects and Timeline面板上單擊第二個(gè)Panorama項(xiàng)就可以了:
圖 4
值得提醒的是,為了在操作時(shí)不影響其它Panorama項(xiàng),我們還可以通過Objects and Timeline面板把其它Panorama項(xiàng)鎖定,正如上圖所示的那樣。在繼續(xù)閱讀下面的內(nèi)容之前,我強(qiáng)烈建議你稍稍暫停一下,把注意力集中在Objects and Timeline面板上,熟悉一下各個(gè)對象之間的關(guān)系,試著單擊每個(gè)對象,然后看看它對應(yīng)了Artboard上的哪個(gè)對象。如果你已經(jīng)迫不及待想要親自體驗(yàn)一下Panorama控件的效果,你現(xiàn)在可以按F5了。
接下來,我們要執(zhí)行以下任務(wù):
- 修改Panorama控件的標(biāo)題
- 去掉Panorama控件的背景
- 刪除現(xiàn)有的兩個(gè)Panorama項(xiàng)
- 添加一個(gè)新的Panorama項(xiàng)
第一個(gè)任務(wù)非常簡單,確保Objects and Timeline面板上的Panorama控件處于選中狀態(tài),在Properties面板上的搜索框里輸入Title,第一個(gè)搜索結(jié)果就是我們要找的屬性了,修改這個(gè)屬性的值,然后按回車:
圖 5
第二個(gè)任務(wù)也挺簡單,在Properties面板上的搜索框里輸入Back,然后選擇No brush就可以了:
圖 6
第三個(gè)任務(wù)更簡單,按下Ctrl鍵,依次選中兩個(gè)Panorama項(xiàng),然后按Del鍵就可以了。最后一個(gè)任務(wù)是添加新的Panorama項(xiàng),打開Assets面板,在搜索框里輸入Pan:
圖 7
然后把PanoramaItem拖到Panorama控件上就可以了。注意,你可以把PanoramaItem拖到Objects and Timeline面板的Panorama控件上,也可以拖到Artboard的Panorama控件上,如果Artboard上的控件比較多,并且把Panorama控件擋住了,那么當(dāng)你把PanoramaItem拖到Artboard上時(shí),有可能會把它誤加到其它控件上。這是添加控件的一般方法,針對添加PanoramaItem,我們還有更簡單的方法,那就是右擊Panorama控件,然后選擇Add PanoramaItem就可以了:
圖 8
現(xiàn)在,向Panorama項(xiàng)添加一個(gè)TextBlock,內(nèi)容隨你,調(diào)整一下位置和大小,然后按F5:
圖 9
一般地,Panorama控件至少包含兩個(gè)Panorama項(xiàng),而這里只有一個(gè),屬于邊界情況,細(xì)心觀察上圖,表面上,右邊好像還有一個(gè)Panorama項(xiàng),但當(dāng)你在屏幕上向左滑動(dòng)時(shí),你會發(fā)現(xiàn)這其實(shí)是同一個(gè)Panorama項(xiàng)。那么向右滑動(dòng)呢?情況一樣。利用這個(gè)特點(diǎn),我們可以創(chuàng)建一個(gè)簡易計(jì)數(shù)器,把Panorama項(xiàng)的TextBlock綁定到一個(gè)計(jì)數(shù)變量上,當(dāng)我們向左滑動(dòng)時(shí),計(jì)數(shù)變量加1,向右時(shí)則減1,其效果就像我們擁有一個(gè)無限延伸的Panorama控件,而邊界情況就是這個(gè)計(jì)數(shù)變量的最大值和最小值,盡管如此,我們也無需太過擔(dān)心,假設(shè)計(jì)算變量的類型是Int32,我相信沒有人會向左或者向右滑動(dòng)超過20億次吧?如果你有興趣的話,不妨把它當(dāng)做課后練習(xí)。現(xiàn)在,按Back退出應(yīng)用程序。
上課啦!
上課啦?什么課?哪里上?看到這些問題,有沒有一種親切的感覺?說不定你今天就問了這些問題哦,那時(shí)你是不是在找課程表呢?如果課程表就在手機(jī)里該多好啊!事不宜遲了,我們自己弄一個(gè)吧。
右擊Projects面板里的項(xiàng)目節(jié)點(diǎn),選擇Add New Item:
圖 10
在彈出的New Item對話框里選擇Windows Phone Pivot Page,輸入頁面的名字,然后按OK:
圖 11
和Panorama頁一樣,Pivot頁也有一個(gè)充滿整個(gè)頁面的Pivot控件,剛創(chuàng)建好的Pivot控件默認(rèn)附帶兩個(gè)Pivot項(xiàng),我們可以把它們分別用于星期一和星期二。確保Pivot控件處于選中狀態(tài),在Properties面板上尋找Title屬性,并把它的值改為"課程表":
圖 12
接著在Objects and Timeline面板上選中第一個(gè)Pivot項(xiàng),在Properties面板上尋找Header屬性,并把它的值改為"星期一":
圖 13
完了之后把第二個(gè)Pivot項(xiàng)的Header屬性值改為"星期二"。此時(shí),你的 Pivot控件應(yīng)該是這樣的:
圖 14
嗯,看起來像個(gè)樣了,然而,標(biāo)題下面那么大的一塊空位應(yīng)該怎么處理呢?毫無疑問,以列表的方式呈現(xiàn)一天的課程是比較適合的,但是,我希望列表的每一項(xiàng)除了顯示課程名稱之外,還能顯示上課時(shí)間和上課地點(diǎn)。
在繼續(xù)設(shè)計(jì)UI之前,我們需要導(dǎo)入一些示例數(shù)據(jù),以便在設(shè)計(jì)時(shí)就能看到最終效果。當(dāng)然,你也可以讓Expression Blend為你生成這些數(shù)據(jù),不過,它無法為我們生成課程名稱以及適合的上課地點(diǎn),這樣,當(dāng)你在設(shè)計(jì)時(shí)調(diào)整控件外觀時(shí)就會感到缺了點(diǎn)兒什么,而這正是使用真實(shí)數(shù)據(jù)的好處。
假設(shè)我們要導(dǎo)入下面這個(gè)XML文件的數(shù)據(jù):
代碼 1
我們可以在Data面板上單擊Create sample data按鈕,然后選擇Import Sample Data from XML…:
圖 15
在彈出的Import Sample Data from XML對話框里,單擊Browse按鈕瀏覽并指定數(shù)據(jù)文件,然后按OK關(guān)閉對話框:
圖 16
此時(shí),Expression Blend會很努力地在后臺幫你生成一大堆東西,等它做完之后,你會看到Data面板上多了一堆東西,現(xiàn)在,確保Data面板上的List Mode按鈕處于按下狀態(tài),然后把courseCollection拖到Pivot項(xiàng)標(biāo)題下面的空白處:
圖 17
此時(shí),Expression Blend會為你創(chuàng)建一個(gè)ListBox,并把它的ItemsSource屬性綁定到courseCollection上,現(xiàn)在,右擊ListBox里的任何地方,然后選擇Auto Size/Fill,以便讓ListBox充滿整個(gè)Grid(Pivot項(xiàng)默認(rèn)有一個(gè)Grid子元素):
圖 18
嗯,不錯(cuò),每個(gè)列表項(xiàng)都包含了課程名稱、上課時(shí)間、下課時(shí)間以及上課地點(diǎn),可是,這些內(nèi)容各占一行,字體大小也是一樣,每個(gè)列表項(xiàng)之間又沒有明顯的間距,顯然不是那么好看,下面我們給它調(diào)整一下,右擊ListBox里的任何地方,然后選擇Edit Additional Templates/Edit Generated Items (ItemTemplate)/Edit Current進(jìn)入列表項(xiàng)模板的編輯狀態(tài):
圖 19
此時(shí),Objects and Timeline面板會發(fā)生變化,上面的對象不再是我們之前看到的那些,而變成列表項(xiàng)里的對象:
圖 20
從上圖不難看出,每個(gè)列表項(xiàng)都包含了四個(gè)TextBlock,這些TextBlock是用一個(gè)StackPanel裝著的。現(xiàn)在,你可以發(fā)揮你的創(chuàng)造力,把它調(diào)整成你喜歡的樣子,下面是我的調(diào)整結(jié)果:
圖 21
此時(shí),Objects and Timeline面板上面的對象應(yīng)該是這樣的:
圖 22
單擊上圖紅框那個(gè)箭頭退出列表項(xiàng)模板的編輯狀態(tài)。此時(shí),Objects and Timeline面板回復(fù)"原狀"了,你可以在上面看到Pivot控件和Pivot項(xiàng)。選中第二個(gè)Pivot項(xiàng),按照上面的步驟把星期二的課程數(shù)據(jù)導(dǎo)入,并把它拖到Pivot項(xiàng)上(注意,是第二個(gè)Pivot項(xiàng)哦),然后調(diào)整ListBox的大小,使之充滿整個(gè)Pivot項(xiàng)。那么,列表項(xiàng)的顯示方式怎么辦?要重復(fù)編輯一次嗎?當(dāng)然不用!我們只需應(yīng)用剛才那個(gè)就可以了。右擊ListBox里的任何地方,然后選擇Edit Additional Templates/Edit Generated Items (ItemTemplate)/Apply Resource/courseCollectionItemTemplate:
圖 23
此時(shí),你會發(fā)現(xiàn)列表項(xiàng)的風(fēng)格已經(jīng)變成和前面的一樣了:
圖 24
好了,我們現(xiàn)在可以把其它幾天的課程都加上去,然后在主頁(即第一個(gè)頁面)添加一個(gè)按鈕打開這個(gè)頁面就……慢著!我怎么編輯課程表?
編輯課程表
顯然,如果這個(gè)課程表不能編輯,那么它就等同花瓶了,所以我們要為它增加編輯功能,包括新建、編輯和刪除,我們可以把這些功能放在Application Bar上。在Expression Blend里添加Application Bar非常簡單,右擊Objects and Timeline面板上的PhoneApplicationPage,然后選擇Add ApplicationBar:
圖 25
接著右擊ApplicationBar,然后選擇Add ApplicationBarIconButton:
圖 26
確保剛才添加的Application Bar按鈕處于選中狀態(tài),在Properties面板上把它的IconUri屬性值改為New,并把它的Text屬性值改為"新建":
圖 27
按照上面的步驟添加另外兩個(gè)按鈕,完成之后你的課程表頁面應(yīng)該是這樣的:
圖 28
刪除功能只需獲取選中的課程并把它刪除就可以了,新建和編輯則不同,它們都需要另一個(gè)頁面來處理,我們知道,新建功能和編輯功能在用戶界面上的最大區(qū)別是前者的頁面有內(nèi)容而后者的沒有,所以我們可以為它們創(chuàng)建一個(gè)共用頁面。
創(chuàng)建一個(gè)Windows Phone Page,并把它命名為NewOrEditCoursePage.xaml:
圖 29
完了之后把ApplicationTitle的Text屬性值改為"課程表",但PageTitle保留原樣:
圖 30
因?yàn)樾陆üδ芎途庉嫻δ芄灿猛粋€(gè)頁面,所以PageTitle的Text屬性值可能是新建課程或者編輯課程,這將會在打開此頁面時(shí)通過傳入?yún)?shù)設(shè)置。標(biāo)題下面那塊空地將會放置四個(gè)控件,分別對應(yīng)課程名稱、上課時(shí)間、下課時(shí)間和上課地點(diǎn),首尾兩個(gè)將會是TextBox控件,而中間兩個(gè)將會是Silverlight for Windows Phone Toolkit的TimePicker控件。
右擊Projects面板上的References節(jié)點(diǎn),選擇Add Reference…:
圖 31
在彈出的Add Reference對話框里把C:/Program Files/Microsoft SDKs/Windows Phone/v7.0/Toolkit/Sep10/Bin/Microsoft.Phone.Controls.Toolkit.dll引用進(jìn)來。添加完引用之后就可以把控件添加到頁面了:
圖 32
需要說明的是,TimePicker控件已經(jīng)自帶標(biāo)題功能,你只需設(shè)置它的Header屬性就可以了,而普通的TextBox沒有標(biāo)題功能,只能自行添加TextBlock來模擬,為了使它的顏色和TimePicker控件的標(biāo)題的顏色一樣,我們需要把它的Foreground屬性值改為PhoneSubtleBrush。此時(shí),我的Objects and Timeline面板是這樣的:
圖 33
好了,課程表的用戶界面已經(jīng)設(shè)計(jì)完了,是不是很想看看運(yùn)行效果呢?沒問題!
回到CourseTimetablePage頁,在Objects and Timeline面板上選中第一個(gè)Application Bar按鈕,然后在Properties面板上單擊Events,并雙擊Click旁邊的編輯框:
圖 34
此時(shí),Expression Blend會打開CourseTimetablePage頁的代碼隱藏文件,并為Application Bar按鈕添加一個(gè)事件處理程序方法,我們只需在TODO下面加上一句就可以了:
代碼 2
接著,回到MainPage頁,把上面的TextBlock去掉,拖一個(gè)Button到中間,然后右擊這個(gè)Button,選擇Navigate To/CourseTimetablePage:
圖 35
好了,按F5吧:
圖 36
當(dāng)你單擊屏幕中間那個(gè)按鈕時(shí),課程表就會顯示:
圖 37
向左或者向右滑動(dòng)屏幕可以在不同的Pivot項(xiàng)之間來回切換,而向上或者向下滑動(dòng)屏幕則可以查看當(dāng)天課程。當(dāng)你單擊Application Bar上的新建按鈕時(shí),新建課程表的界面將會顯示:
圖 38
你可以輸入課程名稱和上課地點(diǎn),當(dāng)你單擊上課時(shí)間或者下課時(shí)間下面那個(gè)TimePicker控件時(shí),設(shè)置時(shí)間的界面將會顯示:
圖 39
你可以通過滑動(dòng)設(shè)置時(shí)間,這個(gè)界面下面有兩個(gè)Application Bar按鈕,左邊那個(gè)是確定,右邊那個(gè)是取消,但為什么這兩個(gè)圖標(biāo)是一樣的?其實(shí)它是找不到圖標(biāo)才這樣的,如果你下載了它的代碼,你會在TimePickerPage.xaml里看到它已經(jīng)把圖標(biāo)位置硬性規(guī)定為/Toolkit.Content/ApplicationBar.Check.png和/Toolkit.Content/ApplicationBar.Cancel.png了,你可以在PhoNEToolkitSample/Toolkit.Content文件夾里找到這兩個(gè)圖標(biāo),在項(xiàng)目里創(chuàng)建一個(gè)Tooklit.Content文件夾,把它們復(fù)制進(jìn)去,并把它們的Build Action設(shè)置為Content,重新運(yùn)行就能看到了。
到目前為止,我們只寫了一行代碼(其實(shí)這行代碼也可以省掉的),應(yīng)用程序的功能和操作就基本上體現(xiàn)出來了,此時(shí),你可能會問,在我們設(shè)計(jì)用戶界面的過程里,Expression Blend到底在背后為我們做了什么呢?
Expression Blend如何提供設(shè)計(jì)時(shí)數(shù)據(jù)的支持?
首先我們來看看Expression Blend為我們生成了哪些文件:
圖 40
由于我們的數(shù)據(jù)是從XML文件導(dǎo)入的,所以你會看到一個(gè)XSD文件,這是從我們那個(gè)XML文件生成的XML Schema,這個(gè)XSD文件將會用來生成相關(guān)的類,這些類都放在對應(yīng)的C#文件里,而我們的數(shù)據(jù)最終是以XAML的形式存在的。當(dāng)我們導(dǎo)入XML數(shù)據(jù)時(shí),Expression Blend不但為我們生成這些文件,還在App.xaml的Application.Resource里添加了相應(yīng)的對象:
代碼 3
為什么添加到App.xaml而不是某個(gè)頁面的XAML文件里呢?這是因?yàn)槲覀冊贗mport Sample Data from XML對話框里選擇了Define in Project(參見圖16),如果我們當(dāng)時(shí)選擇Define in This document,它就會添加到某個(gè)頁面的XAML文件里。那么,Expression Blend又是如何得知我們的數(shù)據(jù)保存在哪個(gè)XAML文件里呢?答案就在courses類的構(gòu)造函數(shù)里:
代碼 4
這個(gè)URI看起來有點(diǎn)古怪,如果你用Visual Studio打開這個(gè)項(xiàng)目,你將會看到這個(gè)XAML文件的Build Action是Page,事實(shí)上,它會被編譯成BAML,并且嵌到Iridescent程序集里,這種古怪的URI就是引用程序集內(nèi)嵌資源的表示方式。
接著,當(dāng)我們從Data面板把courseCollection拖到Pivot項(xiàng)上時(shí)(參見圖17),Expression Blend會把它所屬的MondayCoursesSampleData綁到Pivot控件的父容器的DataContext屬性上,在當(dāng)前Pivot項(xiàng)的子容器里創(chuàng)建一個(gè)ListBox,并把courseCollection綁到它的ItemsSource屬性上:
代碼 5
還記得Import Sample Data from XML對話框里有個(gè)選項(xiàng)是Enable sample data when application is running嗎?當(dāng)時(shí)它是選中的,如果我們把這個(gè)選項(xiàng)去掉,DataContext屬性前面就會多一個(gè)"d:":
代碼 6
這個(gè)前綴告訴編譯器在生成最終程序集時(shí)忽略這個(gè)屬性,這樣你就不會在程序運(yùn)行的時(shí)候看到這些數(shù)據(jù)了。
如果你細(xì)心觀察Projects面板,可能會發(fā)現(xiàn)幾個(gè)我們從未提及過的文件:
圖 41
它們是干嘛的呢?事實(shí)上,這些文件在我們剛創(chuàng)建完項(xiàng)目時(shí)就存在了,還記得最初的Panorama頁嗎(參見圖3和圖4),里面的Panorama項(xiàng)是有數(shù)據(jù)的,而這些數(shù)據(jù)就是來自MainViewModelSampleData.xaml文件的。那么,這些數(shù)據(jù)又是如何關(guān)聯(lián)到Panorama控件的呢?打開MainPage.xaml,在文件的頂部,你會發(fā)現(xiàn)它的蛛絲馬跡:
代碼 7
這里使用的不是我們常見的Binding,而是d:DesignData,正如你所看到的,它用來指定數(shù)據(jù)文件的位置,但這些數(shù)據(jù)是在設(shè)計(jì)時(shí)使用的,所以你會看到DataContext前面有個(gè)"d:",事實(shí)上,我們把這些帶有"d:"前綴的屬性稱為設(shè)計(jì)時(shí)屬性。和這些數(shù)據(jù)相關(guān)的類分別定義在MainViewModel.cs和ItemViewModel.cs文件里。如果你打開MainViewModel.cs,你會發(fā)現(xiàn)里面的LoadData方法包含了另一份不同的數(shù)據(jù),為什么,它們分別用來干嘛的?我給你截兩個(gè)圖,看你能否找到什么蛛絲馬跡:
代碼 8
代碼 9
看到了嗎,XAML文件里每個(gè)ItemViewModel對象的LineOne屬性值都是"design XXX",而C#文件的則是"runtime XXX",事實(shí)上,這已經(jīng)道出它們的用途了,XAML文件里的數(shù)據(jù)是設(shè)計(jì)時(shí)使用的,而C#文件里的則是運(yùn)行時(shí)使用的。但是,MainPage.xaml的根元素的DataContext屬性前面有"d:"前綴啊,之前不是說編譯器會在生成程序集的時(shí)候忽略它嗎,那應(yīng)用程序又如何找到運(yùn)行時(shí)使用的數(shù)據(jù)呢?答案就在MainPage.cs里:
代碼 10
App類的ViewModel是一個(gè)靜態(tài)屬性,它的任務(wù)只是創(chuàng)建一個(gè)MainViewModel對象,當(dāng)MainPage的構(gòu)造函數(shù)被調(diào)用時(shí),會把這個(gè)MainViewModel對象綁到自己的DataContext屬性上,當(dāng)Loaded事件觸發(fā)時(shí),如果數(shù)據(jù)還沒裝載,就調(diào)用LoadData方法裝載數(shù)據(jù)。
從上面的討論不難看出,Expression Blend的確為我們做了很多,而這些知識將會協(xié)助我們完成后面的開發(fā)任務(wù)。
保存課程表
說了那么多前端的東西,是時(shí)候看看后端了。課程表軟件說到底就是一個(gè)管理課程表數(shù)據(jù)的軟件,所以數(shù)據(jù)存儲是一個(gè)非常重要的環(huán)節(jié)。如果說用戶界面的設(shè)計(jì)是Expression Blend的強(qiáng)項(xiàng),那么業(yè)務(wù)邏輯的開發(fā)就是Visual Studio的地盤,既然接下來的著眼點(diǎn)是后端,當(dāng)然要切換到Visual Studio啦。
右擊Projects面板里的解決方案節(jié)點(diǎn),選擇Edit in Visual Studio:
圖 42
此時(shí),Visual Studio會打開,接下來,我們將會在Visual Studio里完成后端部分的開發(fā)。
首先,創(chuàng)建一個(gè)Models文件夾,在里面添加一個(gè)Course類,并讓它實(shí)現(xiàn)INotifyPropertyChanged接口:
代碼 11
接著,我們需要為它添加如下屬性:
屬性名字 | 屬性類型 | 備注 |
Name | string | 課程名稱 |
Day | string | 星期幾 |
StartTime | DateTime | 上課時(shí)間 |
EndTime | DateTime | 下課時(shí)間 |
Location | string | 上課地點(diǎn) |
把前端和后端連接起來
還記得Expression Blend是怎么做的嗎?它為MainPage頁創(chuàng)建一個(gè)與之對應(yīng)的MainViewModel類,并在代碼隱藏文件里把后者的實(shí)例綁到前者的DataContext屬性上,而剩下的事情就交給數(shù)據(jù)綁定來處理。接下來,我們將會模范這種做法,把前端和后端連接起來。
我們知道,一個(gè)課程表包含若干列,每列都包含了一個(gè)標(biāo)題和一組當(dāng)天的課程,整個(gè)課程表對應(yīng)于CourseTimetablePage頁,里面的每列對應(yīng)于一個(gè)Pivot項(xiàng),其中,列的標(biāo)題將會作為Pivot項(xiàng)的標(biāo)題顯示,而列所包含的那組當(dāng)天的課程將會在Pivot項(xiàng)所包含的ListBox里顯示。為了方便理解,我們把它們之間的映射關(guān)系制成下表:
頁面 | 頁面的抽象模型 |
CourseTimetablePage頁 | CourseTimetableViewModel類 |
Pivot項(xiàng) | CourseTimetableColumnViewModel類 |
Header屬性 | Header屬性 |
ListBox控件 | Courses屬性 |
操作課程表
我們知道,新增和修改這兩個(gè)操作是共用同一個(gè)頁面的,但它們的內(nèi)部邏輯又稍微有點(diǎn)不同, 你可以分別為它們創(chuàng)建兩個(gè)不同的ViewModel類,針對不同的操作為頁面創(chuàng)建不同的ViewModel對象,也可以在同一個(gè)ViewModel類里通過標(biāo)記變量和條件語句區(qū)分兩種不同的邏輯,你還可以像我這樣,在ViewModels文件夾里創(chuàng)建一個(gè)NewOrEditCourseViewModel抽象類,讓它實(shí)現(xiàn)INotifyPropertyChanged接口,并創(chuàng)建Title和Course兩個(gè)屬性以及Submit和Discard兩個(gè)抽象方法:
代碼 32
接著,在NewOrEditCourseViewModel類里創(chuàng)建NewCourseViewModel和EditCourseViewModel兩個(gè)私有類,并讓它們繼承NewOrEditCourseViewModel類。下面,我們先看NewCourseViewModel類。
課程表上的每個(gè)課程都會關(guān)聯(lián)到一周的某天,但NewOrEditCoursePage頁上卻沒有地方設(shè)置星期幾(參見圖32),這是為什么呢?Pivot控件的一個(gè)特點(diǎn)是每次只能顯示一個(gè)Pivot項(xiàng),這意味著整個(gè)課程表每次只能顯示一天的課程,如果把這天看做上下文,當(dāng)用戶單擊Application Bar上的新增按鈕時(shí),應(yīng)用程序就可以從上下文獲知應(yīng)該把課程添加到哪一天,從而為用戶省下設(shè)置星期幾這個(gè)步驟。我們可以通過參數(shù)傳遞這個(gè)數(shù)據(jù),然后在構(gòu)造函數(shù)里使用它創(chuàng)建Course對象:
代碼 33
當(dāng)用戶單擊確定時(shí),就會把課程添加到JsonCourseStore,而單擊取消的話就什么都不做:
代碼 34
那么,EditCourseViewModel類呢?當(dāng)用戶單擊Application Bar上的編輯按鈕時(shí),它需要的不是今天星期幾,而是用戶當(dāng)前選中的課程是什么,我們又該如何告訴它呢?我們知道,同一天的課程在時(shí)間上是互斥的,因?yàn)橥粫r(shí)間你不可能在不同教室上課(除非你懂影分身術(shù)),換句話說,Course類的Day和StartTime這兩個(gè)屬性組合起來可以成為唯一標(biāo)識。通過這個(gè)唯一標(biāo)識,我們可以從JsonCourseStore里獲取用戶當(dāng)前選中的課程,但是,我們是否把獲取到的課程直接賦給EditCourseViewModel的Course屬性呢?想想看,Course屬性將會和NewOrEditCoursePage頁上的控件進(jìn)行雙向綁定,當(dāng)用戶編輯控件的內(nèi)容時(shí),數(shù)據(jù)會直接反映在Course屬性上,如果Course屬性就是從JsonCourseStore里獲取到的課程,那么數(shù)據(jù)就會直接提交到JsonCourseStore。如果你選擇這樣做,你得先做個(gè)備份,假如用戶單擊取消,你就可以把備份的數(shù)據(jù)還原回去。另一個(gè)做法是從JsonCourseStore里獲取用戶當(dāng)前選中的課程,然后克隆一份賦給Course屬性,當(dāng)用戶單擊確定時(shí),就把Course屬性的數(shù)據(jù)更新過去,而單擊取消的話也是什么都不做。這里選擇后面那種做法:
代碼 35
創(chuàng)建好NewCourseViewModel和EditCourseViewModel兩個(gè)私有類之后,我們需要考慮一下如何訪問它們的實(shí)例,辦法可能有很多,其中最簡單的是在NewOrEditCourseViewModel類里創(chuàng)建兩個(gè)靜態(tài)方法,分別用于創(chuàng)建這兩個(gè)類的實(shí)例:
代碼 36
有了實(shí)例之后,我們就要考慮數(shù)據(jù)綁定的問題了,這個(gè)不難處理,我們總共也只有五個(gè)綁定需要?jiǎng)?chuàng)建,你可以參照下表修改NewOrEditCoursePage.xaml的內(nèi)容:
描述 | 類型 | 屬性 | 綁定表達(dá)式 |
頁面標(biāo)題 | TextBlock | Text | {Binding Title} |
課程名稱 | TextBox | Text | {Binding Course.Name, Mode=TwoWay} |
上課時(shí)間 | TimePicker | Value | {Binding Course.StartTime, Mode=TwoWay} |
下課時(shí)間 | TimePicker | Value | {Binding Course.EndTime, Mode=TwoWay} |
上課地點(diǎn) | TextBox | Text | {Binding Course.Location, Mode=TwoWay} |
菜單·樣品菜色
還差什么呢?噢,對了,目前我們是通過主頁中間的按鈕打開課程表的,這顯然不好意思拿出來見人,我們還是創(chuàng)建一個(gè)正式的主菜單吧。現(xiàn)在讓我們切換到Expression Blend,如果你的Expression Blend已經(jīng)關(guān)閉了,你可以右擊Solution Explorer的MainPage.xaml,然后選擇Open in Expression Blend:
圖 55
菜單的制作方式有很多種,這里選用ListBox:
圖 56
菜單創(chuàng)建好后,右擊里面的"課程表",然后選擇Navigate To/CourseTimetablePage,此時(shí),Expression Blend會為TextBlock添加一個(gè)NavigateToPageAction行為,但這個(gè)行為默認(rèn)是關(guān)聯(lián)到MouseLeftButtonDown事件的,這樣,當(dāng)用戶按下"課程表"還沒松開手就打開課程表了,而我們的習(xí)慣是松手之后才執(zhí)行相關(guān)的操作,為了實(shí)現(xiàn)這個(gè)效果,我們可以把關(guān)聯(lián)事件改為MouseLeftButtonUp:
圖 57
菜單有了,但樣品菜色卻被我們弄沒了,如果你現(xiàn)在打開CourseTimetablePage.xaml,你將會看到此番情景:
圖 58
之前我們手動(dòng)創(chuàng)建兩個(gè)Pivot項(xiàng),然后把它們分別綁到兩個(gè)示例數(shù)據(jù)源,但現(xiàn)在Pivot項(xiàng)是通過數(shù)據(jù)綁定動(dòng)態(tài)創(chuàng)建的,之前那些示例數(shù)據(jù)源就排不上用場了,既然用不了,那就刪了吧,打開Data面板,右擊數(shù)據(jù)源,然后選擇Delete data source:
圖 59
此時(shí),Expression Blend會把SampleData文件夾里的相關(guān)文件一并刪除。
在Expression Blend里,沒有設(shè)計(jì)時(shí)數(shù)據(jù)會為調(diào)整控件模板和樣式帶來很大不便,下次你去找設(shè)計(jì)師調(diào)整一下用戶界面,他們很可能會對你大罵一頓,那么,有沒有辦法讓它再次顯示設(shè)計(jì)時(shí)數(shù)據(jù)呢?答案是有的,而且不止一種,下面來看其中一種。我們知道,CourseTimetablePage頁使用了數(shù)據(jù)綁定,而綁定表達(dá)式只提及了綁定的屬性,并未提及屬性所在的類型,換句話說,只要屬性名字能夠匹配,示例數(shù)據(jù)就應(yīng)該綁得上去。首先,準(zhǔn)備一份XML,注意匹配綁定表達(dá)式的屬性名字:
代碼 48
接著,在Data面板上把它導(dǎo)入(參見圖15),注意,這次要把Enable sample data when application is running選項(xiàng)去掉:
圖 60
導(dǎo)入之后在Data面板上把自動(dòng)生成的ColumnCollection和CourseCollection分別改為Columns和Courses,以便匹配綁定表達(dá)式的屬性名字:
圖 61
現(xiàn)在,把Timetable拖到Pivot控件上,此時(shí)你會看到鼠標(biāo)下方有個(gè)小提示,告訴你Expression Blend將會把Pivot控件的DataContext屬性綁到CoursesSampleDataSource:
圖 62
當(dāng)你松開鼠標(biāo)時(shí),就會看到課程名稱了,但上課時(shí)間、下課時(shí)間和上課地點(diǎn)卻顯示不出來:
圖 63
這是為什么呢?如果你把CourseTimetablePage頁面關(guān)閉,然后重新打開它,你就會看到問題所在了:
圖 64
這個(gè)異常明確地告訴我們調(diào)用轉(zhuǎn)換器時(shí)拋出InvalidCastException。還記得嗎,我們的轉(zhuǎn)換器會把傳入對象強(qiáng)制轉(zhuǎn)換成DateTime對象,然后調(diào)用它的ToShortTimeString方法,但Expression Blend為示例數(shù)據(jù)生成的StartTime屬性和EndTime屬性是字符串類型的!明白為什么會這樣,問題就不難解決了:
代碼 49
重新編譯項(xiàng)目,然后重新打開CourseTimetablePage頁,你就會看到設(shè)計(jì)時(shí)數(shù)據(jù)了:
圖 65
好了,總算對設(shè)計(jì)師有個(gè)交代了。
下課了……
it知識庫:WP7有約(一):課程安排,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時(shí)間聯(lián)系我們修改或刪除,多謝。