【问题标题】:'UserControl' constructor with parameters in C#带有 C# 参数的“UserControl”构造函数
【发布时间】:2009-11-23 16:32:53
【问题描述】:

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

  1. 我知道当一个对象被构造时(没有错误/异常),我的对象是好的。
  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 - 没有为此对象定义无参数构造函数。

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

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 生成的 cmets),我试着搞砸了,我将参数传递给 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,我必须添加第二个不需要的构造函数吗?并实例化控件两次?

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

【问题讨论】:

    标签: c# winforms parameters constructor user-controls


    【解决方案1】:

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

    不过,这有一些好处。

    1. 客户端易于使用。客户端代码不需要追踪一堆数据,它可以立即创建一些东西,然后用合理(如果无趣)的结果来查看它。
    2. 设计人员易于使用。总体而言,设计器代码更清晰、更易于解析。
    3. 不鼓励在单个组件中出现异常的数据依赖性。 (虽然连微软都用SplitContainer搞砸了这个)

    在表单中也有很多支持与设计人员在此技术中正常工作。 DefaultValueAttributeDesignerSerializationVisibilityAttributeBrowsableAttribute 之类的内容让您有机会以最少的努力提供丰富的客户体验。

    (这不是为 Windows 窗体中的客户端体验做出的唯一妥协。抽象基类组件也可能会变得很麻烦。)

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

    【讨论】:

      【解决方案2】:

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

      通常情况下,您需要有一个无参数构造函数,即使无参数构造函数并不理想。例如,许多值类型(IMO)如果没有无参数构造函数会更好,但不可能创建一个以这种方式工作的类型。

      在这些情况下,您必须尽可能以最佳方式设计控件/组件。使用合理的(最好是最常见的)默认参数会大有帮助,因为您至少(希望)可以用一个好的值初始化组件。

      另外,尝试设计组件,使您可以在组件生成后更改这些属性。对于 Windows 窗体组件,这通常没问题,因为在安全加载之前您几乎可以做任何事情。

      再次,我同意 - 这并不理想,但这只是我们必须忍受和解决的问题。

      【讨论】:

        【解决方案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;
            }
        }
        

        因为这种方式总是首先调用基本构造函数,并且对组件的任何引用都是有效的。

        然后,如果需要,您可以重载公共 ctor,确保始终使用正确的值实例化控件。

        无论哪种方式,您都可以确保永远不会调用无参数 ctor。

        我还没有测试过,所以如果它失败了我道歉!

        【讨论】:

        • 如果我没记错的话,由于私有的“无参数”构造函数,我仍然无法在设计模式下使用它。但是,无论如何 +1。
        【解决方案4】:

        我有办法解决它。

        1. 使用无参数构造函数在窗体上创建控件 A。
        2. 使用表单构造函数中的参数化构造函数创建控件 B。
        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);
        }
        

        holder 为 Control A,fixedKAlgorithmComboBox 为 Control B。

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

        【讨论】:

          【解决方案5】:

          这样做:

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

          那么“真正的”构造函数就可以采取相应的行动了。

          【讨论】:

          • 我承认,我不确定这能给我带来什么(或者它如何回答我的问题)。
          • 引起我注意的是重复的 InitializeComponent() 调用。此外,对我来说,带有 UserControl 的私有无参数构造函数可以与 VS 设计器一起使用。
          • 好吧,您可以用它在设计器中显示一条消息,例如“参数 foo 需要为运行时设置”。为了表明需要设置您通常使用构造函数传入的参数。参数的设置可能需要处理一些通常在构造函数中完成的控件设置。
          【解决方案6】:

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

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

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

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

          无论您使用哪种范例,在OnLoad() 方法中放置冗长的初始化代码通常也是一个好主意,特别是因为DesignMode 属性将在加载时起作用,但在构造函数中不起作用。

          【讨论】:

            【解决方案7】:

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

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

            因此,我没有使用实际的构造函数,而是简单地定义了一个 public void PostConstructor,所有的东西都放在其中,你通常会放在构造函数中。所以 UserControl 的实际构造函数总是只包含 InitializeComponent()。 这样您就不必根据 VisualStudios 需要调整您最喜欢的编程范例来正确运行设计器。要使这种编程模式发挥作用,必须从最底层开始遵循。

            在实践中,这个 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[]);
              }
            }
            

            【讨论】:

              【解决方案8】:

              嗯,简而言之,设计师是那种喜欢无参数构造函数的人。所以,据我所知,如果你真的想使用基于参数的构造函数,你可能会被困在以一种或另一种方式解决它。

              【讨论】:

              • “设计师是那种人”......好东西(有趣)!是的,看来我将不得不利用变通方法。我只是想确保没有一些巧妙的做事方式,或者有一些“已知规则”,例如:“你永远不会用我的 UserControl 做我正在做的事情”
              【解决方案9】:

              为设计器提供一个无参数的构造函数并将其设为私有 - 如果你真的必须这样做...... :-)

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

              【讨论】:

              • 如果是私有的,我怀疑设计师能不能用。
              • UserControl 不起作用 - 如果您使用多个程序集,这也会产生重大问题。
              • 你确定他们是private吗?如果将构造函数设为私有,则只有类中的代码可以使用它。如果您将它们设为internal,则同一程序集中的所有代码都可以使用它们,但程序集之外的代码则不能使用(除非您使用InternalsVisibleTo 属性)。为了让 VS 设计者能够使用它们,它们需要是public
              • 是的,可能是这样。反射不会被成员的可访问性等细节阻止......
              • 模式对话框(表单)的私有构造函数是确保对话框使用者不会忘记处理它(或者甚至必须为此创建它)的好方法。在表单上使用公共静态方法并快乐!
              【解决方案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
                  ...
              }
              

              希望这会有所帮助!

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2013-09-01
                • 1970-01-01
                • 1970-01-01
                • 2014-02-03
                • 2017-08-02
                • 2020-07-13
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多