Конструктор 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"...
    System.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 с параметрами конструктора, мне нужно добавить второй конструктор, который мне не нужен? И создать экземпляр управления дважды?

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

Ответ 1

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

Однако это имеет некоторые преимущества.

  • Простота использования для клиентов. Клиентскому коду не нужно отслеживать кучу данных, он может сразу что-то создать и просто увидеть его с разумными (если неинтересными) результатами.
  • Простота использования для дизайнера. Код конструктора более ясен и проще разбирать вообще.
  • Отказывает необычные зависимости данных в одном компоненте. (Несмотря на то, что даже Microsoft сдула этот файл с SplitContainer)

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

Ответ 7

Просто сделайте следующее:

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

Тогда конструктор "real" может действовать соответствующим образом.

Ответ 8

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

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

Поэтому вместо того, чтобы использовать фактический конструктор, я просто определяю public void PostConstructor, где все вещи помещаются, вы обычно помещаете в конструктор. Таким образом, Actual Constructor 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
  }
}

И, наконец, Form, вызывающий один из User Control, просто помещает PostConstructor после InitializeComponent.

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

Ответ 9

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

  • Создайте элемент управления A в форме с помощью конструктора без параметров.
  • Создайте элемент управления B с параметризованным конструктором в форме conttructor.
  • Скопировать положение и размер от A до B.
  • Сделать невидимым.
  • Добавьте 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);
}

держателем является Control A, fixedKAlgorithmComboBox - это элемент управления B.

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

Ответ 10

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

Пример:

// 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
    ...
}

Надеюсь, это поможет!