Отображение производного пользовательского элемента управления в режиме конструктора

У меня есть 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 просто не работает. Даже если я добавлю конструктор по умолчанию, представление конструктора показывает макет DerivedClass без различных изменений отображения.

Меня не интересует использование представления конструктора для изменения элементов управления. Я не против этого, но тот факт, что метки находятся в массиве, кажется, не позволяет представлению дизайнера получить к ним доступ. Меня просто интересует возможность увидеть эффекты моего кода макета дисплея в DerivedClass.


person phillipwei    schedule 08.05.2012    source источник


Ответы (2)


Похоже, что в конструкторе Windows Forms есть ограничение, которое препятствует запуску собственного конструктора текущего разработанного класса - запускаются только конструкторы родительского класса (ов).

Если я возьму ваш пример:

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 вызывает конструктор DerivedControl, как и ожидалось

Видимо особенность конструктора. Согласно этому сообщению в блогах MSDN (что довольно старое)

  1. Форма Form1 должна быть построена до того, как вы сможете добавить другую форму, скажем, форму Form2, которая визуально наследуется от нее. Это связано с тем, что конструктор для Form2 должен создавать экземпляр Form1, а не System.Windows.Forms.Form. Это также объясняет, почему, если вы открываете Form2 в конструкторе, подключаете отладчик к Visual Studio и устанавливаете точку останова в InitializeComponent Form1, точка останова срабатывает.

  2. Над InitializeComponent есть комментарий, который предостерегает вас от модификации вручную. Это связано с тем, что разработчику необходимо анализировать этот код, и у него есть некоторые ограничения в отношении того, что он может анализировать. Как правило, гарантированно анализируется все, что он сериализует там, но не произвольный код, который вы можете добавить.

  3. Если вы вручную (с помощью кода) добавляете элемент управления в форму в конструкторе или в обработчике событий Load, элемент управления не отображается в конструкторе. Это связано с тем, что дизайнер не анализирует это — он анализирует только InitializeComponent.

Единственный способ заставить это надежно работать - либо переместить весь мой код в метод, который вызывается InitializeComponent (и иногда вам нужно помнить, чтобы вставить его обратно, когда он "перезаписывается" дизайнером) или сделать то, что я сделал выше, и создать GrandchildUserControl для имитации вызова конструктора фактического элемента управления, который меня интересует.

FWIW Я считаю, что это следствие 1) и 3) и почти наверняка является обходным путем чего-то, что задумано.

Пункт 1) также дает отличный метод исследования для таких ситуаций — вы можете запустить другой экземпляр Visual Studio и подключиться к первому экземпляру Visual Studio и отладить вызовы методов, которые там выполняются. Это помогло мне. устранить несколько проблем с дизайнером в прошлом.

person dash    schedule 08.05.2012

Как указал 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 будет перепутан. Вы можете контролировать такое поведение, реализовав сериализатор для своих элементов управления. т.е. вам нужно реализовать класс BaseControlCodeDomSerializer, производный от System.ComponentModel.Design.Serialization.CodeDomSerializer, переопределяющий метод 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;
    }

Наконец, вы можете просто связать сериализатор с элементом управления с помощью этого атрибута:

[DesignerSerializer("MyControls.BaseControlCodeDomSerializer", typeof(CodeDomSerializer))]
person Francesco Baruchelli    schedule 15.05.2012