在Designer视图中渲染派生用户控件

时间:2012-05-08 14:39:26

标签: c# winforms windows-forms-designer

我有一个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处理显示逻辑。标签的数量需要是可变的(不同的DerivedClasses将具有不同的num值)。

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

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

2 个答案:

答案 0 :(得分: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);

    }

}

然后,当我在设计师中寻找Derived Control时,我什么也看不见。但是,如果我添加从DerivedControl派生的以下控件:

public class GrandchildControl : DerivedControl
{

    public GrandchildControl() : base() { }

}

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

GrandchildControl invokes DerivedControl constructor as expected

它似乎是设计师的一个特征。根据{{​​3}}上的博客文章(这是相当古老的)

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

  2. InitializeComponent上面有一条注释,警告您不要修改 它手动。这是因为设计者需要解析这段代码, 它可以解析什么有一些限制。一般来说 保证解析在那里序列化的任何东西,但不是 您可以添加的任意代码。

  3. 如果您手动(通过代码)在构造函数或Load中向表单添加控件 事件处理程序,控件不会显示在设计器中。这是 因为设计师没有解析它 - 它只解析 的InitializeComponent。

  4. 我唯一能够可靠地工作的方法是将我的所有代码移动到一个被InitializeComponent调用的方法中(有时候,你必须记住在被“覆盖”的时候将其粘贴回来设计师)或者像我上面那样做,并创建一个GrandchildUserControl来伪造我感兴趣的实际控件的构造函数调用。

    FWIW我相信这是1)和3)的结果,几乎可以肯定是设计出来的事情。

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

答案 1 :(得分: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与此属性关联到Control:

[DesignerSerializer("MyControls.BaseControlCodeDomSerializer", typeof(CodeDomSerializer))]