Визуализация производного пользовательского элемента управления в режиме конструктора
У меня есть 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 ответа
Кажется, в конструкторе 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), я вижу:
Кажется, это особенность дизайнера. Согласно этому посту в блогах MSDN (который довольно старый)
Форма Form1 должна быть построена до того, как вы сможете добавить другую форму, скажем Form2, которая визуально наследуется от нее. Это связано с тем, что конструктор для Form2 должен создавать экземпляр Form1, а не System.Windows.Forms.Form. Это также объясняет, почему, если вы откроете Form2 в конструкторе, подключите отладчик к Visual Studio и установите точку останова в InitializeComponent формы Form1, точка останова действительно попадет.
Над InitializeComponent есть комментарий, который предупреждает вас против его изменения вручную. Это потому, что дизайнеру необходимо проанализировать этот код, и у него есть некоторые ограничения относительно того, что он может анализировать. Обычно гарантируется синтаксический анализ того, что там было сериализовано, но не произвольный код, который вы можете добавить.
Если вы вручную (через код) добавляете элемент управления в форму в конструкторе или в обработчике события Load, этот элемент управления не отображается в конструкторе. Это потому, что дизайнер не анализирует это - он только анализирует InitializeComponent.
Единственный способ, которым я когда-либо получал это для надежной работы, - это переместить весь мой код в метод, который вызывается InitializeComponent (и иногда, вы должны помнить, чтобы вставить его обратно, когда он "перезаписан" дизайнером) или сделать, как я делал выше, и создать GrandchildUserControl, чтобы подделать вызов конструктора фактического элемента управления, который меня интересует.
FWIW Я считаю, что это является следствием 1) и 3) и почти наверняка является обходом чего-то, что разработано.
Пункт 1) также поднимает замечательную методику расследования для таких ситуаций - вы можете запустить другой экземпляр Visual Studio и подключиться к первому экземпляру Visual Studio и отладить выполняющиеся там вызовы методов. Это помогло мне устранить некоторые проблемы дизайнера в прошлом.
Как было показано в 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))]