在WinForms中以可本地化的forms在运行时更改CurrentUICulture

我一直在搜索如何更改Localizable属性设置为true的Form的语言。

https://msdn.microsoft.com/en-us/library/system.threading.thread.currentuiculture(v=vs.110).aspx

这是设置表单的语言,但这需要在我们实例化表单之前设置。 此事件后无法调用此方法。

搜索信息,我看到了以下问题: https : //stackoverflow.com/a/11738932/3286975但是,正如评论所说,我在TabControl和MenuStrip中有控件,因此它们不受影响。

我试图通过获取表单的所有控件而没有运气来修改它。

在此处输入图像描述

在此处输入图像描述

在此菜单中,我调用以下回调:

  private void englishToolStripMenuItem_Click_1(object sender, EventArgs e) { string lang = (string) ((ToolStripMenuItem) sender).Tag; base.Culture = CultureInfo.CreateSpecificCulture(lang); } private void spanishToolStripMenuItem_Click(object sender, EventArgs e) { string lang = (string) ((ToolStripMenuItem) sender).Tag; base.Culture = CultureInfo.CreateSpecificCulture(lang); } 

我通过使用标签来改变文化。

当我点击它没有任何反应。 另外,我从上述答案中修改了一点ApplyResources方法。

 private void ApplyResources(Control parent, CultureInfo culture) { this.resManager.ApplyResources(parent, parent.Name, culture); foreach (Control ctl in parent.IterateAllChildren()) { //this.ApplyResources(ctl, culture); this.resManager.ApplyResources(ctl, ctl.Name, culture); } } 

IterateAllChildren的地址如下: https ://stackoverflow.com/a/16725020/3286975

另外,我试过(System.LINQ): Controls.OfType (因为我有一个Label来测试这个)没有运气……

在此处输入图像描述

在此处输入图像描述

但是当我选择西class牙语时,没有文字被改变。

所以,也许,我对孩子们很满意。 或者也许通过调用CreateCulture方法,我不知道。

提前致谢!

编辑:

我已通过Culture Info测试获取表单的资源管理器,并且每次都返回默认值:

  ResourceSet resourceSet = new ResourceManager(typeof(frmCredentials)).GetResourceSet(new CultureInfo(lang), true, true); foreach (DictionaryEntry entry in resourceSet) { string resourceKey = entry.Key.ToString(); object resource = entry.Value; //resourceSet.GetString(resourceKey); if (resource.GetType().Equals(typeof(string))) Console.WriteLine("Key: {0}\nValue: {1}\n\n", resourceKey, (string) resource); } 

new CultureInfo(lang) ,我也测试了: new CultureInfo("es")Thread.CurrentThread.CurrentCulture (CurrentUICulture)没有运气。 就像它永远不存在或被替换,但在我的设计和文件浏览器中,我可以看到文件。

EDIT2:

也许是因为我正在使用ILMerge将所有dll合并为一个独特的dll。 我正在审查这个问题: 单组装多语言Windows Forms部署(ILMerge和附属程序集/本地化) – 可能吗?

回复EDIT2:

是的,删除ILMerge问题解决了,我给出的第一个解决方案解决了这个问题。 但由于某种原因,西class牙语被视为默认语言,当我试图从中获取资源集时,它并没有给我任何回报。

麻烦,我已将Localizable属性设置为false,并且它没有创建带有值的默认resx文件。 我不知道这是不是一个好习惯。

我会尝试新的东西……

MVVM(模型 – 视图 – ViewModel)方法有一些好处,可以在您的情况下使用。

为将用于本地化的语言创建新的资源文件。 使用表单自己的资源文件可能很小,因为每次在设计器中进行更改时它都会重新生成 – 所以我认为自己的资源文件将更容易维护,甚至可以与其他表单甚至项目共享。

 LocalizationValues.resx // (default english), set Access Modifier to "Internal" or "Public" "Title": "Title" "Description": "Description" LocalizationValues.es.resx "Title": "Título" "Description": "Descripción" 

Visual Studio生成静态类LocalizationValues ,其属性为.resx文件的键。 因此“Title”可以作为LocalizationValues.Title访问

创建“viewmodel”类,它表示您在本地化中使用的所有文本。 类应该实现INotifyPropertyChanged接口。

 public class LocalizationViewModel : INotifyPropertyChanged { public string Title { get { return LocalizationValues.Title; } } public string Description { get { return LocalizationValues.Description; } } public void SetLanguage(string language) { var culture = new CultureInfo(language); Thread.CurrentThread.CurrentUICulture = culture; // This is important, // By raising PropertyChanged you notify Form to update bounded controls NotifyAllPropertyChanged(); } public event PropertyChangedEventHandler PropertyChanged; protected void NotifyAllPropertyChanged() { // Passing empty string as propertyName // will indicate that all properties of viewmodel have changed NotifyPropertyChanged(string.Empty); } protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } 

然后在Form中将viewmodel绑定到控件

 public partial class YourForm : Form { private LocalizationViewModel _viewmodel; public YourForm() { InitializeComponent(); _viewmodel = new LocalizationViewModel(); // Bound controls to correspondent viewmodel's properties LblTitle.DataBindings.Add("Text", _viewmodel, "Title", true); LblDescription.DataBindings.Add("Text", _viewmodel, "Description", true); } // Menu buttons to change language private void SpanishToolStripMenuItem_Click(object sender, EventArgs e) { _viewmodel.SetLanguage("es"); } private void EnglishToolStripMenuItem_Click(object sender, EventArgs e) { _viewmodel.SetLanguage("en"); } } 

上面的方法将提供更多的好处,然后只更新控件。 您可以清楚地分离应用程序的各个部分,这些部分可以相互独立地进行测试。

有不同的解决方案可以解决这个问题,包括其他答案中提到的MVVM。 但你也可以考虑其他一些选择。

在所有控件上调用ApplyResource

您可以在所有控件上设置当前线程的currunt UI文化和调用ApplyResource 。 为此,您需要创建一个方法来返回所有控件,然后在所有控件上调用ApplyResource ,例如:

 private void englishToolStripMenuItem_Click(object sender, EventArgs e) { SetCulture("en-US"); } private void persianToolStripMenuItem_Click(object sender, EventArgs e) { SetCulture("fa-IR"); } public void SetCulture(string cultureName) { System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(cultureName); var resources = new System.ComponentModel.ComponentResourceManager(this.GetType()); GetChildren(this).ToList().ForEach(c => { resources.ApplyResources(c, c.Name); }); } public IEnumerable GetChildren(Control control) { var controls = control.Controls.Cast(); return controls.SelectMany(ctrl => GetChildren(ctrl)).Concat(controls); } 

创建文本本地化扩展程序组件

您还可以创建可在设计时和运行时使用的扩展程序组件,并将一些资源文件和资源键分配给控件。 这样,您可以通过更改当前线程的当前UI文化来简单地在语言之间切换。 只是看一个想法的例子,看看这篇文章:

  • 如何在设计时从资源文件中将文本设置为控件?

经过一些尝试,我意识到有几件事情都失败了。

我还要说,非常感谢此问题中的所有给定帮助,并且LocalizedForm代码段非常有用。

第一个问题是我已经意识到使用此解决方案的子层次结构中的控制级别超过1的控件不起作用。

迭代所有控件是一项昂贵的任务。 也许我的最差但迭代次数较少(因为我只使用文本搜索控件)

  ///  /// Current culture of this form ///  [Browsable(false)] [Description("Current culture of this form")] [EditorBrowsable(EditorBrowsableState.Never)] public CultureInfo Culture { get { return this.culture; } set { if (this.culture != value) { ResourceSet resourceSet = new ComponentResourceManager(GetType()).GetResourceSet(value, true, true); IEnumerable entries = resourceSet .Cast() .Where(x => x.Key.ToString().Contains(".Text")) .Select(x => { x.Key = x.Key.ToString().Replace(">", "").Split('.')[0]; return x; }); foreach (DictionaryEntry entry in entries) { if (!entry.Value.GetType().Equals(typeof(string))) return; string Key = entry.Key.ToString(), Value = (string) entry.Value; try { Control c = Controls.Find(Key, true).SingleOrDefault(); c.Text = Value; } catch { Console.WriteLine("Control {0} is null in form {1}!", Key, GetType().Name); } } this.culture = value; this.OnCultureChanged(); } } } 

我做的是以下,首先,我搜索ResourceManager ,小心! 因为这是第二个问题,如果你在某些情况下使用CultureInfo.CreateSpecificCulture而不是CultureInfo.GetCultureInfo ,将创建一个新文化并返回默认值( Form1.resx值而不是Form1.es.resx值(例如))。

一旦我们从resx文件加载了所有的值,我们迭代所有这些值,然后我们删除double >>(在某些情况下会出现),我们得到那些只声明了Text属性的 控件的名称。

下一步是找到Control并替换它的Text …

好吧,我对派生类有点乱,这就是为什么我创建了一个try-catch系统 ,因为, Controls.Find在所有派生类中搜索我更喜欢更具体一些,但我不知道如何…(这就是我创建这个问题的原因)

有了这个,我们不能保存任何对象,因为我们不会清除并重新创建它们。

这里的主要问题不是我这样做的方式,因为它是正确的。 问题是,当你调用例如这样时, Assemblies合并会做一些奇怪的事情

ResourceSet resourceSet = new ComponentResourceManager(GetType()).GetResourceSet(value, true, true);

value将是默认值…类似于此文化不存在 ,并且因为, 合并的程序集无法找到resx文件。

所以,我将尝试@ScottChamberlain向我建议的AppDomain.CurrentDomain.AssemblyResolve 。 或者ILRepack也许。

任何帮助优化或想法(在评论中)为什么这不起作用将不胜感激!