【问题标题】:.NET WinForms localization - replacing ComponentResourceManager.NET WinForms 本地化 - 替换 ComponentResourceManager
【发布时间】:2013-03-05 11:22:56
【问题描述】:

在我当前的项目(.NET Windows 窗体应用程序)中,我要求 .NET 窗体应该本地化,但本地化元素(只是翻译,而不是图像或控件位置)应该按顺序来自数据库使最终用户能够根据需要修改控件的可本地化属性(只是标题/文本)。 为了让开发人员摆脱本地化问题的负担,对我来说最好的解决方案是在 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));

然后我可以毫无问题地解决其他所有问题。

不幸的是,我没有找到如何做这样的事情,所以我在这里问一个关于如何做的问题。

附: 我也会接受任何其他符合要求的本地化解决方案: 1. 无需重新部署应用程序就可以更改翻译 2. 开发者在创建表单/用户控件时不应该关心翻译

谢谢。

编辑: Larry 提供了一本很好的参考书,其中的代码部分解决了我的问题。借助该帮助,我能够创建自己的组件,该组件替换 InitializeComponent 方法中的默认 ComponentResourceManager。 这是一个名为 Localizer 的组件的代码,它将 ComponentResourceManager 替换为自定义 MyResourceManager 以便其他人也可以从中受益:

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);

        }
    }


}

【问题讨论】:

标签: .net winforms visual-studio-2010 localization resourcemanager


【解决方案1】:

我是面向 Visual Studio 开发人员的本地化工具的作者(为了全面披露)。我强烈建议获取 Guy Smith-Ferrier 的书“.NET Internationalization, The Developer's Guide to Building Global Windows and Web Applications”的副本。无论如何,我相信那是正确的书(肯定是正确的作者),但是您需要检查一下,因为我已经很久没有看过它了(也许从那以后他甚至写了一些新的东西)。 Guy 是 MSFT MVP 和本地化专家。他向您展示了如何准确地完成您正在尝试的事情,在他的案例中,通过创建一个您可以拖动到每个表单的托盘区域的组件。然后该组件将允许您用您自己的替换“ComponentResourceManager”(他的设计中涉及多个类)。然后,您可以从任何来源(包括数据库)读取您的字符串。 IIRC,他自己的例子甚至使用了数据库。您可能无需购买他的书就可以在线找到代码,因为我认为他甚至可以在自己的网站上提供代码。您还可以在著名的购书网站上找到他的书中的免费段落,因为即使您不使用他的技术(在其他地方很难找到),这些信息也是无价的。请注意,我曾经(很久以前)测试过他的代码,并且它像宣传的那样工作。

【讨论】:

  • 非常感谢。我会检查一下并将您的答案标记为正确。
  • @larry 如何替换 winforms 的那个?
  • 太棒了。谢谢。让我去读一整本书以获得快速答案。
  • @Pangamma 谁说你必须阅读整本书。只需找到相关信息。它仍然不会很简单,但是如果您希望“快速”和简单地回答一个固有的晦涩和重要的问题(在现实世界中很少有人熟悉,规避本地化在 Visual Studio 中的正常工作方式),那么祝你好运找到更及时的答案。操作员显然认为这本书是一个“很好的参考”(见他的“编辑”),我同意。
  • @Larry Fair 够了。
【解决方案2】:

我是这样替换的:

public class CustomCodeDomSerializer : CodeDomSerializer
{
    public override object Serialize(IDesignerSerializationManager manager, object value)
    {
        for (var i = 0; manager.Context[i] != null; i++)
        {
            var collection = manager.Context[i] as CodeStatementCollection;
            if (collection != null)
            {
                foreach (var statement in collection)
                {
                    var st = statement as CodeVariableDeclarationStatement;
                    if (st?.Type.BaseType == typeof(ComponentResourceManager).FullName)
                    {
                        var ctr = new CodeTypeReference(typeof(CustomComponentResourceManager));
                        st.Type = ctr;
                        st.InitExpression = new CodeObjectCreateExpression(ctr, new CodeTypeOfExpression(manager.GetName(value)));
                    }
                }
            }
        }
        var baseClassSerializer = (CodeDomSerializer)manager.GetSerializer(value.GetType().BaseType, typeof(CodeDomSerializer));
        var codeObject = baseClassSerializer.Serialize(manager, value);
        return codeObject;
    }
}

[DesignerSerializer(typeof(CustomCodeDomSerializer), typeof(CodeDomSerializer))]
public class CustomUserControl : UserControl { }

那么视图应该继承CustomUserControl而不是UserControl(或CustomForm : Form):

public partial class SomeView : CustomUserControl { ... }

然后生成设计器文件:

private void InitializeComponent()
{
    CustomComponentResourceManager resources = new CustomComponentResourceManager(typeof(SomeView));
    ...
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-30
    • 2015-04-15
    相关资源
    最近更新 更多