<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      WM有約II(八):本地化

      WM有約II(八):本地化

       

      Written by Allen Lee

       

      讓用戶界面支持多種語言

            如果你不曾為.NET Compact Framework的應(yīng)用程序做過本地化,我建議你先去閱讀MSDN的《設(shè)備的本地化注意事項》,以便了解.NET Compact Framework在這方面的一些限制。

            首先,在當(dāng)前項目里創(chuàng)建一個Resources文件夾,并在里面創(chuàng)建若干資源文件:

      圖 1

      接著,編輯Resource1.en-US.resx和Resource1.zh-CN.resx資源文件,分別提供英語和簡體中文:

      圖 2

      圖 3

      那么,剩下那個(默認)資源文件有什么用呢?把上面其中一個資源文件的Name列復(fù)制到Resource1.resx,留空Value列:

      圖 4

      打開Resource1.Designer.cs,你會發(fā)現(xiàn)Visual Studio幫你創(chuàng)建了一個Resource1類,里面包含了獲取語言資源的代碼,比如說,上面的Form1_label1_Text和Form1_label2_Text可以通過如下所示的兩個屬性獲取:

      代碼 1

      我們可以在Form1里創(chuàng)建一個SetUpUITexts方法,在里面使用Resource1類:

      代碼 2

            在使用Resource1類的這些屬性之前,我們得先設(shè)置Resource1.Culture屬性,正是這個屬性指定了用戶界面的語言。由于Resource1類并不保存這個屬性的值,于是我們需要另外編寫代碼把它保存到配置文件里。在Options.xml里添加下面這行XML:

      <option name="language" value="zh-CN" />

      并在OptionManager類里添加如下屬性:

      代碼 3

      當(dāng)然,選項窗體也需修改,以便用戶選擇語言:

      圖 5

      那么,選項窗體最下面那個ComboBox里應(yīng)該提供什么語言給用戶選擇?是不是應(yīng)用程序支持的語言呢?當(dāng)然不是,應(yīng)用程序支持英語和簡體中文,而操作系統(tǒng)可能不支持簡體中文,卻支持英語和其它語言,所以那個ComboBox里提供的語言應(yīng)該是兩者的交集。然而,.NET Compact Framework并沒提供內(nèi)置的方法,我們又如何獲取系統(tǒng)支持的語言呢?這里有篇帖子給出了使用非托管API的辦法:

      http://stackoverflow.com/questions/435951/compact-framework-retrieve-a-list-of-countries-and-regions

      我們可以直接使用CultureInfoHelpr.GetCultures方法獲取系統(tǒng)支持的語言。于是,我們可以創(chuàng)建一個LanguageManager類,在里面提供一個AvailableLanguages屬性,用于獲取填充到那個ComboBox的語言:

      代碼 4

      此外,LanguageManager還需提供獲取/設(shè)置當(dāng)前語言以及語言變更通知功能,當(dāng)用戶更改當(dāng)前語言時,LanguageManager會把更改反應(yīng)到配置文件和Resource1.Culture屬性,然后發(fā)出語言變更通知:

      代碼 5

      當(dāng)應(yīng)用程序啟動時,我們需要設(shè)置Resource1.Culture屬性,并訂閱LanguageChanged事件和本地化主窗體,于是,主窗體的構(gòu)造函數(shù)需要添加如下代碼:

      代碼 6

      其它窗體則不必訂閱該事件,因為用戶每次打開的都是"全新"的窗體,所以我們只需要為其它窗體添加類似于主窗體的SetUpUITexts方法,并在它們的構(gòu)造函數(shù)里調(diào)用即可。

            回到選項窗體,當(dāng)用戶打開選項窗體時,它會從LanguageManager.AvailableLanguages屬性獲取可選語言,并填充到ComboBox里,然后設(shè)置語言的顯示方式:

      代碼 7

      當(dāng)用戶單擊OK菜單項關(guān)閉選項窗體時,ComboBox的當(dāng)前選擇會反應(yīng)到LanguageManager.Language屬性上:

      LanguageManager.Instance.Language = (CultureInfo)cmxLanguages.SelectedItem;

            下面我們來看看效果:

      圖 6

      圖 7

      圖 8

      圖 9

      圖 10

      圖 11

      整體而言還算不錯,帶有方框的地方還需繼續(xù)改善,其中,紅色方框是多語言沒有觸及的地方,而藍色方框則是因為不同語言文字長度不同導(dǎo)致的空白,圖11還發(fā)現(xiàn)一個BUG,ComboBox的選中項和當(dāng)前語言不吻合,這是因為我們還沒把LanguageManager.Language屬性的值賦給ComboBox.SelectedItem(代碼7),改過來就好了。

       

      讓枚舉支持多種語言

            還記得我們?nèi)绾蚊枋鲋鞔绑w的歷史記錄的過濾條件嗎?使用FilterOptions枚舉(參見《WM有約II(七):番外篇》的代碼6和代碼7)。這種做法的好處是簡單直接,然而,一旦遇到多語言的需求就會力不從心,出現(xiàn)圖6的情況,那么,我們應(yīng)該如何改善這個問題呢?

            首先,在資源文件里添加相應(yīng)的條目:

      Resource1.resx

      InterceptionHistory_FilterOptions_All

       

      InterceptionHistory_FilterOptions_Today

       

      表 1

      Resource1.en-US.resx

      InterceptionHistory_FilterOptions_All

      All

      InterceptionHistory_FilterOptions_Today

      Today

      表 2

      Resource1.zh-CN.resx

      InterceptionHistory_FilterOptions_All

      所有

      InterceptionHistory_FilterOptions_Today

      今天

      表 3

      我們的任務(wù)就是建立枚舉和對應(yīng)資源之間的關(guān)聯(lián),那么,它們兩者又該如何關(guān)聯(lián)起來呢?一個辦法是通過Dictionary維護枚舉值和資源鍵之間的關(guān)系,我們可以創(chuàng)建一個MultilingualFilterOptionsHelper類來管理這個Dictionary。由于FilterOptions是在InterceptionHistory類里定義的,使用時必須引用它的全稱"InterceptionHistory. FilterOptions",不太方便,于是我們可以先給它一個別名:

      using FilterOptions = Trombone.InterceptionHistory.FilterOptions;

      再創(chuàng)建MultilingualFilterOptionsHelper類:

      代碼 8

      由于MultilingualFilterOptionsHelper的主要任務(wù)是"計算"給定枚舉在當(dāng)前語言的顯示文本,于是它需要提供一個GetLocalizedName的方法來負責(zé)這項工作:

      代碼 9

      當(dāng)然,就我們的問題而言,上面這個"一對一"的"計算"功能是遠遠不夠的,因為我們最終要把枚舉作為數(shù)據(jù)源綁定到主窗體的ComboBox上,所以我們需要一個能夠計算所有枚舉成員的顯示文本的方法。然而,.NET Compact Framework沒有提供Enum.GetValues方法,我們無法簡單直接地獲取枚舉的所有成員,一個變通的做法就是讓別人傳給你:

      代碼 10

      原本,如果"計算"結(jié)果只是作為主窗體的ComboBox的數(shù)據(jù)源,那么把GetLocalizedFilterOptions方法的返回值類型定為object是最簡單直接的做法,因為這樣我們就可以返回匿名類型數(shù)組,但事實上,當(dāng)用戶更改過濾條件時,我們需要把新的過濾條件傳給InterceptionHistory,于是我們需要為返回值定義一個新的類型:

      代碼 11

      并對GetLocalizedFilterOptions方法做相應(yīng)的修改。MultilingualFilterOptionsHelper類是針對FilterOptions枚舉的,但它的代碼可以通過泛型一般化,使它可以處理任何枚舉:

      代碼 12

      當(dāng)然,你也可以通過反射創(chuàng)建一個GetEnumValues方法:

      代碼 13

      這樣,你就可以免卻別人向你傳遞枚舉成員了:

      代碼 14

      如果還沒滿足,希望可以通過特性在枚舉成員上指定資源鍵,像這樣:

      代碼 15

      那么你可以創(chuàng)建一個MultilingualEnumAttribute:

      代碼 16

      這樣,你就可以免卻別人向你傳遞關(guān)聯(lián)關(guān)系了:

      代碼 17

            回到主窗體,創(chuàng)建一個SetUpFilterOptions方法來初始化那個ComboBox:

      代碼 18

      這個方法可以在應(yīng)用程序啟動時使用,也可以在用戶更改當(dāng)前語言時使用,對于后者,我們需要在重設(shè)那個ComboBox的數(shù)據(jù)源之后把原先選中的項選上。這樣,我們就可以把初始化那個ComboBox的代碼替換為這個方法的調(diào)用了:

      代碼 19

      而處理LanguageManager.LanguageChanged事件和ComboBox.SelectedIndexChanged事件的代碼也需要稍作修改:

      代碼 20

      代碼 21

            下面我們來看看效果:

      圖 12

            毫無疑問,我們已經(jīng)實現(xiàn)了想要的功能,可這就行了嗎?我相信,任何一個訓(xùn)練有素的程序員在完成一個設(shè)計或者實現(xiàn)一個功能之后都會反問自己這樣一個問題:這個設(shè)計/代碼有足夠的彈性嗎?不同的程序員對這個"足夠的"的理解可能有著很大差異,那么,一般而言怎樣才算"足夠的"呢?拿本例來說吧,假如現(xiàn)在有一個新的需求,在主窗體上顯示本周的歷史記錄,對于不懂程序開發(fā)的用戶來說,他們可能認為只需在ComboBox里添加一個"本周",然后把根據(jù)這個條件查詢到的數(shù)據(jù)顯示在主窗體上,他們并不清楚這個需求會牽涉多大范圍的改動,對于程序員來說,這個范圍當(dāng)然越小越好,最好就是只需創(chuàng)建一個包含過濾邏輯和返回顯示文本的對象,然后把剩下的事情交給應(yīng)用程序,這樣的話,需求和實現(xiàn)的增長水平就相當(dāng)了。然而,回顧當(dāng)前的實現(xiàn),我們不難發(fā)現(xiàn)它并不能很好地適應(yīng)這種線性增長的需求,怎么辦?

            重構(gòu)或許是一條出路,為什么說"或許"呢,試想一下,如果這是你一個人的項目,那么即使你把它推倒重來也只是你一個人的事,如果你在一個團隊里,情況就不太一樣了,你的重構(gòu)行為會通過代碼間接影響別人,而別人也會/要對這些影響作出回應(yīng)。人們常說,懶惰是程序員的優(yōu)秀特質(zhì),每個程序員都有懶惰的權(quán)利,然而,懶惰并不總是和產(chǎn)生高度重用的代碼有關(guān),它有時也會和傾向于保持現(xiàn)有代碼不變有關(guān),當(dāng)你辯說重構(gòu)能使應(yīng)用程序更好地適應(yīng)新的需求,別人也會舉出重構(gòu)帶來的沖擊和需求發(fā)生的幾率來反駁,這種討論常常從兩個人發(fā)展成一伙人,中間伴隨多次反復(fù),從這個層面上看,重構(gòu)已經(jīng)不是單純的技術(shù)之事了。團隊里的每個成員都有選擇懶惰的自由,但每個成員的自由又會影響其他成員的自由,這讓我想起存在主義的其中一個哲學(xué)觀點——他人是地獄,協(xié)調(diào)每個成員的自由是管理的藝術(shù)。就本例而言,重構(gòu)并不會導(dǎo)致這些問題,所以我們不妨趁此機會觀察一下重構(gòu)會使現(xiàn)有代碼如何演變。

       

      重構(gòu):枚舉 + 條件判斷 => 策略模式

            首先,創(chuàng)建一個IFilter接口:

      代碼 22

      接著,創(chuàng)建一個FilterBase抽象類,負責(zé)INotifyPropertyChanged接口的實現(xiàn),當(dāng)用戶更改當(dāng)前語言時,它會通知ComboBox過濾器的Text屬性改變了:

      代碼 23

      然后就是兩個具體的過濾器實現(xiàn)了:

      代碼 24

      代碼 25

      接下來,在InterceptionHistory類里添加一個Filter屬性:

      代碼 26

      如果你讀過之前的文章,你可能會覺得代碼24和代碼25似曾相識,事實上,它們是從原來的FilterOptions屬性提取出來的。此外,我們還需要一組過濾器對象作為ComboBox的數(shù)據(jù)源:

      代碼 27

            回到主窗體,我們需要一個SetUpFilters方法來設(shè)置ComboBox的數(shù)據(jù)源:

      代碼 28

      接著,把上面的代碼19改為SetUpFilters方法的調(diào)用,而上面的代碼21也要做相應(yīng)的調(diào)整:

      代碼 29

      刪除不要的代碼并運行應(yīng)用程序,效果和圖12一樣。

            下面,我們試著在新的體系下添加一個新的過濾器——"本周"。首先,分別在三個資源文件里添加相應(yīng)的條目;接著,創(chuàng)建一個ThisWeekPassFilter類:

      代碼 30

      然后,在InterceptionHistory.AvailableFilters屬性(參見代碼27)里添加一個ThisWeekPassFilter實例:

      代碼 31

      最后,運行一下看看效果:

      圖 13

      圖 14

            現(xiàn)在,添加新的過濾器變得如此簡便,以至于我不禁想添加更多的過濾器,比如說,我想查看發(fā)送方的等級為Contact或以上的歷史記錄,或者發(fā)送方的請求為PingSchedule的歷史記錄,又或者所有等候處理的歷史記錄等等。以上這些都是無需用戶參與的,如果我希望添加涉及用戶參與的呢,比如說,查看指定發(fā)送方的歷史記錄,顯然,我們需要向用戶提供一個輸入?yún)?shù)的界面,這些參數(shù)可以看作過濾器的配置信息,當(dāng)然也需要存儲下來,以免用戶每次使用都要重新輸入。以上這些都是簡單過濾,如果我需要比較復(fù)雜的過濾呢,比如說,我想查看指定發(fā)送方本周的歷史記錄,或者發(fā)送方的等級為Whitelist且請求為PingSchedule的歷史記錄,我們當(dāng)然可以完全重新創(chuàng)建兩個獨立的過濾器,但由于它們都可以看作多個簡單過濾的組合,于是我又不禁想把過濾器改為鏈式結(jié)構(gòu),這樣,復(fù)雜過濾器就可以看作由簡單過濾器組合的過濾鏈了,當(dāng)然,這也意味著我們需要向用戶提供一個更復(fù)雜的界面來管理這些過濾器……

            一開始,我使用枚舉和條件判斷來實現(xiàn)這部分功能,我甚至不希望添加新的過濾器,因為這意味著要修改遍布各處的零散代碼,一不小心就會找不著北;接著,在實現(xiàn)多語言的時候,我開始探討如何重構(gòu)這部分功能,使之更具彈性;后來,重構(gòu)的價值被證明之后,我不但萌生了添加更多過濾器的想法,還想為用戶提供更復(fù)雜的組合過濾鏈,而這在之前使用枚舉和條件判斷來實現(xiàn)的時候是無法想象的。在這個過程里,我們清晰地感受到實現(xiàn)的演進,然而,過濾鏈實現(xiàn)的呈現(xiàn)并非必然的,它實際上是在重構(gòu)之后才(更容易)看到的可能,如果我們一直停留在原來的枚舉和條件判斷,或許我們會因為代碼邏輯變得更加復(fù)雜糾纏而放棄,最終走向另一個方向。在一個更高的層面上看,程序員的想法影響了功能的實現(xiàn),而實現(xiàn)的方式也會反過來影響程序員下一步的想法,接著,程序員下一步的想法又會影響功能的后續(xù)實現(xiàn),而后續(xù)實現(xiàn)的方式也會反過來影響程序員再下一步的想法……細心思考這個過程,不難發(fā)現(xiàn)程序員的想法和功能實現(xiàn)的方式并非簡單的一一對應(yīng),而是像下面這幅圖那樣相互影響、共同演進:

      圖 15

      這個過程實際上體現(xiàn)了喬治·索羅斯的"反射理論(reflexivity)"。我們常常說需求總是在變,事實上,需求的演變過程也存在上述特征,很多時候,客戶的后續(xù)需求都是在看了當(dāng)前效果之后才萌生的,你可以說是客戶的潛在需求,但你無法斷定這個需求的必然性,就像上面提到的過濾鏈一樣,它的出現(xiàn)并非必然的,任何期望以靜態(tài)的方法在一開始把需求固定下來的努力都是徒勞的,因為它企圖回避參與者的認知和客觀事實之間的不對應(yīng)問題。迭代方法似乎是我們的救命草,因為它承認雙方的相互作用,不幸的是,我們永遠無法到達終極需求,只能無限接近,因為人類的心智永遠可以創(chuàng)造出新的需求,如果說凡是有源頭的都不是永恒的,那么只有在我們廢棄這個項目/產(chǎn)品時這個過程才會真正終結(jié),從這點來看,如果我們還要為這個過程加上一個期限,那么迭代方法很可能是通往地獄的另一條路。

       

      處理日期和時間

            日期和時間的處理是本地化過程需要考慮的問題之一,它們的表現(xiàn)形式依賴于區(qū)域設(shè)置,一般情況下,日期和時間的處理包括存儲、解析和顯示三種操作,對于存儲和解析,要根據(jù)固定區(qū)域設(shè)置來處理,而對于顯示,則根據(jù)當(dāng)前區(qū)域設(shè)置來處理。

            聽起來好像很復(fù)雜,但做起來其實很簡單,拿PingSchedule(參見《WM有約II(四):你明天有空嗎?》)來舉例,發(fā)送查詢短信息的代碼(在Form1.cs的btn_Click方法里)現(xiàn)在是:

      代碼 32

      由于我們沒有指定區(qū)域設(shè)置,ToString方法將會使用當(dāng)前區(qū)域設(shè)置,這樣的話,如果接收方的區(qū)域設(shè)置和發(fā)送方的不同,解析過程就會出問題。若要解決這個問題,只需向ToString方法傳遞固定區(qū)域設(shè)置:

      代碼 33

      而接收方也需要告訴Parse方法根據(jù)固定區(qū)域設(shè)置進行解析(原本代碼參見《WM有約II(四):你明天有空嗎?》的代碼7):

      代碼 34

      應(yīng)用程序里涉及日期和時間的存儲和解析的還有RegistrationQueue類和InterceptionHistory類,對于前者,我們可以套用上面的做法修改代碼相應(yīng)的地方,而對于后者,由于我們使用db4o直接存取DateTime對象,db4o會處理相關(guān)細節(jié),無需我們動手。

            接著,我們來看看日期和時間的顯示問題,這次,我們拿RegistrationQueue來舉例。首先,分別在三個資源文件里添加用于DataGrid表頭顯示的文本(參見圖8)。接著,創(chuàng)建一個SetUpRegistrationQueue方法,這個方法將會完成三個工作,第一個是創(chuàng)建DataGrid的表格樣式:

      代碼 35

      我們通過DataGridTextBoxColumn.FormatInfo屬性來指定區(qū)域設(shè)置,DataGridTextBoxColumn.MappingName屬性則用于指定該列將會顯示Registration對象的哪個屬性,DataGridTableStyle.MappingName屬性比較麻煩,它一般用于指定DataGrid將會顯示DataSet的哪個表的,當(dāng)數(shù)據(jù)源是對象(泛型)集合時,需要通過BindingSource作為中介,并把DataGridTableStyle.MappingName屬性設(shè)為元素類型的名字,第二個是設(shè)置DataGrid樣式和數(shù)據(jù)源:

      代碼 36

      第三個是處理LanguageManager.LanguageChanged事件:

      代碼 37

      最后,把初始化DataGrid的代碼替換為SetUpRegistrationQueue方法的調(diào)用,運行一下看看效果:

      圖 16

      圖 17

      應(yīng)用程序里涉及日期和時間的顯示還有InterceptionHistory,但處理方法是一樣的,所以這里就不一一細說了。

            另一個與日期和時間有關(guān)的問題是"一周的第一天",在實現(xiàn)ThisWeekPassFilter類時,我們?nèi)藶榈匕阉付樾瞧谝唬欢瑢τ诓煌膮^(qū)域設(shè)置來說,這個"一周的第一天"可能是不同的,所以不能硬編碼,我們可以通過如下代碼獲取當(dāng)前區(qū)域設(shè)置的"一周的第一天":

      DayOfWeek firstDayOfWeek = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;

      當(dāng)然,ThisWeekPassFilter.GetLowerBound方法需要據(jù)此做出相應(yīng)的調(diào)整。

       

      你還想要什么?

            應(yīng)用程序首次啟動時,當(dāng)前語言應(yīng)該是不存在的,因為用戶還沒設(shè)置,但我們總得選擇一個來使用,目前的做法是在配置文件里硬編碼"zh-CN",這會導(dǎo)致應(yīng)用程序在不支持簡體中文的操作系統(tǒng)上出問題,硬編碼"en-US"可能是最簡單的方法,因為英文總是得到支持,但這對于簡體中文的用戶來說并非預(yù)期效果,所以我們可以在應(yīng)用程序首次啟動時,先檢查操作系統(tǒng)的語言是否應(yīng)用程序所支持的,若是,把當(dāng)前語言設(shè)為此語言,否則,使用"en-US"。這部分邏輯可以放在LanguageManager的構(gòu)造函數(shù)里:

      代碼 38

      應(yīng)用程序首次啟動時,由于配置文件的language選項的值是空字符串,OptionManager將會返回固定區(qū)域設(shè)置,我們通過把OptionManager返回的區(qū)域設(shè)置和固定區(qū)域設(shè)置進行比較來判斷是否首次啟動,注意,如果你用"=="運算符來比較,結(jié)果總是false,即使兩個都是固定區(qū)域設(shè)置,換用Equals方法來比較就沒問題了。值得提醒的是,上面代碼使用了Language和AvailableLanguages屬性而不是對應(yīng)的私有成員,這是因為這些屬性包含了其它邏輯。

            至此,應(yīng)用程序的開發(fā)要暫告一段落了,下一集,我們將會探討本系列的最后一個話題——部署。

      posted @ 2009-04-06 12:58  Allen Lee  閱讀(3840)  評論(20)    收藏  舉報
      主站蜘蛛池模板: 大胸少妇午夜三级| 国产69精品久久久久乱码免费| 国产精品二区中文字幕| 人妻夜夜爽天天爽一区| 亚洲人成电影在线播放| 国产精品午夜福利91| 国产稚嫩高中生呻吟激情在线视频| 强奷乱码中文字幕| 强奷乱码欧妇女中文字幕熟女| 日本一卡2卡3卡4卡无卡免费| 在线精品国产中文字幕| 日韩精品一区二区三区激情视频| 91老熟女老女人国产老| 免费视频一区二区三区亚洲激情| 黄色大全免费看国产精品| 日本精品aⅴ一区二区三区| 日韩精品一区二区三区四| 亚洲AV日韩AV综合在线观看| 国产精品日日摸夜夜添夜夜添2021 | 亚洲精品国产中文字幕| 亚洲三区在线观看无套内射| 亚洲精品综合一区二区在线| 亚洲a∨国产av综合av| 国产专区一va亚洲v天堂| 久久无码中文字幕免费影院蜜桃 | 日本在线a一区视频高清视频| 国产精品一线二线三线区| 久爱无码精品免费视频在线观看| 亚洲色偷偷色噜噜狠狠99| 视频一区二区三区高清在线| 国产超高清麻豆精品传媒麻豆精品 | 尤物视频色版在线观看| 柯坪县| 欧美精品一区二区三区中文字幕| 亚洲国内精品一区二区| 浦城县| 欧美日韩亚洲国产| 97se亚洲综合自在线| 国产午夜福利视频第三区| 暖暖 在线 日本 免费 中文| 无套内谢少妇毛片在线|