在调用ApplyResources之后重新应用动态添加的UserControl的布局

在WinForms应用程序中, Panel用作占位符以将单个用户控件显示为导航策略:每当用户希望导航到给定区域时,相应的用户控件将添加到Panel。 简化:

 contentPanel.Controls.Clear(); userControl.Dock = DockStyle.Fill; contentPanel.Controls.Add(userControl); 

由于要求不受我的控制,表单必须支持动态切换语言。 这是使用Hans Passant的答案实现并正常工作,并修改使用用户控件的资源管理器,该管理器正确获取并将本地化文本应用于控件。

但是,在从User Control的相应资源文件中应用资源之后, DockStyle.Fill导致的布局将丢失,因为User Control的组成控件本身并未设置为具有DockStyle.Fill 。 这会导致控件不再拉伸以填充可用区域,并且仅限于设计器/资源文件中定义的原始大小。 请注意,在应用资源后,User Control的Dock属性仍然正确设置为DockStyle.Fill

我创建了一个示例应用程序来说明/重现问题:下面的表单有一个面板,动态添加用户控件并设置为DockStyle.Fill 。 用户控件的标签位于默认语言环境的左上角和德语语言环境的右上角。 我希望表单捕捉标签,该标签固定在表单右边缘的右侧,但用户控件的大小会重置为设计时的值。 查看源代码

如果我在德语区域设置上启动表单,则标签正确地布置在表单的右边缘:

在此处输入图像描述

我想要发生的是调用ApplyResources后保留布局。 当然,我可以简单地制作控件的LocationSize属性的副本(如上面提到的相同问题的另一个答案中所建议的),但不幸的是,这些属性的值在区域设置之间是不同的。 因此,在应用本地化字符串和定位之后,如何指导用户控件重新布局其所有控件?

我试过的

  • 通过查看InitializeComponent() ,我尝试调用PerformLayout()Panel容器,用户控件和表单无济于事。
  • 在调用ApplyResources之前和之后添加SuspendLayout()ResumeLayout(true) ,也没有成功。

其他实施细节

  • 对实例化用户控件的引用保存在主窗体的私有字典中。 引发该控件的导航时,将删除先前的用户控件,并使用上面的代码段添加现有引用。
  • 对用户更改语言的事件做出反应:

     protected virtual void OnChangeCulture(CultureInfo newCulture) { System.Threading.Thread.CurrentThread.CurrentCulture = newCulture; System.Threading.Thread.CurrentThread.CurrentUICulture = newCulture; SuspendLayout(); ComponentResourceManager resources = new ComponentResourceManager(this.GetType()); ApplyResources(resources, this, newCulture); ResumeLayout(true); } 
  • 将资源应用于表单中的所有控件:

     private void ApplyResources(ComponentResourceManager resourceMgr, Component target, CultureInfo culture) { //Since target can be a Control or a Component, get their name and children (OMITTED) in order to apply the resources and recurse string name; IEnumerable children; //Have the resource manager apply the resources to the given target resourceMgr.ApplyResources(target, name, culture); //iterate through the collection of children and recursively apply resources foreach (Component c in children) { //In the case of user controls, they have their own ResourceManager with the translated strings, so get it and use it instead if (c is UserControl) resourceMgr = new ComponentResourceManager(c.GetType()); //recursively apply resources to the child this.ApplyResources(resourceMgr, c, culture); } } 

非常感谢任何指针!

我可以建议以下自定义扩展方法:

 using System.ComponentModel; using System.Globalization; namespace System.Windows.Forms { public static partial class Extensions { public static void ApplyResources(this Control target, CultureInfo culture = null) { ApplyResources(new ComponentResourceManager(target.GetType()), target, "$this", culture); } static void ApplyResources(ComponentResourceManager resourceManager, Control target, string name, CultureInfo culture = null) { // Preserve and reset Dock property var dock = target.Dock; target.Dock = DockStyle.None; // Reset Anchor property target.Anchor = AnchorStyles.Top | AnchorStyles.Left; // Have the resource manager apply the resources to the given target resourceManager.ApplyResources(target, name, culture); // Iterate through the collection of children and recursively apply resources foreach (Control child in target.Controls) { if (child is UserControl) ApplyResources(child, culture); else ApplyResources(resourceManager, child, child.Name, culture); } // Restore Dock property target.Dock = dock; } } } 

基本的变化是两个。

首先,由于存储的位置/大小是相对于容器设计大小(在停靠之前),我们保留Dock属性,在对控件及其子控件的ApplyResources调用期间将其重置为None ,最后将其恢复为当前值。

这基本上解决了右锚的问题。 但是,由于Windows窗体设计器不保存具有默认值的属性值,因此Anchor属性的默认值为AnchorStyles.Top | AnchorStyles.Left AnchorStyles.Top | AnchorStyles.Left ,它没有存储,因此设置不正确(当你的样本从德语到英语时)。

因此,第二个修复是在ApplyResources调用之前将其重置为默认值。

用法很简单:

 protected virtual void OnChangeCulture(CultureInfo newCulture) { System.Threading.Thread.CurrentThread.CurrentCulture = newCulture; System.Threading.Thread.CurrentThread.CurrentUICulture = newCulture; SuspendLayout(); this.ApplyResources(); // <-- ResumeLayout(true); } 

请注意, SuspendLayoutResumeLayout调用不是必需的 - 它可以使用或不使用它们。 它们用于最终防止闪烁,这在您的示例中不会发生。