Конструктор "UserControl" с параметрами в C#

Назовите меня сумасшедшим, но я тип парня, который любит конструкторы с параметрами (если это необходимо), в отличие от конструктора без параметров, за которым следуют свойства. Мой мыслительный процесс: если свойства необходимы для фактического построения объекта, они должны войти в конструктор. Я получаю два преимущества:

  1. я знаю, что когда объект построен (без ошибки/исключения), мой объект хорош.
  2. это помогает не забыть установить определенное свойство.

это мышление начинает вредить мне в отношении развития формы / usercontrol. Представьте себе это UserControl:

public partial class MyUserControl : UserControl
{
  public MyUserControl(int parm1, string parm2)
  {
    // We'll do something with the parms, I promise
    InitializeComponent();
  }
}

во время разработки, если я уроню это UserControl на бланке я получаю Exception:

не удалось создать компонент "MyUserControl"...
Система.MissingMethodException - для этого объекта не определен конструктор без параметров.

мне кажется, что это единственный способ обойти это было добавить конструктор по умолчанию (если кто-то еще знает способ).

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

  public MyUserControl(int parm1, string parm2)
  {
    // We'll do something with the parms, I promise
    InitializeComponent();
  }
}

весь смысл не включать конструктор без параметров состоял в том, чтобы избежать его использования. И я даже не могу использовать DesignMode свойство делать что-то вроде:

public partial class MyUserControl : UserControl
{
  public MyUserControl()
  {
    if (this.DesignMode)
    {
      InitializeComponent();
      return;
    }

    throw new Exception("Use constructor with parameters");
  }
}

это тоже не работает:

if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)

хорошо, идем дальше ...

у меня есть мой конструктор без параметров, я могу бросить его в форму, и форма InitializeComponent будет выглядеть это:

private void InitializeComponent()
{
  this.myControl1 = new MyControl();

  // blah, blah
}

и поверьте мне, потому что я это сделал (да, игнорируя комментарии, созданные Visual Studio), я попытался возиться, и я передал параметры InitializeComponent чтобы я мог передать их конструктору MyControl.

что приводит меня к этому:

public MyForm()
{
  InitializeComponent(); // Constructed once with no parameters

  // Constructed a second time, what I really want
  this.myControl1 = new MyControl(anInt, aString);  
}

для меня использовать UserControl параметры в конструктор, я должен добавить второй конструктор, который мне не нужен? И создать экземпляр элемента управления дважды?

я чувствую, что должен делать что-то не так. Мысли? Мнения? Уверенность (надеюсь)?

10 ответов


проектные решения, принятые в отношении того, как работает Windows Forms, более или менее исключают параметризацию .ctors для компонентов windows forms. Вы можете использовать их, но когда вы это делаете, вы выходите за рамки общепринятых механизмов. Скорее, Windows Forms предпочитает инициализацию значений через свойства. Это действительный метод проектирования, если не широко используется.

это имеет некоторые преимущества, хотя.

  1. простота использования для клиентов. Клиентскому коду не нужно отследите кучу данных, он может сразу что-то создать и просто увидеть его с разумными (если неинтересными) результатами.
  2. простота использования для дизайнера. Код конструктора понятнее и проще анализировать в целом.
  3. препятствует необычным зависимостям данных внутри одного компонента. (Хотя даже microsoft взорвала этот с SplitContainer)

в формах есть много поддержки для правильной работы с дизайнером в этом техника тоже. Такие вещи, как DefaultValueAttribute, DesignerSerializationVisibilityAttribute и BrowsableAttribute даст вам возможность обеспечить богатый опыт клиента с минимальными усилиями.

(это не единственный компромисс, который был сделан для работы с клиентами в windows forms. Абстрактные компоненты базового класса также могут стать волосатыми.)

Я бы предложил придерживаться конструктора без параметров и работать в рамках принципов проектирования windows forms. Если есть реальные предпосылки, что ваш UserControl необходимо применить, инкапсулировать их в другой класс, а затем назначить экземпляр этого класса вашему элементу управления через свойство. Это также даст немного лучшее разделение беспокойства.


существует две конкурирующие парадигмы для проектирования классов:

  1. используйте конструкторы без параметров и установите кучу свойств после
  2. использовать параметризованные конструкторы для задания свойств в конструкторе

на Visual Studio Windows Forms Designer заставляет вас предоставить констуктор без параметров на элементах управления для правильной работы. На самом деле для этого требуется только конструктор без параметров, чтобы создавать экземпляры элементов управления, но не проектировать их (конструктор фактически анализирует метод InitializeComponent при проектировании элемента управления). Это означает, что конструктор можно использовать для создания формы или пользовательского элемента управления без конструктора без параметров, но нельзя создать другой элемент управления для использования этого элемента управления, так как конструктор не сможет создать его экземпляр.

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

независимо от того, какую парадигму вы используете, также обычно рекомендуется поместить длинный код инициализации в OnLoad() метод, тем более, что DesignMode свойство будет работать во время загрузки, но не работать в конструкторе.


Я бы порекомендовал

public partial class MyUserControl : UserControl
{
    private int _parm1;
    private string _parm2;

    private MyUserControl()
    {
        InitializeComponent();
    }

    public MyUserControl(int parm1, string parm2) : this()
    {
        _parm1 = parm1;
        _parm2 = parm2;
    }
}

таким образом, базовый конструктор всегда вызывается первым, и любые ссылки на компоненты допустимы.

при необходимости вы можете перегрузить открытый ctor, гарантируя, что элемент управления всегда создается с правильными значениями.

в любом случае вы гарантируете, что без параметров ctor никогда не вызывается.

Я не тестировал это, поэтому, если он падает, я извиняюсь!


к сожалению, это проблема дизайна, которая будет возникать часто, а не только в пространстве управления.

часто бывают ситуации, когда вам нужен конструктор без параметров, даже если конструктор без параметров не идеален. Например, многие типы значений, IMO, были бы лучше без конструкторов без параметров, но невозможно создать тот, который работает таким образом.

в этих ситуациях вам нужно просто спроектировать элемент управления / компонент в как можно лучше. Использование разумных (и предпочтительно наиболее распространенных) параметров по умолчанию может значительно помочь, поскольку вы можете по крайней мере (надеюсь) инициализировать компонент с хорошим значением.

кроме того, попробуйте спроектировать компонент таким образом, чтобы можно было изменить эти свойства после создания компонента. С компонентами Windows Forms это обычно нормально, так как вы можете в значительной степени делать все, пока время загрузки безопасно.

опять же, я согласен - это не идеал, но это просто то, с чем мы должны жить и работать.


короче говоря, дизайнер - это парень, которому нравятся конструкторы без параметров. Итак, насколько мне известно, если вы действительно хотите использовать конструкторы на основе параметров, вы, вероятно, застряли с работой вокруг него так или иначе.


предоставлять конструктор без параметров конструктор и сделать его частным - если вы действительно должны сделать это таким образом... :-)

EDIT: ну, конечно, это не будет работать для UserControls. Очевидно, я плохо соображал. Дизайнер должен выполнить код в InitializeComponent (), и он не может работать, если конструктор является частным. Сожалеть об этом. Это тут работы для форм, однако.


просто сделать это:

public partial class MyUserControl : UserControl
{
    public MyUserControl() : this(-1, string.Empty)
    {
    }

    public MyUserControl(int parm1, string parm2)
    {
        // We'll do something with the parms, I promise
        if (parm1 == -1) { ... }
        InitializeComponent();
    }
}

тогда "реальный" конструктор может действовать соответственно.


прошло довольно много времени с тех пор, как был задан вопрос, но, возможно, мой подход кому-то полезен.

Я лично также предпочитаю использовать параметризованные конструкторы, чтобы не забыть задать определенное свойство.

поэтому вместо использования фактического конструктора я просто определяю public void PostConstructor где все вещи помещаются, вы обычно помещаете в конструктор. Таким образом, фактический конструктор UserControl всегда содержит только InitializeComponent (). Таким образом, вам не нужно настраивать свою любимую парадигму программирования на VisualStudios, чтобы правильно запустить конструктор. Чтобы эта схема программирования работала, она должна следовать с самого дна.

на практике это PostConstructionalizm будет выглядеть так: Начнем с элемента управления в нижней части иерархии вызовов UserControl.

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

  public void PostConstructor(YourParameters[])
  {
      //setting parameters/fillingdata into form
  }
}

таким образом, UserControl, содержащий ChildControl выглядело бы примерно так:

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

  public void PostConstructor(YourParameters[])
  {
      ChildControl.PostConstructor(YourParameters[])
      //setting parameters/fillingdata into form
  }
}

и, наконец, форма, вызывающая один из пользовательских элементов управления, просто помещает PostConstructor после InitializeComponent.

public partial class UI : Form
{
  public UI(yourParameters[])
  {
    InitializeComponent();
    FatherControl.PostConstructor(yourParameters[]);
  }
}

У меня есть способ обойти это.

  1. создайте элемент управления A в форме с конструктором без параметров.
  2. создайте элемент управления B с параметризованным конструктором в форме contstructor.
  3. копировать положение и размер от A до B.
  4. сделать невидимым.
  5. добавить B к материнской.

надеюсь, это поможет. Я просто столкнулся с тем же вопросом и попробовал и протестировал этот метод.

код для продемонстрировать:

public Form1()
{
    InitializeComponent();
    var holder = PositionHolderAlgorithmComboBox;
    holder.Visible = false;
    fixedKAlgorithmComboBox = new MiCluster.UI.Controls.AlgorithmComboBox(c => c.CanFixK);
    fixedKAlgorithmComboBox.Name = "fixedKAlgorithmComboBox";
    fixedKAlgorithmComboBox.Location = holder.Location;
    fixedKAlgorithmComboBox.Size = new System.Drawing.Size(holder.Width, holder.Height);
    holder.Parent.Controls.Add(fixedKAlgorithmComboBox);
}

держатель контролировать, fixedKAlgorithmComboBox Б. контроль

еще лучшим и полным решением было бы использовать reflect для копирования свойств один за другим из A В B. На данный момент я занят, и я этого не делаю. Может быть, в будущем я вернусь с кодом. Но это не так сложно и я верю, что вы можете сделать это самостоятельно.


У меня была аналогичная проблема при попытке передать объект, созданный в главной форме Windows, в пользовательскую форму UserControl. Что сработало для меня, это добавление свойства со значением по умолчанию в UserControl.Дизайнер.cs и обновление его после вызова InitializeComponent () в основной форме. Наличие значения по умолчанию предотвращает WinForms designer от создания ошибки "ссылка на объект не установлена на экземпляр объекта".

пример:

// MainForm.cs
public partial class MainForm : Form
   public MainForm() 
   {
     /* code for parsing configuration parameters which producs in <myObj> myConfig */
     InitializeComponent();
     myUserControl1.config = myConfig; // set the config property to myConfig object
   }

//myUserControl.Designer.cs
partial class myUserControl
{
    /// <summary> 
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    // define the public property to hold the config and give it a default value
    private myObj _config = new myObj(param1, param2, ...);      
    public myObj config
    {
        get
        {
            return _config ;
        }
        set
        {
            _config = value;
        }
    }

    #region Component Designer generated code
    ...
}

надеюсь, что это помогает!