运行时更改语言的正确方法

23

运行时更改表单语言的正确方法是什么?

  1. 手动递归地设置所有控件,例如这样
  2. 将语言选项保存到文件中 > 重新启动应用程序 > 在 InitializeComponent(); 之前加载语言选项
  3. 使用表单构造函数替换活动表单实例(如果可能的话)
  4. 其他方法

有许多关于此主题的未完成的线程,但没有提供真正的答案来说明什么是正确的做法。

更新:
为了澄清我的问题:

像这样做:

public Form1()
{
    Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
    this.InitializeComponent();
}

正常工作,所有的控件和资源中的其他内容都可以正确翻译。

像这样做:

private void button1_Click(object sender, EventArgs e)
{
    Thread.CurrentThread.CurrentUICulture = new CultureInfo("en");
}

不起作用,表单仍保留在之前我设置的语言。InitializeComponent();


可能是重复的问题:如何在运行时更改WinForms应用程序的文化 - Hans Passant
3
@Hans 这不是重复问题,我的问题是:正确的做法是什么,你提供的链接只是提供了快速修复而不是真正的答案。 - formatc
1
一个“快速修复”有什么问题?大多数程序员更喜欢它们而不是完全重新设计框架。我猜你认为这应该是Winforms的内置功能。但事实并非如此,只有应用“快速修复”的能力才是一项设计特性。 - Hans Passant
1
@Hans 这是我的工作申请,所以我想知道“正确”的方法。答案中的代码是半功能性的,因为它没有涵盖 ListView 的 ColumnHeader.Text 属性,这是由于框架中的错误导致返回 ColumnHeader.Name 为空,所以它有点像框架的重新设计。而且它并没有回答我的问题,即什么是正确的方法。 - formatc
再次强调,没有适当的方法,这不是内置功能。如果您实际上在列标题方面遇到问题,那么您在问题描述方面做得相当糟糕。如果这是一份工作申请,那么当然您应该说“这是不可能的,需要一个丑陋的快速解决方案”。 - Hans Passant
@user1010609:我没有本地化ListView的ColumnHeaders的问题。 - mnn
5个回答

27

我认为Hans Passant在评论中提供的解决方案可能是唯一的(通用)解决方案。

就我个人而言,我使用这个基类来处理所有需要本地化的表单:

public class LocalizedForm : Form
{
    /// <summary>
    /// Occurs when current UI culture is changed
    /// </summary>
    [Browsable(true)]
    [Description("Occurs when current UI culture is changed")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    [Category("Property Changed")]
    public event EventHandler CultureChanged;

    protected CultureInfo culture;
    protected ComponentResourceManager resManager;

    /// <summary>
    /// Current culture of this form
    /// </summary>
    [Browsable(false)]
    [Description("Current culture of this form")]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public CultureInfo Culture
    {
        get { return this.culture; }
        set
        {
            if (this.culture != value)
            {
                this.ApplyResources(this, value);

                this.culture = value;
                this.OnCultureChanged();
            }
        }
    }

    public LocalizedForm()
    {
        this.resManager = new ComponentResourceManager(this.GetType());
        this.culture = CultureInfo.CurrentUICulture;
    }

    private void ApplyResources(Control parent, CultureInfo culture)
    {
        this.resManager.ApplyResources(parent, parent.Name, culture);

        foreach (Control ctl in parent.Controls)
        {
            this.ApplyResources(ctl, culture);
        }
    }

    protected void OnCultureChanged()
    {
        var temp = this.CultureChanged;
        if (temp != null)
            temp(this, EventArgs.Empty);
    }
}

那么,我不直接更改 Thread.CurrentThread.CurrentUICulture,而是使用静态管理器类中的属性来更改 UI 文化:

public static CultureInfo GlobalUICulture
{
    get { return Thread.CurrentThread.CurrentUICulture; }
    set
    {
        if (GlobalUICulture.Equals(value) == false)
        {
            foreach (var form in Application.OpenForms.OfType<LocalizedForm>())
            {
                form.Culture = value;
            }

            Thread.CurrentThread.CurrentUICulture = value;
        }
    }
}

15
请注意,如果您仅在“parent.Controls”上循环,那么您并没有完全遍历控件树。例如,TabControl在“parent.TabPages”中保存其选项卡页,而MenuStrip在“parent.Items”中保存其菜单项,其他控件也有类似的情况。请谨慎处理。 - Chrigl
使用上述代码,如何本地化MenuStrip或ToolStrip? - Tiago Freitas Leal
在TabControl中,每个选项卡都有自己的名称。因此,如果它是“tabPage4”,那么键就是“tabPage4.Text”。 - Park JongBum
TabControls有它们自己的控件树。如果你查看源代码,添加TabPages实际上只是向控件集合中添加一个控件。 - Zach Pedigo

9
我发现另一种方法:
将初始化表单的代码移动到一个私有方法中,如下所示:
private void FormInitialize() {/*Your code here*/}

在表单构造函数中,使用如下方式:
public Form1()
{
    InitializeComponent();
    FormInitialize();
}

您可以通过按钮、菜单项或其他方式调用方法,例如:

private void ChangeCultureToFrench_Click(object sender, EventArgs e)
{
    Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr");
    this.Controls.Clear();
    this.InitializeComponent();
    FormInitialize();
}

我希望这可以有所帮助;-)

3

我几分钟前发现了这种方法。只需快速简单地重启主表单。也许对某些人有帮助。事件是在我的表单内部创建的,当用户从菜单中选择语言后调用,但在所选文化名称保存到设置后。然后从该设置加载文化名称。正如我所需要的那样工作,并且看起来是正确的解决方案。

static class Program
{
    private static bool doNotExit = true;
    private static FormMain fm;
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {


        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        while(doNotExit)
        {
            System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(Properties.Settings.Default.language);//
            System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(Properties.Settings.Default.language);//

            doNotExit = false;
            fm = new FormMain();
            fm.lanugageChangedEvent += new EventHandler(main_LanugageChangedEvent);
            Application.Run(fm);
        }
    }



    static void main_LanugageChangedEvent(object sender, EventArgs e)
    {  
        doNotExit = true;
        fm.Close();   
    }
}

0
关于您提到的ColumnHeader .NET框架的错误,我最近也发现了这个问题,并发布了一个相关问题(但是没有收到任何回复)。我通过硬编码ColumnHeaders的更改来解决了这个问题。例如:
resources.ApplyResources(_myHeader, "_myHeader", culture);

你基本上只需将对.Name的调用替换为名称的文字字符串。我已经测试过它可以工作。不幸的是,这意味着它不能作为更改所有控件所使用的代码的一部分。您将需要为每个需要更改的ColumnHeader对象添加一行。如果您有一个具有可变列数的列表视图,那可能会变得棘手。另一个选择是创建本地化资源文件。我假设您可能已经为诸如消息框文本和其他字符串之类的内容拥有了它们。然后,您可以在资源文件中添加一个条目,例如“columnHeader_myHeader”,并为每种语言设置适当的语言文本。最后,您可以使用以下方式手动更改列标题的文本:
_myHeader.Text = myResourceFileName.columnHeader_myHeader;

这将根据当前线程文化选择适当的语言。 Hans是正确的,.NET中似乎没有绝对可靠的“正确”本地化方法,但有各种工具可供使用。对于像工作申请这样的东西,即使现在可能已经太晚了,我的建议是尽可能学习多种不同的本地化方法,了解其优缺点,然后选择一个系统并能够说明为什么您认为它是“正确”的选择。他们可能更关心您的逻辑和推理以及先前经验的演示,而不是实际方法。


0

希望这能帮到任何人,我发现这对我来说是最好的,因为我需要根据语言更改按钮位置(在搜索框的右侧或左侧以及标签旁边的文本字段)。

  1. 在主要部分保存一个公共变量,用于保存语言。
  2. 创建一个处理可视化部分的类
  3. 创建将保存任何语言数据的xml文件(在我的xml标记名称=对象名称中)。
  4. 将该类的构造函数发送到表单(以保存和处理)
  5. 连接到当前的xml文件

从主窗体调用initialView(视图类的一部分),并随时更改语言(只需连接到正确的xml文件):

public void initialView()
{
    //Set rightToLeft values
    initialIndent(mainForm);

    //set visual text values
    initialTxt();
}

private void initialTxt()
{
    // Are there any more controls under mainObj (Form1)?
    Boolean endOfElemsUnderPnl = false;

    // The current Control im working on
    Object curObj = mainForm;

    do
    {
        // MenuStrip needs to be handled separately
        if (typeof(MenuStrip).ToString().Equals(curObj.GetType().ToString()))
        {
            foreach (ToolStripMenuItem miBase in ((MenuStrip)(curObj)).Items)
            {
                miBase.Text = mainForm.dbCon.getData(miBase.Name.ToString());
                foreach (ToolStripMenuItem miInnerNode in miBase.DropDownItems)
                {
                    miInnerNode.Text = mainForm.dbCon.getData(miInnerNode.Name.ToString());
                }
            }
        }

        // Any other Control i have on the form
        else
        {
            ((Control)(curObj)).Text = mainForm.dbCon.getData(((Control)(curObj)).Name.ToString());
        }

        curObj = mainForm.GetNextControl(((Control)(curObj)), true);

        // Are there any more controls under mainObj?
        if (null == curObj)
        {
            endOfElemsUnderPnl = true;
        }

    } while (!endOfElemsUnderPnl);
}

private void initialIndent(frmMyFileManager parent)
{
    if (parent.Language.Equals("Hebrew"))
    {
        parent.RightToLeft = RightToLeft.Yes;
    }
    else if (parent.Language.Equals("English"))
    {
        parent.RightToLeft = RightToLeft.No;
    }
    else
    {
        parent.RightToLeft = RightToLeft.No;
    }
}

这是一个示例,展示了在运行时我有多么容易:

private void selectLanguageToolStripMenuItem_Click(object sender, EventArgs e)
{
    DialogResult res = MessageBox.Show(this, "click yes for english and no for hebrew", "Select language", MessageBoxButtons.YesNoCancel);

    if (DialogResult.Yes == res)
    {
        Language = "English";
    }
    else if (DialogResult.No == res)
    {
        Language = "Hebrew";
    }
    dbCon = new CDBConnector("****\\lang" + Language + ".xml");
    view.initialView();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接