简体   繁体   English

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

[英]Rendering Derived User Control in Designer View

I have a UserControl hierarchy that looks something like this: 我有一个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();
        }
    }
}

And in a different file: 并在另一个文件中:

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

This structure is designed so that the BaseClass handles core logic and DerivedClass handles display logic. 设计此结构是为了使BaseClass处理核心逻辑,DerivedClass处理显示逻辑。 The number of Labels needs to be variable (different DerivedClasses will have different num values). 标签的数量需要是可变的(不同的DerivedClasses将具有不同的num值)。

My problem is that I would like the designer view to show the UserControl as it would look after the display adjustments. 我的问题是,我想设计视图来显示用户控件因为它看起来显示调整 There are several problems -- first, if BaseClass lacks a default constructor, then the DerivedClass's designer view just fails. 有几个问题 - 首先,如果BaseClass缺少默认构造函数,那么DerivedClass的设计器视图就会失败。 Even if I add a default constructor, the designer view shows the layout of DerivedClass without the various display changes. 即使我添加了默认构造函数,设计器视图也会显示DerivedClass的布局,而不会显示各种显示。

I'm not interested in using the designer view to change the Controls. 我对使用设计器视图更改控件不感兴趣。 I'm not opposed to it, but the fact that Labels are in an Array seems to prevent the designer view from being able to access them. 我并不反对它,但标签在数组中的事实似乎阻止了设计者视图能够访问它们。 I am simply interested in being able to see the effects of my display layout code in the DerivedClass. 我只是对能够在DerivedClass中看到我的显示布局代码的效果感兴趣。

There appears to be a limitation in the Windows Forms designer that prevents the currently designed class' own constructor from running - only the parent class(es) constructors are fired. Windows窗体设计器中似乎存在一个限制,它阻止当前设计的类自己的构造函数运行 - 只触发父类(即)构造函数。

If I take your example: 如果我举个例子:

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

    }

}

Then, when I look in the designer for Derived Control, I see nothing. 然后,当我在设计师中寻找Derived Control时,我什么也看不见。 However, if I add the following control derived from DerivedControl: 但是,如果我添加从DerivedControl派生的以下控件:

public class GrandchildControl : DerivedControl
{

    public GrandchildControl() : base() { }

}

And, after building my project, look at that in the designer (Visual Studio 2010), I see: 在构建我的项目之后,在设计器(Visual Studio 2010)中查看它,我看到:

GrandchildControl按预期调用DerivedControl构造函数

It appears to be a feature of the designer. 它似乎是设计师的一个特征。 According to this blog post on MSDN blogs (which is quite old) 根据MSDN博客上的这篇博文(相当陈旧)

  1. Form1 must be built before you can add another Form, say Form2, that visually inherits from it. 必须先构建Form1,然后才能添加另一个可视化继承的Form,例如Form2。 This is because the designer for Form2 has to instantiate Form1, not System.Windows.Forms.Form. 这是因为Form2的设计者必须实例化Form1,而不是System.Windows.Forms.Form。 This also explains why if you open Form2 in the designer, attach a debugger to Visual Studio and set a breakpoint in Form1's InitializeComponent, the breakpoint does get hit. 这也解释了为什么如果在设计器中打开Form2,将调试器附加到Visual Studio并在Form1的InitializeComponent中设置断点,断点就会受到影响。

  2. There is a comment above InitializeComponent that warns you against modifying it manually. InitializeComponent上面有一条注释,警告您不要手动修改它。 This is because the designer needs to parse this code, and it has some limitations as to what it can parse. 这是因为设计人员需要解析此代码,并且它可以解析什么有一些限制。 It is generally guaranteed to parse whatever it serialized in there, but not arbitrary code that you may add. 通常保证解析它中序列化的内容,但不能解析您可能添加的任意代码。

  3. If you are manually (through code) adding a control to the form in the constructor or in the Load event handler, the control doesn't show up in the designer. 如果您手动(通过代码)在构造函数或Load事件处理程序中向窗体添加控件,则控件不会显示在设计器中。 This is because the designer doesn't parse that - it only parses InitializeComponent. 这是因为设计者没有解析它 - 它只解析InitializeComponent。

The only way I have ever got this to reliably work is to either move all of my code into a method that get's called by InitializeComponent (and occasionally, you have to remember to stick it back in when it get's "overwritten" by the designer) or to do as I've done above and create a GrandchildUserControl to fake the constructor call of the actual control I'm interested in. 我能够可靠地工作的唯一方法是将我的所有代码移动到一个由InitializeComponent调用的方法中(有时候,你必须记住在被设计者“覆盖”时将其粘贴回来)或者像我上面那样做,并创建一个GrandchildUserControl来伪造我感兴趣的实际控件的构造函数调用。

FWIW I believe this is a consequence of 1) and 3) and is almost certainly a work-around of something that is by design. FWIW我相信这是1)和3)的结果,几乎可以肯定是设计的一种解决方法。

Point 1) also raises a great investigative technique for these situations - you can actually launch another instance of Visual Studio and attach to the first instance of Visual Studio and debug the method calls running there. 第1点还为这些情况提出了一种很好的调查技术 - 您实际上可以启动另一个Visual Studio实例并附加到Visual Studio的第一个实例并调试在那里运行的方法调用。 This has helped me troubleshoot several designer issues in the past. 这帮助我解决了过去的几个设计师问题。

As dash pointed out, the designer create an instance of the base class, then parses the InitializeComponent method and executes its instructions. 正如dash指出的那样,设计者创建基类的实例,然后解析InitializeComponent方法并执行其指令。 This means that there is no simple way to have it execute the code in the derived classes' constructors (you should parse the constructor and execute its instructions). 这意味着没有简单的方法让它在派生类的构造函数中执行代码(您应该解析构造函数并执行其指令)。

You have to be careful that the solution of having the derived class' specific instructions grouped in a method which is called inside InitializeComponent works only if the method is generic enough to be defined in the base class, since the designer is working with an instance of this base class. 你必须要小心,将派生类的特定指令分组在一个在InitializeComponent中调用的方法的解决方案只有在方法足够通用以在基类中定义时才有效,因为设计者正在使用一个实例这个基类。 This means that if the method is declared as virtual in the base class that will be the one that is executed, whilst if there is no such method in the base class the designer will crash. 这意味着如果方法在基类中被声明为虚拟的,那么在基类中,如果基类中没有这样的方法,则设计器将崩溃。

You could do something along these lines. 你可以沿着这些方向做点什么。 Define this method in the base class: 在基类中定义此方法:

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

Then you have to call it in the InitializeComponent of your derived control passing the proper value of num. 然后你必须在派生控件的InitializeComponent中调用它,传递适当的num值。

All of your settings are then to be moved to the InitializeComponent method, too. 然后,您的所有设置也将移动到InitializeComponent方法。 Of course, if they can be generalized, you can write the same kind of method. 当然,如果它们可以推广,你可以编写相同的方法。

The main drawback of this approach is the fact that everything will work until you don't modify your control from the designer, because your InitializeComponent will be messed up. 这种方法的主要缺点是,在您不从设计器修改控件之前一切都会正常工作,因为您的InitializeComponent将被搞砸。 You can control this kind of behaviour by implementing a serializer for your controls. 您可以通过为控件实现序列化程序来控制此类行为。 Ie you have to implement a class BaseControlCodeDomSerializer derived from System.ComponentModel.Design.Serialization.CodeDomSerializer redefining the Serialize method someway like this: 即你必须实现从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;
    }

Finally, you can simply associate the Serializer to the Control with this Attribute: 最后,您只需使用此属性将Serializer与Control关联:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM