C # 中带参数的“ UserControl”构造函数

你可以说我疯了,但我是那种喜欢带参数的构造函数(如果需要的话)的人,而不喜欢没有参数后面跟着设置属性的构造函数。我的想法是: 如果实际构造对象需要这些属性,那么它们应该放到构造函数中。我有两个优势:

  1. 我知道当构造一个对象时(没有错误/异常) ,我的对象是好的。
  2. 它有助于避免忘记设置某个属性。

在表单/用户控制开发方面,这种思维方式开始伤害我:

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到构造函数,我必须添加一个不需要的第二个构造函数?然后实例化控件两次?

我觉得我一定是做错了什么。有什么想法? 意见? 保证(希望) ?

56838 次浏览

Provide a parameterless constructor for the designer and make it private - if you really must do it this way... :-)

EDIT: Well of course this won't work for UserControls. I obviously wasn't thinking clearly. The designer need to execute the code in InitializeComponent() and it's can't work if the constructor is private. Sorry about that. It does work for forms, however.

This is unfortunately a design issue that will occur frequently, not just in the control space.

There are often situations where you need to have a parameterless constructor, even though a parameterless constructor is not ideal. For example, many value types, IMO, would be better off without parameterless constructors, but it's impossible to create one that works that way.

In these situations, you have to just design the control/component in the best manner possible. Using reasonable (and preferably the most common) default parameters can help dramatically, since you can at least (hopefully) initialize the component with a good value.

Also, try to design the component in a way that you can change these properties after the component is generated. With Windows Forms components, this is typically fine, since you can pretty much do anything until load time safely.

Again, I agree - this isn't ideal, but it's just something we have to live with and work around.

Just do this:

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

Then the 'real' constructor can act accordingly.

Well, in short, the designer is the kind of guy that likes parameter-less constructors. So, to the best of my knowledge, if you really want to use parameter based constructors you are probably stuck with working around it one way or the other.

I would recommend

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

As this way the base constructor is always called first and any references to components are valid.

You could then overload the public ctor if need be, ensuring the control is always instantiated with the correct values.

Either way, you ensure that the parameterless ctor is never called.

I haven't tested this so if it falls over I apologise!

Design decisions made regarding the way Windows Forms works more or less preclude parameterized .ctors for windows forms components. You can use them, but when you do you're stepping outside the generally approved mechanisms. Rather, Windows Forms prefers initialization of values via properties. This is a valid design technique, if not widely used.

This has some benefits, though.

  1. Ease of use for clients. Client code doesn't need to track down a bunch of data, it can immediately create something and just see it with sensible (if uninteresting) results.
  2. Ease of use for the designer. Designer code is clearer and easier to parse in general.
  3. Discourages unusual data dependencies within a single component. (Though even microsoft blew this one with the SplitContainer)

There's a lot of support in forms for working properly with the designer in this technique also. Things like DefaultValueAttribute, DesignerSerializationVisibilityAttribute, and BrowsableAttribute give you the opportunity to provide a rich client experience with minimal effort.

(This isn't the only compromise that was made for client experience in windows forms. Abstract base class components can get hairy too.)

I'd suggest sticking with a parameterless constructor and working within the windows forms design principles. If there are real preconditions that your UserControl must enforce, encapsulate them in another class and then assign an instance of that class to your control via a property. This will give a bit better separation of concern as well.

There are two competing paradigms for designing classes:

  1. Use parameterless constructors and set a bunch of properties afterwards
  2. Use parameterized constructors to set properties in the constructor

The Visual Studio Windows Forms Designer forces you to provide a parameterless constuctor on controls in order to work properly. Actually, it only requires a parameterless constructor in order to instantiate controls, but not to design them (the designer will actually parse the InitializeComponent method while designing a control). This means that you can use the designer to design a form or user control without a parameterless constructor, but you cannot design another control to use that control because the designer will fail to instantiate it.

If you don't intend to programmatically instantiate your controls (i.e. build your UI "by hand"), then don't worry about creating parameterized constructors, since they won't be used. Even if you are going to programmatically instantiate your controls, you may want to provide a parameterless constructor so they can still be used in the designer if need be.

Regardless of which paradigm you use, it is also generally a good idea to put lengthy initialization code in the OnLoad() method, especially since the DesignMode property will work at load time, but not work in the constructor.

It's quite a while since the question was asked, but maybe my approach is helpful to somebody.

I personally also prefer to use parameterized Constructors to avoid forgetting to set a certain property.

So instead of using the actual Constructor I simply define a public void PostConstructor where all things are put you would normally put in the Constructor. So the Actual Constructor of the UserControl always contains only InitializeComponent(). This way you don't have to adjust your favourite programming paradigm to VisualStudios needs to run the Designer properly. For this programming schema to work it has to be followed from the very bottom.

In practice this PostConstructionalizm would look somewhat like this: Let's start with a Control at the bottom of your UserControl call hierarchy.

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


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

So a UserControl containing the ChildControl would look something like that:

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


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

And finally a Form calling one of the User Control simply puts the PostConstructor after InitializeComponent.

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

I have a way to work around it.

  1. Create a control A on the form with the parameterless constructor.
  2. Create a control B with parameterized constructor in the form contstructor.
  3. Copy position and size from A to B.
  4. Make A invisible.
  5. Add B to A's parent.

Hope this will help. I just encountered the same question and tried and tested this method.

Code for demonstrate:

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

holder is Control A, fixedKAlgorithmComboBox is Control B.

An even better and complete solution would be to use reflect to copy the properties one by one from A to B. For the time being, I am busy and I am not doing this. Maybe in the future I will come back with the code. But it is not that hard and I believe you can do it yourself.

I had a similar problem trying to pass an object created in the main Windows Form to a custom UserControl form. What worked for me was adding a property with a default value to the UserControl.Designer.cs and updating it after the InitializeComponent() call in the main form. Having a default value prevents WinForms designer from throwing an "Object reference not set to an instance of an object" error.

Example:

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

Hope this helps!