web-dev-qa-db-fra.com

Constructeur 'UserControl' avec des paramètres en C #

Appelez-moi fou, mais je suis du genre à aimer les constructeurs avec des paramètres (si nécessaire), par opposition à un constructeur sans paramètre suivi de la définition des propriétés. Mon processus de pensée: si les propriétés sont nécessaires pour construire réellement l’objet, elles doivent aller dans le constructeur. J'obtiens deux avantages:

  1. Je sais que lorsqu'un objet est construit (sans erreur/exception), mon objet est bon.
  2. Cela évite d’oublier de définir une propriété donnée.

Cet état d'esprit commence à me faire mal en ce qui concerne le développement de form/usercontrol. Imaginez cette UserControl:

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

Au moment de la conception, si je laisse cette UserControl sur un formulaire, je reçois une Exception:

Impossible de créer le composant 'MyUserControl' ...
System.MissingMethodException - Aucun constructeur sans paramètre défini pour cet objet.

Il me semble que le seul moyen de contourner ce problème consiste à ajouter le constructeur par défaut (à moins que quelqu'un d'autre connaisse un moyen).

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

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

Le but de ne pas inclure le constructeur sans paramètre était d'éviter de l'utiliser. Et je ne peux même pas utiliser la propriété DesignMode pour faire quelque chose comme:

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

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

Cela ne marche pas non plus:

if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)

Bien, je me déplace ...

J'ai mon constructeur sans paramètre, je peux le déposer sur le formulaire, et la variable InitializeComponent du formulaire ressemblera à ceci:

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

  // blah, blah
}

Et croyez-moi, parce que je l'ai fait (oui, en ignorant les commentaires générés par Visual Studio), j'ai essayé de déconner et j'ai passé des paramètres à InitializeComponent afin de les transmettre au constructeur de MyControl.

Ce qui m'amène à ceci:

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

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

Pour que je puisse utiliser un UserControl avec des paramètres dans le constructeur, je dois ajouter un second constructeur dont je n'ai pas besoin? Et instancier le contrôle deux fois?

Je sens que je dois faire quelque chose de mal. Pensées? Des avis? Assurance (espérons-le)?

97
JustLooking

Les décisions de conception prises concernant le fonctionnement de Windows Forms empêchent plus ou moins les .ctors paramétrés pour les composants de formulaires Windows. Vous pouvez les utiliser, mais lorsque vous le faites, vous sortez des mécanismes généralement approuvés. Au lieu de cela, Windows Forms préfère l’initialisation des valeurs via les propriétés. C'est une technique de conception valide, si elle n'est pas largement utilisée.

Cela a cependant quelques avantages.

  1. Facilité d'utilisation pour les clients. Le code client n'a pas besoin de rechercher un tas de données, il peut immédiatement créer quelque chose et le voir avec des résultats sensibles (même s'ils ne sont pas intéressants).
  2. Facilité d'utilisation pour le concepteur. Le code de concepteur est plus clair et plus facile à analyser en général.
  3. Décourage les dépendances de données inhabituelles au sein d'un seul composant. (Même si Microsoft a fait sauter celui-ci avec le SplitContainer )

Les formulaires sont également très utiles pour que les formulaires fonctionnent correctement avec le concepteur. Des choses comme DefaultValueAttribute , DesignerSerializationVisibilityAttribute , et BrowsableAttribute vous permettent de fournir une expérience client riche avec un minimum d'effort.

(Ce n'est pas le seul compromis qui ait été fait pour l'expérience du client dans les formulaires Windows. Les composants de la classe de base abstraite peuvent également devenir poilus.)

Je suggèrerais de rester avec un constructeur sans paramètre et de respecter les principes de conception de Windows Forms. S'il existe de véritables conditions préalables que votre UserControl doit appliquer, encapsulez-les dans une autre classe, puis affectez une instance de cette classe à votre contrôle via une propriété. Cela donnera aussi une meilleure séparation des préoccupations.

61
Greg D

Il existe deux paradigmes concurrents pour la conception des cours:

  1. Utilisez des constructeurs sans paramètre et définissez ensuite un ensemble de propriétés
  2. Utiliser des constructeurs paramétrés pour définir des propriétés dans le constructeur

Le Concepteur de formulaires Windows Visual Studio vous oblige à fournir un composant sans paramètre sur les contrôles afin de fonctionner correctement. En réalité, il suffit d’un constructeur sans paramètre pour instancier des contrôles, mais pas pour les concevoir (le concepteur analysera en fait la méthode InitializeComponent lors de la conception d’un contrôle). Cela signifie que vous pouvez utiliser le concepteur pour concevoir un formulaire ou un contrôle utilisateur sans constructeur sans paramètre, mais vous ne pouvez pas concevoir un autre contrôle pour utiliser ce contrôle, car le concepteur ne parviendra pas à l'instancier.

Si vous n'avez pas l'intention d'instancier par programme vos contrôles (c'est-à-dire de construire votre interface utilisateur "à la main"), ne vous inquiétez pas pour la création de constructeurs paramétrés, car ils ne seront pas utilisés. Même si vous allez instancier vos contrôles par programme, vous souhaiterez peut-être fournir un constructeur sans paramètre afin qu'ils puissent toujours être utilisés dans le concepteur si besoin est.

Quel que soit le paradigme utilisé, il est généralement préférable de mettre un long code d'initialisation dans la méthode OnLoad(), d'autant plus que la propriété DesignMode fonctionnera au moment du chargement, mais pas dans le constructeur.

36
Kevin Kibler

Je recommanderais

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

De cette façon, le constructeur de base est toujours appelé en premier et toutes les références aux composants sont valides.

Vous pouvez ensuite surcharger le public ctor si nécessaire en vous assurant que le contrôle est toujours instancié avec les valeurs correctes.

Dans les deux cas, vous vous assurez que le ctor sans paramètre n’est jamais appelé.

Je n'ai pas testé cela, donc si ça tombe, je m'excuse!

9
Antony Koch

Fournissez un constructeur sans paramètre au concepteur et rendez-le privé - si vous devez vraiment le faire de cette façon ... :-)

EDIT: Bien sûr, cela ne fonctionnera pas pour UserControls. Je ne pensais évidemment pas clairement. Le concepteur doit exécuter le code dans InitializeComponent () et cela ne peut pas fonctionner si le constructeur est privé. Désolé pour ça. Il est-ce que fonctionne pour les formulaires, cependant.

4
Dan Byström

C'est malheureusement un problème de conception qui se produira fréquemment, pas seulement dans l'espace de contrôle.

Il arrive souvent que vous ayez besoin d'un constructeur sans paramètre, même si un constructeur sans paramètre n'est pas idéal. Par exemple, de nombreux types de valeur, IMO, seraient mieux sans constructeurs sans paramètre, mais il est impossible de créer un tel système.

Dans ces situations, vous devez simplement concevoir le contrôle/composant de la meilleure manière possible. L'utilisation de paramètres par défaut raisonnables (et de préférence les plus courants) peut être très utile, car vous pouvez au moins (espérons-le) initialiser le composant avec une bonne valeur.

Essayez également de concevoir le composant de manière à pouvoir modifier ces propriétés une fois le composant généré. Avec les composants Windows Forms, c'est généralement très bien, car vous pouvez pratiquement tout faire jusqu'au chargement en toute sécurité.

Encore une fois, je suis d’accord - ce n’est pas idéal, mais c’est juste quelque chose avec lequel nous devons vivre et travailler.

4
Reed Copsey

En bref, le concepteur est le genre de gars qui aime les constructeurs sans paramètres. Donc, à ma connaissance, si vous voulez vraiment utiliser des constructeurs basés sur des paramètres, vous êtes probablement obligé de travailler avec cela d'une manière ou d'une autre.

4
Fredrik Mörk

Faites juste ceci:

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

Ensuite, le "vrai" constructeur peut agir en conséquence.

3
Bob Nadler

La question a été posée il y a longtemps, mais peut-être que mon approche est utile à quelqu'un.

Personnellement, je préfère aussi utiliser des constructeurs paramétrés pour ne pas oublier de définir une propriété donnée.

Ainsi, au lieu d’utiliser le constructeur actuel, je définis simplement un public vide PostConstructor où toutes les choses sont placées normalement dans le constructeur. Ainsi, le constructeur réel de UserControl ne contient toujours que InitializeComponent () . De cette manière, vous n'avez pas à ajuster votre paradigme de programmation préféré à VisualStudios qui doit exécuter correctement le concepteur. Pour que ce schéma de programmation fonctionne, il doit être suivi de très bas.

En pratique, ceci PostConstructionalizm ressemblerait un peu à ceci: Commençons par un contrôle au bas de la hiérarchie de vos appels UserControl.

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

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

Ainsi, un UserControl contenant le ChildControl ressemblerait à ceci:

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

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

Et enfin, un formulaire appelant l’un des contrôles utilisateur place simplement le PostConstructor après InitializeComponent .

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

J'ai un moyen de contourner le problème. 

  1. Créez un contrôle A sur la fiche avec le constructeur sans paramètre. 
  2. Créez un contrôle B avec un constructeur paramétré sous la forme contstructor.
  3. Copier la position et la taille de A à B.
  4. Faire un invisible.
  5. Ajoutez B au parent de A.

J'espère que cela aidera. Je viens de rencontrer la même question et essayé et testé cette méthode.

Code pour démontrer:

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

le titulaire est le contrôle A, fixedKAlgorithmComboBox est le contrôle B.

Une solution encore meilleure et complète consisterait à utiliser reflect pour copier les propriétés une à une de A à B. Pour le moment, je suis occupé et je ne le fais pas. Peut-être que dans le futur je reviendrai avec le code. Mais ce n’est pas si difficile et je crois que vous pouvez le faire vous-même.

0
Eric Chow

J'ai eu un problème similaire en essayant de transmettre un objet créé dans le Windows Form principal à un formulaire UserControl personnalisé. Ce qui a fonctionné pour moi a été d'ajouter une propriété avec une valeur par défaut à UserControl.Designer.cs et de la mettre à jour après l'appel InitializeComponent () du formulaire principal. Avoir une valeur par défaut empêche le concepteur WinForms de générer une erreur "Référence d'objet non définie à une instance d'objet".

Exemple:

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

J'espère que cela t'aides!

0
mikmaksi