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

use*_*732 6 c# .net-3.5 winforms

在一个WinForms应用程序,一个Panel被用作占位符来显示单个用户控制的导航策略:每当用户希望导航到给定的区域,相应的用户控制被添加到面板.简化:

contentPanel.Controls.Clear();
userControl.Dock = DockStyle.Fill;
contentPanel.Controls.Add(userControl);
Run Code Online (Sandbox Code Playgroud)

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

但是,在应用来自用户控件的相应资源文件的资源之后,DockStyle.Fill由于用户控件的组成控件本身没有设置,因此导致的布局丢失DockStyle.Fill.这具有控制不再拉伸以填充可用区域的效果,并且限于设计器/资源文件中定义的原始大小.请注意,在应用资源后,Dock仍然可以正确设置用户控件的属性DockStyle.Fill.

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

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

在此输入图像描述

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

我试过的

  • 通过观察InitializeComponent(),我已经打过电话PerformLayout()Panel容器,用户控件和表格都无济于事.
  • 添加SuspendLayout()ResumeLayout(true)前后调用ApplyResources后,也没有成功.

其他实施细节

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

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 将资源应用于表单中的所有控件:

    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<Component> 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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

非常感谢任何指针!

Iva*_*oev 2

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

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最重要的变化有两个。

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

这基本上解决了右锚的问题。但是,由于 Windows 窗体设计器不保存具有默认值的属性值,并且属性的默认值为AnchorAnchorStyles.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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,SuspendLayoutResumeLayout调用并不是必需的 - 无论有没有它们,它都可以工作。它们最终用于防止闪烁,这在您的示例中不会发生。