本文介绍了在调用ApplyResources之后重新应用动态添加的UserControl的布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

由于我无法控制某个要求,因此表单必须支持动态切换语言。可以使用



如果我在在德语区域设置中,标签正确放置在表单的右边缘:





我想发生的是在调用 ApplyResources 之后保留了布局。当然,我可以简单地复制控件的 Location Size 属性(如),但不幸的是,这些属性的值在语言环境之间有所不同。因此,在应用本地化的字符串和位置之后,如何指导用户控件重新布局其所有控件?



我尝试过的操作




  • 通过查看 InitializeComponent(),我尝试调用 PerformLayout() Panel 容器,用户控件和窗体无用。

  • 在调用ApplyResources之前和之后添加 SuspendLayout() ResumeLayout(true)成功。



其他实施细节




  • 对实例化用户控件的引用保存在主窗体的专用字典中。对该控件进行导航时,将删除先前的用户控件,并在上面的代码段中添加现有参考。

  • 对更改语言的用户事件做出反应:

     受保护的虚拟void OnChangeCulture(CultureInfo newCulture)
    {
    System.Threading.Thread.CurrentThread.CurrentCulture = newCulture ;
    System.Threading.Thread.CurrentThread.CurrentUICulture = newCulture;

    SuspendLayout();
    ComponentResourceManager资源=新的ComponentResourceManager(this.GetType());
    ApplyResources(资源,this,newCulture);
    ResumeLayout(true);
    }


  • 将资源应用于以下形式的所有控件:

      private void ApplyResources(ComponentResourceManager resourceMgr,Component target,CultureInfo文化)
    {
    //由于目标可以是控件或组件,获取其名称和子级(省略)以应用资源并递归
    字符串名称;
    IEnumerable< Component>孩子们

    //已使资源管理器将资源应用于给定的目标
    resourceMgr.ApplyResources(target,name,culture);

    //遍历子级集合并递归应用资源
    foreach(子级c)
    {
    //对于用户控件,它们有自己的ResourceManager和翻译后的字符串,因此如果(c是UserControl)获取并使用它,则使用

    resourceMgr = new ComponentResourceManager(c.GetType());

    //将资源递归地应用到孩子
    this.ApplyResources(resourceMgr,c,culture);
    }
    }




非常感谢您提供任何指针!

解决方案

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

 使用System.ComponentModel; 
使用System.Globalization;

命名空间System.Windows.Forms
{
公共静态局部类扩展
{
公共静态void ApplyResources(此控制目标,CultureInfo文化= null )
{
ApplyResources(new ComponentResourceManager(target.GetType()),目标, $ this,区域性);
}

静态void ApplyResources(ComponentResourceManager resourceManager,控制目标,字符串名称,CultureInfo文化= null)
{
//保存并重置Dock属性
var dock = target.Dock;
target.Dock = DockStyle.None;
//重置锚属性
target.Anchor = AnchorStyles.Top | AnchorStyles.Left;
//让资源管理器将资源应用于给定的目标
resourceManager.ApplyResources(target,name,culture);
//遍历子项的集合并递归地应用资源
foreach(target.Controls中的控制子项)
{
if(子项为UserControl)
ApplyResources(儿童,文化);
else
ApplyResources(resourceManager,child,child.Name,culture);
}
//恢复Dock属性
target。
}
}
}

基本更改是两个。



首先,由于存储的位置/大小与容器设计大小有关(在停靠之前),因此我们保留了 Dock 属性,在对控件及其子元素的 ApplyResources 调用期间将其重置为 None 到当前值。



这基本上可以解决带有右锚点的问题。但是,由于Windows窗体设计器不会保存具有默认值的属性值,并且 Anchor 属性的默认值为 AnchorStyles。 AnchorStyles.Left ,它没有存储,因此设置不正确(在示例中从德语转换为英语时)。



因此,第二个解决方法是在 ApplyResources 调用之前简单地将其重置为默认值。



用法很简单:

 受保护的虚拟空隙OnChangeCulture(CultureInfo newCulture)
{
System.Threading.Thread.CurrentThread.CurrentCulture = newCulture;
System.Threading.Thread.CurrentThread.CurrentUICulture = newCulture;

SuspendLayout();
this.ApplyResources(); //<-
ResumeLayout(true);
}

请注意, SuspendLayout ResumeLayout 调用不是必不可少的-无论有没有它们都可以使用。它们用于最终防止闪烁,这种情况在您的示例中不会发生。


In a WinForms application, a Panel is used as a placeholder to display a single User Control as a navigation strategy: whenever the user wishes to navigate to a given area, the respective User Control is added to the Panel. Simplified:

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

As a result of a requirement that is out of my control, the Form must support switching the language dynamically. This is implemented and working fine using Hans Passant's answer, with a modification to use the User Control's Resource Manager, which correctly gets and applies the localized text to controls.

After applying the resources from the User Control's respective resource file, however, the layout resulting from DockStyle.Fill is lost for the User Control's constituent controls that are not themselves set to have a DockStyle.Fill. This has the effect that controls no longer stretch to fill the available area, and are limited to the original size defined in the designer/resource file. Note that the Dock property of the User Control is still set correctly to DockStyle.Fill after applying the resources.

I created an example application which illustrates/reproduces the problem: the form below has a panel to which a user control is added dynamically and set to DockStyle.Fill. The user control has a label which is anchored top left on the Default locale and top right in the German locale. I would expect the form to snap the label which is anchored to the right against the right margin of the form, but the size of the user control is reset to the value at design time. View source code.

If I start the form on the German locale, the label is correctly laid out against the right edge of the form:

What I'd like to happen is that the layout is retained after calling ApplyResources. Of course I could simply make a copy of the controls' Location and Size properties (as suggested in another answer to the same question mentioned above) but unfortunately values of these properties differ between locales. So, after the localized string and positioning are applied, how can the User Control be directed to layout all its controls anew?

What I've tried

  • By looking into InitializeComponent(), I've tried calling PerformLayout() to the Panel container, the User Control, and the Form to no avail.
  • Adding SuspendLayout() and ResumeLayout(true) before and after the call to ApplyResources, also without success.

Additional implementation details

  • References to instantiated User Controls are kept in a private dictionary in the Main Form. When navigation for that control is raised, the previous user control is removed and the existing reference added with the snippet above.
  • Reacting to the user event of changing the language:

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

  • Applying the resources to all controls in the form:

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

Many thanks in advance for any pointers!

解决方案

I can suggest the following custom extension method:

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

The essential changes are two.

First, since the stored location/sizes are relative to the container design size (before being docked), we preserve the Dock property, reset it to None during the ApplyResources calls to the control and its children, and finally restore it to the current value.

This basically resolves the issue with right anchor. However, since Windows Forms designer doesn't save property values having default values, and the default value for Anchor property is AnchorStyles.Top | AnchorStyles.Left, it's not stored and hence is not set correctly (when going from German to English in your sample).

So the second fix is to simply reset it to its default value before ApplyResources call.

The usage is simple:

protected virtual void OnChangeCulture(CultureInfo newCulture)
{
    System.Threading.Thread.CurrentThread.CurrentCulture = newCulture;
    System.Threading.Thread.CurrentThread.CurrentUICulture = newCulture;

    SuspendLayout();
    this.ApplyResources(); // <--
    ResumeLayout(true);
}

Note that SuspendLayout and ResumeLayout calls are not essential - it works with or without them. They are used to eventually prevent flickering, which doesn't happen in your example.

这篇关于在调用ApplyResources之后重新应用动态添加的UserControl的布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

11-01 13:51