|
Written by Allen Lee
不要留戀過(guò)去
怎樣才能約束用戶,不讓其選擇過(guò)去的日期呢?有一個(gè)很傻的辦法,就是每次啟動(dòng)應(yīng)用程序的時(shí)候,自動(dòng)把MonthCalendar控件的MinDate屬性的值設(shè)為今天。這樣雖然禁止了用戶選擇過(guò)去的日期,卻帶來(lái)另外一些問(wèn)題:
- 當(dāng)月之前的日期無(wú)法查看。
- 和選項(xiàng)窗體的Min Date設(shè)置相沖。
有鑒于此,我們采用另一種辦法,就是在用戶選中某個(gè)日期時(shí),判斷這個(gè)日期是否已經(jīng)過(guò)去,若是,則禁用Pin菜單項(xiàng),若否,則啟用Pin菜單項(xiàng)。那么,如何獲知用戶選中了某個(gè)日期?最簡(jiǎn)單的辦法就是使用MonthCalendar控件的DateChanged事件:
代碼 1
運(yùn)行應(yīng)用程序,你會(huì)發(fā)現(xiàn),當(dāng)我選中今天或者將來(lái)的日期時(shí),Pin菜單項(xiàng)是啟用的(圖1),而當(dāng)我選中過(guò)去的日期時(shí),Pin菜單項(xiàng)則是禁用的(圖2):
圖 1
圖 2
這(幾)天不要選
在繼續(xù)之前,我們有必要搞清楚,"排除某(幾)天"究竟是什么意思。在這里,"排除某(幾)天"并不是指禁止用戶選中那(幾)天,而是指那(幾)天不在計(jì)劃中,但我們很清楚,計(jì)劃趕不上變化,或許那(幾)天真正到來(lái)的時(shí)候又可以選了。
和之前的"釘住日期"相比,"排除日期"除了無(wú)需在MonthCalendar控件上有所反映之外,其它部分基本上是一樣的,它支持排除某天、連續(xù)的幾天和某個(gè)周末,用來(lái)保存被排除的日期的文件和應(yīng)用程序放在同一個(gè)文件夾里,應(yīng)用程序在啟動(dòng)的時(shí)候會(huì)檢查這個(gè)文件是否存在,如果不存在就創(chuàng)建一個(gè)空白的文件。從上面這些描述來(lái)看,"排除日期"和"釘住日期"在很大程度上共享著相同的代碼,于是,接下來(lái)就是考慮如何重用現(xiàn)有的代碼并實(shí)現(xiàn)新的功能。
首先要處理的是LoadPinnedDates和SavePinnedDates兩個(gè)方法(參見(jiàn)《WM有約(一):你好,CF》的代碼5和代碼6),我們提取這兩個(gè)方法的代碼,并創(chuàng)建兩個(gè)新的方法:
代碼 2
代碼 3
這樣,LoadPinnedDates和SavePinnedDates兩個(gè)方法就可以簡(jiǎn)化為分別對(duì)LoadDates和SaveDates兩個(gè)方法的調(diào)用了,而LoadExcludedDates和SaveExcludedDates兩個(gè)方法也可以如法炮制了。在著手實(shí)現(xiàn)這些方法之前,我們還需要提供一個(gè)東西,那就是文件的路徑,也是我們接下來(lái)需要做的事情——改造GetFilePath方法(參見(jiàn)《WM有約(一):你好,CF》的代碼4),改造后的GetFilePath方法將會(huì)用來(lái)映射文件路徑:
代碼 4
有了這些準(zhǔn)備,我們就可以著手實(shí)現(xiàn)LoadExcludedDates和SaveExcludedDates兩個(gè)方法了:
代碼 5
至于LoadPinnedDates和SavePinnedDates兩個(gè)方法的新版本就留給讀者自行處理了。
接著就是"排除日期"的核心功能——ExcludeWeekend和ExcludeRange兩個(gè)方法了,它們與PinWeekend和PinRange兩個(gè)方法(參見(jiàn)《WM有約(一):你好,CF》的代碼1和代碼2)的最大區(qū)別就是不需要把操作結(jié)果反映在MonthCalendar控件上,而它們的共同之處是都要計(jì)算具體的日期并把它們添加到對(duì)應(yīng)的集合里。我們先來(lái)看看計(jì)算具體的日期這部分功能,它分為兩種情況,一種是計(jì)算周末的,另一種是計(jì)算兩個(gè)日期之間的,如果這兩個(gè)日期相同,則視為一天,于是,我們可以創(chuàng)建CalculateWeekend和CalculateRange兩個(gè)方法來(lái)分別負(fù)責(zé)這兩種情況:
代碼 6
有了這些準(zhǔn)備,我們就可以著手實(shí)現(xiàn)ExcludeWeekend和ExcludeRange兩個(gè)方法了:
代碼 7
至于PinWeekend和PinRange兩個(gè)方法的新版本就留給讀者自行處理了。
還差什么呢?對(duì),用戶界面,沒(méi)有這個(gè),我們辛苦了這么久就白干了:
圖 3
還有Exclude菜單項(xiàng)的相關(guān)代碼:
代碼 8
噢,別忘了在InitializeFile方法(參見(jiàn)《WM有約(一):你好,CF》的代碼12)里添加檢查文件是否存在的代碼,以及在適當(dāng)?shù)牡胤教砑颖4鏀?shù)據(jù)的代碼,否則……
運(yùn)行應(yīng)用程序,選中2009年1月17日到2009年1月31日之間的日期,然后單擊Exclude菜單項(xiàng):
圖 4
通過(guò)資源管理器找到ExcludedDates.txt文件,然后用Word Mobile查看里面的內(nèi)容,結(jié)果發(fā)現(xiàn)只有下面3天!
圖 5
問(wèn)題出在哪里?原來(lái),我選中的那幾天的開(kāi)始日期恰好是星期六,于是應(yīng)用程序"自作聰明"地把它視為一般周末!如何解決這個(gè)問(wèn)題?回到代碼8,我們知道,ExcludeWeekend方法的調(diào)用需要滿足兩個(gè)條件,第一個(gè)是用戶只選中了一天,另一個(gè)則是這天是星期六。要知道用戶是否只選中了一天,我們只需要看看SelectionStart和SelectionEnd兩個(gè)屬性是否同一天:
代碼 9
再次運(yùn)行應(yīng)用程序,這次就正常了:
圖 6
需要提醒的是,Pin菜單項(xiàng)的相關(guān)代碼由于應(yīng)用了相同的邏輯,于是也存在相同的問(wèn)題,不過(guò)解決方法是一樣的,所以這里就不說(shuō)了。另外,因?yàn)?排除日期"不像"釘住日期"那樣會(huì)在用戶界面上有所反映,所以當(dāng)我們單擊Exclude菜單項(xiàng)時(shí),一切都在后臺(tái)完成,如果用戶不知情的話,感覺(jué)起來(lái)就像什么也沒(méi)干一樣,為了增強(qiáng)用戶體驗(yàn),最好就顯示一個(gè)消息框告訴用戶日期已被排除。
這(幾)天應(yīng)該選
由于"包含日期"和"排除日期"極其相似,再加上我們?cè)趯?shí)現(xiàn)"排除日期"時(shí)提取的公共代碼也適用于"包含日期",于是,我們可以用非一般的速度來(lái)實(shí)現(xiàn)"包含日期"的內(nèi)部邏輯:
代碼 10
至于用戶界面,我們同樣為它添加一個(gè)Include菜單項(xiàng):
圖 7
而這個(gè)菜單項(xiàng)的相關(guān)代碼如下:
代碼 11
其它東西,例如應(yīng)用程序啟動(dòng)的時(shí)候檢查用來(lái)保存日期的文件是否存在、讀取保存的日期和在適當(dāng)?shù)臅r(shí)候保存日期,和前面的實(shí)現(xiàn)大同小異,這里就不細(xì)說(shuō)了。
運(yùn)行應(yīng)用程序,選中2009年2月14日,然后單擊Include菜單項(xiàng):
圖 8
由于這天剛好是星期六,所以應(yīng)用程序執(zhí)行了包含周末的邏輯,這也是預(yù)期的行為:
圖 9
到了這里,你可能會(huì)認(rèn)為"排除日期"和"包含日期"也是時(shí)候告一段落了,但事實(shí)上我們還有一個(gè)問(wèn)題需要處理。試想一下,如果我對(duì)同一個(gè)日期先后執(zhí)行包含和排除操作,那么應(yīng)用程序是否應(yīng)該分別在m_IncludedDates和m_ExcludedDates兩個(gè)集合里登記這個(gè)日期?我們知道,"排除日期"和"包含日期"都是用來(lái)反映計(jì)劃的調(diào)整,比起分別在兩個(gè)地方登記同一個(gè)日期,執(zhí)行抵消操作或許更有意義。舉個(gè)例子,剛才我包含了2009年2月14日,現(xiàn)在我要排除這個(gè)日期,那么應(yīng)用程序應(yīng)該從m_IncludedDates里刪除這個(gè)日期而不是向m_ExcludedDates添加這個(gè)日期。怎么樣?是不是很簡(jiǎn)單?然而,這個(gè)東西實(shí)現(xiàn)起來(lái)一點(diǎn)都不容易,因?yàn)槲覀兺ǔ2僮鞯氖且唤M日期而不是單個(gè)日期,如果我們足夠好運(yùn),那么要抵消的日期集合會(huì)是被抵消的日期集合的子集,如果我們不夠運(yùn)氣,那么……一般地,如果我們要包含一組日期,那么我們要先檢查m_ExcludedDates是否包含了這些日期的部分或全部,如果是,則從m_ExcludedDates里刪除相同部分,剩下的才添加到m_IncludedDates。以IncludeWeekend方法(參見(jiàn)代碼10)為例,從最初的monthCalendar1.SelectionStart到最后的m_IncludedDates.AddRange需要經(jīng)過(guò)如下四步:
圖 10
其中,第三步的Subtract方法是解決這個(gè)問(wèn)題的關(guān)鍵,那么,如何實(shí)現(xiàn)這個(gè)方法呢?我們知道,List本身沒(méi)有提供這個(gè)方法,要想達(dá)到這樣的效果就得使用C# 3.0的擴(kuò)展方法了。下面來(lái)看看我的實(shí)現(xiàn):
代碼 12
對(duì)于second里的每個(gè)日期,Subtract方法試圖從first里刪除,并通過(guò)Remove方法的返回值判斷刪除操作是否成功,如果不成功,就意味著這個(gè)日期應(yīng)該添加到m_IncludedDates里,于是返回這個(gè)日期。有了這些準(zhǔn)備,我們就可以著手修改IncludeWeekend方法:
代碼 13
另外,IncludeRange、ExcludeWeekend和ExcludeRange等方法也需要修改,不過(guò)都是大同小異,所以就不一一列舉了。
下一次是什么時(shí)候?
下一次……在MonthCalendar控件下面……
圖 11
通常,這種可預(yù)測(cè)的"下一次"都意味著計(jì)算周期的存在,對(duì)于這個(gè)應(yīng)用程序,這個(gè)周期是兩周,以星期六為計(jì)算基準(zhǔn),比如說(shuō),假設(shè)上圖的5、6和7三天已被釘住,那么 下次應(yīng)該被釘住的日期將會(huì)是19、20和21三天,于是"Next time:"下面的Label就應(yīng)該顯示"2008年12月19日"。這個(gè)計(jì)算過(guò)程的一般形式如下圖所示:
圖 12
有了這些分析,我們就可以著手實(shí)現(xiàn)CalculateNextTime方法了:
代碼 14
故事到此結(jié)束了嗎?當(dāng)然不是,前面我們花了這么多精力來(lái)實(shí)現(xiàn)"排除日期"和"包含日期",如果僅僅用來(lái)保存一些日期,那么我也未免太無(wú)聊了。
首先,我們來(lái)看看"包含日期"將會(huì)如何影響"下一次"的計(jì)算,還是借用圖11,假設(shè)19、20和21三天已被釘住,今天是23號(hào),那么"Next time:"下面的Label應(yīng)該顯示"2009年1月2日",但如果26、27和28三天已被包含,那么"Next time:"下面的Label就應(yīng)該顯示"2008年12月26日"了。簡(jiǎn)而言之,在時(shí)間軸上排在前面的"包含日期"將會(huì)取代使用默認(rèn)算法計(jì)算出來(lái)的日期:
代碼 15
接著,我們?cè)賮?lái)看看"排除日期"將會(huì)如何影響"下一次"的計(jì)算,假設(shè)2、3和4三天已被釘住,今天是6號(hào),那么"Next time:"下面的Label應(yīng)該顯示"2009年1月17日",但如果17到31之間的日期已被排除,那么"Next time:"下面的Label就應(yīng)該顯示"2009年2月7日"了。
圖 13
簡(jiǎn)而言之,"排除日期"會(huì)導(dǎo)致使用默認(rèn)算法計(jì)算出來(lái)的日期逐周往后推,直到計(jì)算出來(lái)的日期沒(méi)被排除為止:
代碼 16
由此可見(jiàn),完整的CalculateNextTime方法應(yīng)該包含如下四步:
圖 14
其中,第一步和第四步是從代碼14里分解出來(lái)的:
代碼 17
有了這些準(zhǔn)備,我們就可以著手實(shí)現(xiàn)完整的CalculateNextTime方法了:
代碼 18
最后,終于到最后了,我們要把計(jì)算結(jié)果顯示在應(yīng)用程序主窗體的"Next time:"下面,那么,我們應(yīng)該在什么時(shí)候顯示呢,又應(yīng)該在什么時(shí)候更新呢?用Activated事件!你可能會(huì)問(wèn),為什么不用Load事件呢?這是因?yàn)楫?dāng)用戶單擊應(yīng)用程序右上角的關(guān)閉按鈕時(shí),應(yīng)用程序?qū)嶋H上只是最小化到后臺(tái),當(dāng)用戶通過(guò)菜單或者其它方式再次啟動(dòng)應(yīng)用程序時(shí),實(shí)際上只是把應(yīng)用程序"還原"到前臺(tái),而在這個(gè)過(guò)程里L(fēng)oad事件并不會(huì)被觸發(fā)。事不宜遲,讓我們完成本集的最后一段代碼吧:
代碼 19
運(yùn)行應(yīng)用程序,終于看到下一次是什么時(shí)候了:
圖 15
你還想要什么?
在這本集里,我們花費(fèi)巨大精力實(shí)現(xiàn)"下一次"的計(jì)算,然而,"除了'現(xiàn)在',你永遠(yuǎn)不能生活在任何其他時(shí)刻,你所能得到的只是現(xiàn)在的時(shí)光,未來(lái)在到來(lái)時(shí)也只不過(guò)是另一個(gè)現(xiàn)在"([美]韋恩·W·戴爾,《你的誤區(qū)》),好好把握每一個(gè)"現(xiàn)在",你將會(huì)得到一個(gè)滿意的軌跡。
到目前為止,應(yīng)用程序的用戶界面都是為"垂直"屏幕設(shè)計(jì)的,有沒(méi)有想過(guò),假如用戶旋轉(zhuǎn)設(shè)備的屏幕,使之變成"水平"的,將會(huì)發(fā)生什么事情呢?下一集,我們將會(huì)探討這個(gè)問(wèn)題及其解決方案,我們還會(huì)嘗試創(chuàng)建用戶控件來(lái)封裝MonthCalendar控件、"下一次"Label以及相關(guān)的代碼,如果"時(shí)間"允許的話,我們還會(huì)看看如何在這個(gè)用戶控件上實(shí)現(xiàn)數(shù)據(jù)綁定。
相關(guān)文章:
NET技術(shù):WM有約(三):下一次是什么時(shí)候?,轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。