在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

內容

介紹

背景

使用的代碼

自動更新ObjectDataProvider

設計時支持

增加本地化字符串

添加更多的 Cultures

列舉可用 Cultures

用戶控件設計解決方案

限制

歷史

介紹

本文是以簡單且可維護的方式解決WPF應用程序本地化問題。

在這種情況下,我還想:

  1. 在運行時切換區域設置——可自動更新所有得本地化元素
  2. 使用現有的可以在Visual Studio中維護的資源文件(**.resx files)。
  3. 對Expression Blend(還有其他XAML設計應用程序)提供設計時支持。

示例應用程序中需要有一些字符串來展示本地化功能,我將以最簡短的方式來實現。

背景

關於WPF本地化問題有許多其他的文章,包括使用Locbaml本地化WPF應用程序(https://www.codeproject.com/KB/WPF/Article.aspx),其中介紹了對XAML文件進行本地化的不同方法,每種方法都有各自的優缺點。

這篇文章中的第一種方法(不使用LocBaml.exe的目標本地化,https://www.codeproject.com/KB/WPF/Article.aspx)給了我很多啟發,但為了在運行時自動更新元素(對於所有元素,甚至是現有的Windows),我不得不尋找其它的解決方案。

另一個較早的項目是WPF “WPF Multi-Lingual at Runtime”(https://www.codeproject.com/KB/WPF/Article.aspx),它確實為運行時自動更新提供瞭解決方案,但在我看來,它對語言資源文件的管理太複雜了。在本文中,通過支持使用現有資源文件( *.resx files),我們仍然可以輕鬆地將現有應用程序轉換為新的區域設置。

我完全推薦閱讀和理解上面這兩篇文章,其中有很多信息,以及在其他項目中幫助我很多的有用技巧。

使用代碼

ObjectDataProvider自動更新

為了在更改當前區域設置時自動更新元素,我正在利用ObjectDataProvider提供的功能特性。

來自MSDN頁面的說明:

“當您想用另一個對象替換當前綁定源對象並更新所有關聯的綁定時,這個類也很有用。”

因此,我們需要做的就是替換(或刷新)ObjectDataProvider對象實例,並且ODP屬性上的任何綁定都將自動更新。

這就是這個多語言支持解決方案的改進之處。

雖然可以直接綁定到自動生成的RESX designer類的屬性(Resources.Designer.cs),但我們還需要為ODP獲得該類的一個實例。

所有的 ResXFileCodeGenerators (默認和自定義)都將資源 Resources 構造函數標記為 internal(內部的),這意味著只能從同一程序集中訪問它。(這同時也意味著無法從XAML文件中去訪問構造函數——即使用ODP ObjectType)。要解決這個問題,我們可以在ODP上使用MethodName屬性。(另一種選擇可能是擴展現有的自定義ResXFileCodeGenerator來標記構造函數為public,但不需要這樣做)

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

…的方法:

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

使用MethodName意味著ODP將成為方法返回的對象,允許我們綁定Resources類的實例。我們可以創建這個實例,因為上面對內部構造函數的調用來自同一個程序集中,而不是直接來自XAML。

這樣做的一個限制是資源類必須是公共的,因為我們不能使用公共方法返回一個內部類的實例(這會導致編譯錯誤)。這意味著我們可以使用擴展的強類型資源發生器[^]在Visual Studio 2005和2008,或附帶的PublicResXFileCodeGenerator工具Visual Studio 2008。

我喜歡擴展的代碼生成器,因為它生成了非常有用的字符串格式化方法。

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

更新當前的區域設置(Culture)非常簡單,我添加了一個方法來CultureResources類更新當前資源文件和ObjectDataProvider觸發一個更新,導致調用GetResourceInstance方法,更新ODP ObjectInstance,刷新任何綁定在ODP上元素——更新到新的資源文件。

設計時支持

在設計時,Properties.Resources。區域設置最初設置為項目中的使用默認語言集,如果沒有設置默認語言,則使用當前線程的區域設置。

增加本地化字符串

您想要本地化的所有字符串都需要在所有資源文件中定義,以便本地化工作,因此,在使用默認資源設置所有內容之後,通常會更容易添加更多的區域性.resx文件。否則,您將需要向所有現有的RESX文件添加每個新字符串。

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

然後,我們可以向所需的UI元素添加綁定:

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

如果我們將這個資源字符串添加到默認資源RESX文件中,在重新編譯項目之後,這個默認的字符串值現在應該出現在設計器中,當然,在運行應用程序時也是如此。

如果您看到除了默認值之外為資源文件添加的字符串似乎總是顯示默認值,那麼請檢查每個RESX文件中的資源字符串名稱是否正確。如果有綁定錯誤,那麼綁定中的路徑集不匹配任何RESX文件中的任何字符串,而且它甚至不能像以前那樣返回默認的RESX值。

添加更多區域設置

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

向項目中添加另一種區域設置的簡單方法是複製和粘貼默認資源文件。在Visual Studio中創建一個新文件的resx文件。從MSDN MSDN CultureInfo 列表中(http://msdn2.microsoft.com/en-us/library/system.globalization.cultureinfo.aspx)選擇合適的區域文件名字。在資源文件擴展名中添加區域性代碼,如在Resources.Fr-fr.resx,在編譯應用程序時,Visual Studio將使用它創建本地化的DLL。

現在您已經有了一個新的RESX文件,您可以更改區域資源文件的資源值,這樣新的區域(Resources.Fr-fr.resx)設置就添加完成了。

列舉可用語言文化區域設置

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

通過向這個項目添加一些區域設置,可以演示用於枚舉我們實現的那些區域性的代碼。我這樣做是為了避免在添加新區域設置時需要重新構建應用程序。

對於現有的已安裝的程序,您只需要創建一個帶有新的區域設置名稱的文件夾,並將新的正確命名的資源DLL放入其中。重新啟動應用程序,它就能列出系統中可選擇的區域設置列表。

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

以上是檢查任何與區域性名稱匹配的文件夾的application bin目錄的一種相對快速的方法。如果字符串參數不匹配任何已定義的CultureInfo類型,CultureInfo.GetCultureInfo()方法將會失敗。

UserControls設計時支持解決方案

在使用用戶控件(UserControl)本地化支持時,我遇到了一個問題。如果您想要本地化的屬性是在用戶控件之外(作為依賴項屬性添加到代碼隱藏文件中)是可訪問的,那麼沒有問題,您可以按照上面描述的那樣本地化它們。但是,如果您希望本地化的屬性不是外部可訪問的,比如Label Content屬性,那麼解決方案就有點麻煩了。

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

當您在UserControl中添加一個綁定到一個標籤時,它將在運行時被正確地顯示出來,在設計時(例如在Blend中),當它被自己加載時也會被正確展示。不幸的是,當您加載包含UserControl的窗口時,它將無法展示出來。(這似乎只是Blend工具的一個問題,在這種情況下,Visual Studio 2008設計器能夠正常展示。)

我理解,將UserControl作為窗口的子控件加載時的問題是,設計器創建控件的實例,然後將其添加到窗口中。運行時可用的資源不存在,因為實例不是在窗口中創建的,因此上面的綁定失敗,無法呈現控件。在多次嘗試解決這種情況的失敗之後,我最終得出了以下結論:

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案

使用DesignerProperties.GetIsInDesignMode()意味著該代碼只在設計時執行,而它所做的只是將包含我們的Resources ObjectDataProvider的ResourceDictionary添加到設計器本身,以便在初始化UserControl時可以使用它們。這實際上是ODP的第二個實例,在運行時會很糟糕(因為只有App.xaml中包含的第一個實例會被更新),但在設計時很好,因為我們不會更新區域性。

問題解決了。

限制

在本例中,我使用WPF綁定,這需要依賴屬性來綁定。在其他情況下,您可能希望訪問這些屬性,但是添加綁定並不合適,也不容易實現。例如,當您希望直接從代碼訪問本地化的值時。為了在這種情況下保持自動更新工作,您可以在ObjectDataProvider DataChanged事件上連接一個eventhandler,該事件是在我們更新ODP後觸發的。因此,當在eventhandler中重新獲取值時,更新的資源值是可用的。或者,您可以確保在知道ODP已更新後重新獲取本地化後的值,這沒什麼區別。

在VisualStudio中提供運行時和設計時支持的WPF本地化解決方案


分享到:


相關文章: