繁体   English   中英

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

[英]'UserControl' constructor with parameters in C#

说我疯了,但我是那种喜欢带参数的构造函数(如果需要)的人,而不是不带参数然后设置属性的构造函数。 我的思考过程:如果实际构造 object 需要这些属性,那么在构造函数中它们应该是 go。 我有两个优势:

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

在表单/用户控件开发方面,这种心态开始伤害我。 想象一下这个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 - 没有为此 object 定义无参数构造函数。

在我看来,唯一的解决办法就是添加默认构造函数(除非其他人知道办法)。

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

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

有关Windows窗体工作方式的设计决策或多或少排除了Windows窗体组件的参数化.ctors。 您可以使用它们,但是当您这样做时,便会超出公认的机制。 而是Windows窗体更喜欢通过属性初始化值。 如果未广泛使用,这是一种有效的设计技术。

不过,这有一些好处。

  1. 易于客户使用。 客户端代码不需要跟踪一堆数据,它可以立即创建某些内容,并以有意义(如果不感兴趣)的结果来查看它。
  2. 设计者易于使用。 通常,设计器代码更清晰,更易于解析。
  3. 阻止单个组件中异常的数据依赖关系。 (尽管微软甚至用SplitContainer吹灭了它)

表单中也有很多支持,可以与设计师一起使用此技术。 诸如DefaultValueAttributeDesignerSerializationVisibilityAttributeBrowsableAttribute使您有机会以最小的努力提供丰富的客户端体验。

(这并不是在Windows窗体中为客户体验做出的唯一折衷。抽象基类组件也可能变得毛茸茸。)

我建议坚持使用无参数构造函数,并在Windows窗体设计原则中进行工作。 如果您的UserControl必须执行一些实际的先决条件,则将它们封装在另一个类中,然后通过一个属性将该类的实例分配给您的控件。 这也将更好地分离关注点。

设计类有两种相互竞争的范例:

  1. 使用无参数的构造函数,然后设置一堆属性
  2. 使用参数化的构造函数在构造函数中设置属性

Visual Studio Windows窗体设计器会强制您在控件上提供无参数构造函数,以使其正常工作。 实际上,它只需要一个无参数的构造函数即可实例化控件,而无需设计它们(设计器实际上将在设计控件时解析InitializeComponent方法)。 这意味着您可以使用设计器来设计表单或用户控件而无需无参数的构造函数,但是您不能设计另一个控件来使用该控件,因为设计器将无法实例化该控件。

如果您不打算以编程方式实例化控件(即“手动”构建UI),则不必担心创建参数化的构造函数,因为它们不会被使用。 即使您要以编程方式实例化控件,也可能需要提供无参数的构造函数,以便在需要时仍可以在设计器中使用它们。

无论使用哪种范例,通常都应该在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。

我没有对此进行测试,所以如果它摔倒了,我表示歉意!

为设计师提供一个无参数的构造函数,并将其设为私有-如果您确实必须采用这种方式... :-)

编辑:当然,这不适用于UserControls。 我显然没有在想清楚。 设计人员需要在InitializeComponent()中执行代码,如果构造函数是私有的,则无法使用。 对于那个很抱歉。 但是,它确实适用于表单。

不幸的是,这是一个经常发生的设计问题,而不仅仅是控制空间。

在很多情况下,即使无参构造函数不是理想的,您也需要有无参构造函数。 例如,如果没有无参数构造函数,则许多值类型IMO会更好,但是创建一个可以那样工作的类型是不可能的。

在这些情况下,您仅需以最佳方式设计控件/组件。 使用合理的(最好是最常用的)默认参数可以极大地帮助您,因为您至少(希望)可以使用良好的值初始化组件。

另外,尝试以某种方式设计组件,以便在生成组件后可以更改这些属性。 使用Windows Forms组件通常很好,因为在安全加载时间之前,您几乎可以做任何事情。

再次,我同意-这不是理想的选择,但这只是我们必须与之共处和解决的问题。

好吧,简而言之,设计师是喜欢无参数构造函数的那种人。 因此,据我所知,如果您真的想使用基于参数的构造函数,则可能会坚持以一种或另一种方式来解决它。

只要这样做:

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

然后,“实际”构造函数可以相应地采取行动。

提出问题已经有一段时间了,但是也许我的方法对某人有所帮助。

我个人也更喜欢使用参数化的构造函数,以避免忘记设置某些属性。

因此,与其使用实际的构造函数, 不如定义一个公共无效的PostConstructor ,通常将所有内容放入该构造函数中。 因此,UserControl的实际构造方法始终只包含InitializeComponent() 这样,您不必根据VisualStudios调整自己喜欢的编程范例即可正确运行Designer。 为了使这种编程模式有效,必须从最底层开始。

实际上,此PostConstructionalizm看起来像这样:让我们从UserControl调用层次结构底部的Control开始。

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

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

因此,包含ChildControl的UserControl如下所示:

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. 使A不可见。
  5. 将B添加到A的父母中。

希望这会有所帮助。 我只是遇到了同样的问题,并尝试并测试了这种方法。

演示代码:

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

持有人是控件A,fixedKAlgorithmComboBox是控件B。

更好,更完整的解决方案是使用反射将属性从A逐个复制到B。暂时,我很忙,我没有这样做。 也许将来我会带回代码。 但这并不难,我相信您可以自己做。

尝试将在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
    ...
}

希望这可以帮助!

我的解决方法是定义一个接口并将ParentForm向下转换为它。

/// <summary>Service required for MyUserControl. Please implement this in your parent form.</summary>
interface IMyUserControlParam // or MyUserControl.IParam if you prefer it nested
{
    int Parm1 { get; }
    string Parm2 { get; }
}

public partial class MyUserControl : UserControl
{
    // ... constructor remains parameterless ...

    private void MyUserControl_Load(object sender, EventArgs e)
    {
        // Retrieves parameters from the parent form.
        // Raises if the parent form author forgot to implement the interface.
        // Also you may want to check like: if (ParentForm is not IMyUserControlParam) { ... handle the error gracefully ... }
        IMyUserControlParam param = (IMyUserControlParam)ParentForm;
        m_parm1 = param.Parm1;
        m_parm2 = param.Parm2;
    }
}

public partial class MyForm : Form, IMyUserControlParam
{
    // Define parameters for MyUserControl here
    public int Parm1 => 42;
    public string Parm2 => "foo";
}

它仍然不会像带参数的构造函数那样被编译器检查,但至少它会在加载表单后立即抛出,因此与仅依赖消费者正确调用myUserControl.Init(1, "foo"); 或者
注册事件处理程序,例如myUserControl.MyInit += myUserControl_MyInit; .

作为较短(但不太安全)的变体,您可以使用吸气剂:

public partial class MyUserControl : UserControl
{
    private IMyUserControlParam Param => (IMyUserControlParam)ParentForm;
    private int Parm1 => Param.Parm1;
    private string Parm1 => Param.Parm2;
}

最懒惰的变体是跳过界面并直接引用父表单 class ,如param = (MyForm)ParentForm 但是,这破坏了可重用性,从而违背了创建用户控件的目的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM