wake-up-neo.com

'UserControl'-Konstruktor mit Parametern in C #

Nennen Sie mich verrückt, aber ich bin der Typ, der Konstruktoren mit Parametern (wenn nötig) liebt, im Gegensatz zu einem Konstruktor ohne Parameter, auf den das Festlegen von Eigenschaften folgt. Mein Denkprozess: Wenn die Eigenschaften zum Erstellen des Objekts erforderlich sind, sollten sie in den Konstruktor aufgenommen werden. Ich habe zwei Vorteile:

  1. Ich weiß, dass wenn ein Objekt erstellt wird (ohne Fehler/Ausnahme), mein Objekt gut ist.
  2. Es hilft zu vermeiden, das Festlegen einer bestimmten Eigenschaft zu vergessen.

Diese Denkweise schmerzt mich in Bezug auf die Entwicklung von Formularen/Benutzerkontrollen. Stellen Sie sich diese UserControl vor:

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

Wenn ich diese UserControl auf ein Formular lösche, erhalte ich zur Entwurfszeit eine Exception:

Fehler beim Erstellen der Komponente "MyUserControl" ...
System.MissingMethodException - Kein parameterloser Konstruktor für dieses Objekt definiert.

Es scheint mir, als wäre dies der einzige Weg, um den Standardkonstruktor hinzuzufügen (es sei denn, jemand anderes kennt einen Weg).

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

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

Der parameterlose Konstruktor sollte nicht verwendet werden. Ich kann die DesignMode-Eigenschaft auch nicht verwenden, um Folgendes zu tun:

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

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

Das funktioniert auch nicht:

if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)

Gut, geht mit ...

Ich habe meinen parameterlosen Konstruktor, ich kann ihn im Formular ablegen, und die Variable InitializeComponent des Formulars sieht folgendermaßen aus:

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

  // blah, blah
}

Und vertrauen Sie mir, weil ich es getan habe (ja, die Kommentare, die Visual Studio ignoriert hat), ich versuchte herumzuspielen und Parameter an InitializeComponent übergeben, damit ich sie an den Konstruktor von MyControl übergeben konnte.

Was mich dazu führt:

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

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

Um eine UserControl mit Parametern für den Konstruktor zu verwenden, muss ich einen zweiten Konstruktor hinzufügen, den ich nicht benötige? Und die Kontrolle zweimal instanziieren?

Ich habe das Gefühl, dass ich etwas falsch machen muss. Gedanken? Meinungen Versicherung (hoffentlich)?

97
JustLooking

Entwurfsentscheidungen bezüglich der Funktionsweise von Windows Forms schließen mehr oder weniger parametrisierte .ctors für Windows-Formularkomponenten aus. Sie können sie verwenden, aber wenn Sie dies tun, verlassen Sie die allgemein anerkannten Mechanismen. Vielmehr bevorzugt Windows Forms die Initialisierung von Werten über Eigenschaften. Dies ist eine gültige Designtechnik, wenn nicht weit verbreitet.

Dies hat jedoch einige Vorteile.

  1. Benutzerfreundlichkeit für Kunden. Der Client-Code muss keine Datenmenge aufspüren, er kann sofort etwas erstellen und es mit vernünftigen (wenn auch uninteressanten) Ergebnissen sehen.
  2. Benutzerfreundlichkeit für den Designer. Designer-Code ist im Allgemeinen übersichtlicher und einfacher zu analysieren.
  3. Verhindert ungewöhnliche Datenabhängigkeiten innerhalb einer einzelnen Komponente. (Obwohl auch Microsoft dieses mit der SplitContainer durchgeblasen hat)

Es gibt eine Menge Unterstützung in Formularen, um auch in dieser Technik richtig mit dem Designer zusammenzuarbeiten. Dinge wie DefaultValueAttribute , DesignerSerializationVisibilityAttribute und BrowsableAttribute bieten Ihnen die Möglichkeit, mit minimalem Aufwand ein reichhaltiges Kundenerlebnis zu bieten.

(Dies ist nicht der einzige Kompromiss, der für die Kundenerfahrung in Windows-Formularen getroffen wurde. Abstrakte Basisklassenkomponenten können auch haarig werden.)

Ich würde vorschlagen, bei einem parameterlosen Konstruktor zu bleiben und innerhalb der Windows Forms-Design-Prinzipien zu arbeiten. Wenn es echte Vorbedingungen gibt, die Ihre UserControl erzwingen muss, kapseln Sie sie in eine andere Klasse ein und weisen Sie Ihrem Steuerelement dann über eine Eigenschaft eine Instanz dieser Klasse zu. Dies führt zu einer etwas besseren Trennung der Bedenken.

61
Greg D

Es gibt zwei konkurrierende Paradigmen zur Gestaltung von Klassen:

  1. Verwenden Sie parameterlose Konstruktoren und stellen Sie anschließend eine Reihe von Eigenschaften ein
  2. Verwenden Sie parametrisierte Konstruktoren, um Eigenschaften im Konstruktor festzulegen

Der Visual Studio-Windows Forms-Designer zwingt Sie zur Bereitstellung eines parameterlosen Konstuktors für Steuerelemente, damit er ordnungsgemäß funktioniert. Tatsächlich ist nur ein parameterloser Konstruktor erforderlich, um Steuerelemente zu instanziieren, nicht aber um sie zu entwerfen (der Designer parst die InitializeComponent-Methode beim Entwerfen eines Steuerelements). Dies bedeutet, dass Sie den Designer zum Entwerfen eines Formulars oder Benutzersteuerelements ohne parameterlosen Konstruktor verwenden können. Sie können jedoch kein anderes Steuerelement für die Verwendung dieses Steuerelements entwerfen, da der Designer es nicht instanziieren kann.

Wenn Sie Ihre Steuerelemente nicht programmgesteuert instanziieren möchten (d. H. Ihre Benutzeroberfläche "von Hand" erstellen), müssen Sie sich keine Sorgen um die Erstellung parametrisierter Konstruktoren machen, da diese nicht verwendet werden. Selbst wenn Sie Ihre Steuerelemente programmgesteuert instanziieren möchten, möchten Sie möglicherweise einen parameterlosen Konstruktor bereitstellen, damit diese bei Bedarf im Designer verwendet werden können.

Unabhängig davon, welches Paradigma Sie verwenden, ist es im Allgemeinen auch eine gute Idee, langwierigen Initialisierungscode in die OnLoad()-Methode einzufügen, insbesondere da die DesignMode-Eigenschaft beim Laden funktioniert, nicht aber im Konstruktor.

36
Kevin Kibler

Ich würde empfehlen

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

Auf diese Weise wird der Basiskonstruktor immer zuerst aufgerufen, und alle Verweise auf Komponenten sind gültig.

Sie könnten dann ggf. den public ctor überlasten, um sicherzustellen, dass die Steuerung immer mit den korrekten Werten instanziiert wird.

So oder so stellen Sie sicher, dass der parameterlose Parameter niemals aufgerufen wird.

Ich habe das nicht getestet, also entschuldige ich mich, wenn es vorbei geht!

9
Antony Koch

Stellen Sie dem Designer einen parameterlosen Konstruktor zur Verfügung und machen Sie ihn privat - wenn Sie es wirklich so machen müssen ... :-)

BEARBEITEN: Natürlich funktioniert dies nicht für UserControls. Ich habe offensichtlich nicht klar gedacht. Der Designer muss den Code in InitializeComponent () ausführen und er kann nicht funktionieren, wenn der Konstruktor privat ist. Das tut mir leid. Es funktioniert jedoch für Formulare.

4
Dan Byström

Dies ist leider ein Designproblem, das häufig auftritt, nicht nur im Kontrollbereich.

Oft gibt es Situationen, in denen Sie einen parameterlosen Konstruktor benötigen, obwohl ein parameterloser Konstruktor nicht ideal ist. Beispielsweise wären viele Wertetypen, IMO, ohne parameterlose Konstruktoren besser geeignet, aber es ist unmöglich, einen solchen zu erstellen, der auf diese Weise funktioniert.

In diesen Situationen müssen Sie die Steuerung/Komponente nur so gut wie möglich entwerfen. Die Verwendung vernünftiger (und vorzugsweise die gebräuchlichsten) Standardparameter kann erheblich helfen, da Sie die Komponente (hoffentlich) mit einem guten Wert initialisieren können.

Versuchen Sie außerdem, die Komponente so zu entwerfen, dass Sie diese Eigenschaften ändern können, nachdem die Komponente generiert wurde. Bei Windows Forms-Komponenten ist dies normalerweise in Ordnung, da Sie alles tun können, bis die Ladezeit sicher ist.

Wieder stimme ich zu - das ist nicht ideal, aber es ist nur etwas, mit dem wir leben und umgehen müssen.

4
Reed Copsey

Kurz gesagt, der Designer ist ein Typ, der Parameter ohne Konstruktoren bevorzugt. Wenn Sie also wirklich auf Parameter basierende Konstruktoren verwenden möchten, arbeiten Sie wahrscheinlich mit der Arbeit auf die eine oder andere Weise.

4
Fredrik Mörk

Mach einfach das:

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

Dann kann der "echte" Konstruktor entsprechend handeln.

3
Bob Nadler

Es ist schon eine Weile her, seit die Frage gestellt wurde, aber vielleicht ist mein Ansatz für jemanden hilfreich.

Ich persönlich bevorzuge auch parametrisierte Konstruktoren, um zu vermeiden, dass eine bestimmte Eigenschaft festgelegt wird.

Anstatt den eigentlichen Konstruktor zu verwenden, definiere ich einfach einen public void PostConstructor , in dem alle Dinge enthalten sind, die Sie normalerweise in den Konstruktor stellen würden. Der Actual Constructor des UserControl enthält also immer nur InitializeComponent () ..__ Auf diese Weise müssen Sie Ihr bevorzugtes Programmierparadigma nicht an VisualStudios anpassen, um den Designer ordnungsgemäß auszuführen. Damit dieses Programmierschema funktioniert, muss es von ganz unten befolgt werden.

In der Praxis würde dies PostConstructionalizm in etwa so aussehen: Beginnen wir mit einem Control am unteren Rand Ihrer UserControl-Aufrufhierarchie.

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

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

Ein UserControl mit ChildControl würde also so aussehen:

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

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

Ein Formular, das eines der Benutzersteuerelemente aufruft, setzt den PostConstructor schließlich nach InitializeComponent .

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

Ich habe eine Möglichkeit, es zu umgehen. 

  1. Erstellen Sie mit dem parameterlosen Konstruktor ein Steuerelement A im Formular. 
  2. Erstellen Sie ein Steuerelement B mit parametrisiertem Konstruktor im Formular-Konstruktor.
  3. Position und Größe von A nach B kopieren.
  4. A unsichtbar machen.
  5. Fügen Sie B zu A's Eltern hinzu.

Hoffe das wird helfen. Ich bin gerade auf dieselbe Frage gestoßen und habe diese Methode getestet und getestet.

Code zur Demonstration:

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

inhaber ist Control A, FixedKAlgorithmComboBox ist Control B.

Eine noch bessere und umfassendere Lösung wäre, die Eigenschaften mithilfe von reflect nacheinander von A nach B zu kopieren. Im Moment bin ich damit beschäftigt und mache das nicht. Vielleicht werde ich in Zukunft mit dem Code zurückkommen. Aber es ist nicht so schwer und ich glaube, dass Sie es selbst schaffen können.

0
Eric Chow

Ich hatte ein ähnliches Problem beim Versuch, ein im Windows-Hauptformular erstelltes Objekt an ein benutzerdefiniertes UserControl-Formular zu übergeben. Was für mich funktionierte, war das Hinzufügen einer Eigenschaft mit einem Standardwert zu UserControl.Designer.cs und die Aktualisierung nach dem Aufruf InitializeComponent () im Hauptformular. Mit einem Standardwert wird verhindert, dass der WinForms-Designer einen Fehler "Objektverweis, der nicht auf eine Instanz eines Objekts festgelegt ist" auslöst.

Beispiel:

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

Hoffe das hilft!

0
mikmaksi