.NET WinForms 本地化 - 替換 ComponentResourceManager
在我目前的項目(.NET Windows 窗體應用程序)中,我要求 .NET Windows 窗體應該本地化,但本地化元素(只是翻譯,而不是圖像或控制位置)應該來自數據庫,以便啟用最終使用者可以根據需要修改控制項的可本地化屬性(只是標題/文本)。為了讓開發人員擺脫本地化問題的負擔,對我來說最好的解決方案是在 VS 設計器中簡單地將表單標記為可本地化。這會將所有可本地化的屬性值放在表單 .resx 文件中。
現在我的問題是如何從數據庫中提供翻譯。表單被標記為可本地化的那一刻,VS 表單設計器將放置所有可以本地化的表單 .resx 文件。設計器還將修改標準的 Designer.cs InitializeComponent 方法,以便實例化 ComponentResourceManager,然後使用該資源管理器載入對象(控制項和組件)的可本地化屬性。
我已經看到人們建立了自己的方法來將本地化屬性應用於 Form 及其控制項的解決方案。我見過的所有解決方案通常歸結為遞歸地遍歷 Form 及其控制項的 Controls 集合併應用翻譯。不幸的是,.NET Forms 本地化並不是那麼簡單,這並不能涵蓋所有場景,尤其是如果您有一些 3rd 方控制項。例如:
this.navBarControl.OptionsNavPane.ExpandedWidth = ((int)(resources.GetObject("resource.ExpandedWidth"))); // // radioGroup1 // resources.ApplyResources(this.radioGroup1, "radioGroup1"); ... this.radioGroup1.Properties.Items.AddRange(new DevExpress.XtraEditors.Controls.RadioGroupItem[] { new DevExpress.XtraEditors.Controls.RadioGroupItem(resources.GetString("radioGroup1.Properties.Items"), resources.GetString("radioGroup1.Properties.Items1")), new DevExpress.XtraEditors.Controls.RadioGroupItem(resources.GetString("radioGroup1.Properties.Items2"), resources.GetString("radioGroup1.Properties.Items3"))});我見過的所有解決方案都無法進行上述組件所需的翻譯。
由於 VS 已經生成了在需要的地方提供翻譯的程式碼,我理想的解決方案是以某種方式用我自己的派生類替換 ComponentResourceManager。如果我可以在 InitializeComponent 中替換以下行:
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));和
System.ComponentModel.ComponentResourceManager resources = new MyComponentResourceManager(typeof(Form1));然後我可以毫無問題地解決其他所有問題。
不幸的是,我沒有找到如何做這樣的事情,所以我在這裡問一個關於如何做的問題。
PS 我也會接受任何其他符合要求的本地化解決方案: 1. 無需重新部署應用程序就可以更改翻譯 2. 開發人員在創建表單/使用者控制項時不應該關心翻譯
謝謝你。
編輯:拉里提供了一本很好的參考書,其中的程式碼部分解決了我的問題。借助該幫助,我能夠創建自己的組件,該組件替換了 InitializeComponent 方法中的預設 ComponentResourceManager。這是一個名為 Localizer 的組件的程式碼,它用自定義的 MyResourceManager 替換了 ComponentResourceManager,以便其他人也可以從中受益:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.ComponentModel.Design.Serialization; using System.CodeDom; using System.ComponentModel.Design; namespace LocalizationTest { [Designer(typeof(LocalizerDesigner))] [DesignerSerializer(typeof(LocalizerSerializer), typeof(CodeDomSerializer))] public class Localizer : Component { public static void GetResourceManager(Type type, out ComponentResourceManager resourceManager) { resourceManager = new MyResourceManager(type); } } public class MyResourceManager : ComponentResourceManager { public MyResourceManager(Type type) : base(type) { } } public class LocalizerSerializer : CodeDomSerializer { public override object Deserialize(IDesignerSerializationManager manager, object codeDomObject) { CodeDomSerializer baseSerializer = (CodeDomSerializer) manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer)); return baseSerializer.Deserialize(manager, codeDomObject); } public override object Serialize(IDesignerSerializationManager manager, object value) { CodeDomSerializer baseSerializer = (CodeDomSerializer)manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer)); object codeObject = baseSerializer.Serialize(manager, value); if (codeObject is CodeStatementCollection) { CodeStatementCollection statements = (CodeStatementCollection)codeObject; CodeTypeDeclaration classTypeDeclaration = (CodeTypeDeclaration)manager.GetService(typeof(CodeTypeDeclaration)); CodeExpression typeofExpression = new CodeTypeOfExpression(classTypeDeclaration.Name); CodeDirectionExpression outResourceExpression = new CodeDirectionExpression( FieldDirection.Out, new CodeVariableReferenceExpression("resources")); CodeExpression rightCodeExpression = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("LocalizationTest.Localizer"), "GetResourceManager", new CodeExpression[] { typeofExpression, outResourceExpression }); statements.Insert(0, new CodeExpressionStatement(rightCodeExpression)); } return codeObject; } } public class LocalizerDesigner : ComponentDesigner { public override void Initialize(IComponent c) { base.Initialize(c); var dh = (IDesignerHost)GetService(typeof(IDesignerHost)); if (dh == null) return; var innerListProperty = dh.Container.Components.GetType().GetProperty("InnerList", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); var innerList = innerListProperty.GetValue(dh.Container.Components, null) as System.Collections.ArrayList; if (innerList == null) return; if (innerList.IndexOf(c) <= 1) return; innerList.Remove(c); innerList.Insert(0, c); } } }
我是 Visual Studio 開發人員本地化工具的作者(為了全面披露)。我強烈建議獲取 Guy Smith-Ferrier 的書“.NET Internationalization, The Developer’s Guide to Building Global Windows and Web Applications”的副本。無論如何,我相信那是正確的書(肯定是正確的作者),但是您需要檢查一下,因為我已經很久沒有看過它了(也許從那以後他甚至寫了一些新的東西)。Guy 是 MSFT MVP 和本地化專家。他向您展示瞭如何準確地完成您正在嘗試的事情,在他的例子中,通過創建一個您可以拖動到每個表單的托盤區域的組件。然後該組件將允許您替換“ComponentResourceManager” 用你自己的(他的設計中有幾個類)。然後,您可以從任何來源(包括數據庫)讀取您的字元串。IIRC,他自己的例子甚至使用了數據庫。您可能無需購買他的書就可以線上找到程式碼,因為我認為他甚至可以在自己的網站上提供程式碼。您還可以在著名的購書網站上找到他的書中的免費段落,因為即使您不使用他的技術(在其他地方很難找到),這些資訊也是無價的。請注意,我曾經(很久以前)測試過他的程式碼,並且它像宣傳的那樣工作。您還可以在著名的購書網站上找到他的書中的免費段落,因為即使您不使用他的技術(在其他地方很難找到),這些資訊也是無價的。請注意,我曾經(很久以前)測試過他的程式碼,並且它像宣傳的那樣工作。您還可以在著名的購書網站上找到他的書中的免費段落,因為即使您不使用他的技術(在其他地方很難找到),這些資訊也是無價的。請注意,我曾經(很久以前)測試過他的程式碼,並且它像宣傳的那樣工作。