3

我有一个UserControl看起来像这样的层次结构:

public class BaseClass : UserControl
{
    protected Label[] Labels;
    public BaseClass(int num)
    {
        Labels = new Label[num];
        for(int i=0; i<num; i++)
        {
            Labels[i] = new Label();
        }
    }
}

在另一个文件中:

public class DerivedClass : BaseClass
{
    public DerivedClass() : base(2)
    {
        // Do stuff to the location, size, font, text of Labels
    }
}

设计此结构以便 BaseClass 处理核心逻辑,而 DerivedClass 处理显示逻辑。Labels 的数量需要是可变的(不同的 DerivedClass 会有不同的 num 值)。

我的问题是我希望设计器视图显示 UserControl,因为它会照顾显示调整。有几个问题——首先,如果 BaseClass 缺少默认构造函数,那么 DerivedClass 的设计器视图就会失败。即使我添加了默认构造函数,设计器视图也会显示 DerivedClass 的布局,而不会进行各种显示更改。

我对使用设计器视图来更改控件不感兴趣。我并不反对它,但标签位于数组中的事实似乎阻止了设计器视图能够访问它们。我只是对能够在 DerivedClass 中查看我的显示布局代码的效果感兴趣。

4

2 回答 2

3

Windows 窗体设计器中似乎存在一个限制,阻止当前设计的类自己的构造函数运行 - 只有父类构造函数被触发。

如果我以你为例:

public partial class BaseControl : UserControl
{
    public BaseControl()
    {
        InitializeComponent();
    }


    protected Label[] Labels;

    public BaseControl(int num) : base()
    { 
        Labels = new Label[num]; 
        for(int i=0; i<num; i++) 
        { 
            Labels[i] = new Label(); 
        } 
    }

}

public class DerivedControl : BaseControl
{

    public DerivedControl() : base(5)
    {
        Controls.Add(Labels[0]);

        Labels[0].Text = "Hello";
        Labels[0].Location = new System.Drawing.Point(10, 10);

    }

}

然后,当我查看派生控件的设计器时,我什么也没看到。但是,如果我添加从 DerivedControl 派生的以下控件:

public class GrandchildControl : DerivedControl
{

    public GrandchildControl() : base() { }

}

而且,在构建我的项目之后,在设计器(Visual Studio 2010)中查看它,我看到:

GrandchildControl 按预期调用 DerivedControl 构造函数

这似乎是设计师的一个特点。根据MSDN 博客上的这篇博文(这很旧)

  1. 必须先构建 Form1,然后才能添加另一个表单,比如 Form2,它在视觉上继承自它。这是因为 Form2 的设计器必须实例化 Form1,而不是 System.Windows.Forms.Form。这也解释了为什么如果在设计器中打开 Form2,将调试器附加到 Visual Studio 并在 Form1 的 InitializeComponent 中设置断点,断点会被命中。

  2. InitializeComponent 上方有一条注释,警告您不要手动修改它。这是因为设计者需要解析这段代码,并且它对它可以解析的内容有一些限制。通常可以保证解析其中序列化的任何内容,但不能解析您可能添加的任意代码。

  3. 如果您在构造函数或 Load 事件处理程序中手动(通过代码)将控件添加到窗体,则该控件不会显示在设计器中。这是因为设计器不解析它 - 它只解析 InitializeComponent。

我曾经让它可靠地工作的唯一方法是将我的所有代码移动到一个由 InitializeComponent 调用的方法中(有时,当它被设计者“覆盖”时,你必须记住将它放回原处)或者像我上面所做的那样创建一个 GrandchildUserControl 来伪造我感兴趣的实际控件的构造函数调用。

FWIW 我相信这是 1) 和 3) 的结果,并且几乎可以肯定是设计的解决方法。

第 1 点)还针对这些情况提出了一种很好的调查技术 - 您实际上可以启动另一个 Visual Studio 实例并附加到第一个 Visual Studio 实例并调试在那里运行的方法调用。这帮助我解决了过去的几个设计师问题。

于 2012-05-08T16:07:01.357 回答
2

正如 dash 所指出的,设计者创建了一个基类的实例,然后解析 InitializeComponent 方法并执行它的指令。这意味着没有简单的方法让它执行派生类的构造函数中的代码(您应该解析构造函数并执行其指令)。

您必须小心,将派生类的特定指令分组在一个在 InitializeComponent 内部调用的方法中的解决方案仅在该方法足够通用以在基类中定义时才有效,因为设计者正在使用这个基类。这意味着,如果该方法在基类中声明为虚拟方法,则该方法将被执行,而如果基类中没有此类方法,则设计器将崩溃。

你可以按照这些思路做一些事情。在基类中定义这个方法:

    protected void CreateLabels(int num)
    {
        Labels = new Label[num];
        for(int i=0; i<num; i++)
        {
            Labels[i] = new Label();
            this.Controls.Add(Labels[i]);
        }
    }

然后,您必须在派生控件的 InitializeComponent 中调用它,并传递正确的 num 值。

然后,您的所有设置也将移至 InitializeComponent 方法。当然,如果它们可以泛化,你可以编写同一种方法。

这种方法的主要缺点是,在您不从设计器修改控件之前,一切都会正常工作,因为您的 InitializeComponent 将被搞砸。您可以通过为控件实现序列化程序来控制这种行为。即你必须实现一个派生自 System.ComponentModel.Design.Serialization.CodeDomSerializer 的类 BaseControlCodeDomSerializer 重新定义 Serialize 方法,如下所示:

    public override object Serialize(IDesignerSerializationManager manager, object value)
    {
        BaseControl aCtl = value as BaseControl;
        if (aCtl == null)
            return null;
        if (aCtl.Labels == null)
            return null;
        int num = aCtl.Labels.Length;

        CodeStatementCollection stats = new CodeStatementCollection();

        stats.Add(new CodeSnippetExpression("CreateLabels(" + num.ToString() + ")"));

        return stats;
    }

最后,您可以简单地将 Serializer 与具有此属性的控件相关联:

[DesignerSerializer("MyControls.BaseControlCodeDomSerializer", typeof(CodeDomSerializer))]
于 2012-05-15T10:08:46.977 回答