Constructeur 'UserControl' avec paramètres en C#
Appelez-moi fou, mais je suis le type de gars qui aime les constructeurs avec des paramètres (si nécessaire), par opposition à un constructeur sans paramètres suivi de définir des propriétés. Mon processus de pensée: si les propriétés sont nécessaires pour construire réellement l'objet, elles devraient aller dans le constructeur. Je reçois deux avantages:
- je sais que lorsqu'un objet est construit (sans erreur / exception), mon objet est bon.
- , Il permet d'éviter d'oublier de définir un certain propriété.
Cet état d'esprit commence à me faire mal en ce qui concerne le développement form / usercontrol. Imaginez ceci 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 tomber cette UserControl
sur un formulaire, j'obtiens un Exception
:
Impossible de créer le composant 'MyUserControl'...
Système.MissingMethodException - aucun constructeur sans paramètre défini pour cet objet.
Il me semble que le seul moyen de contourner cela était d'ajouter le constructeur par défaut (à moins que quelqu'un d'autre connaît 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 fonctionne pas non plus:
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
Bien, on avance ...
J'ai mon constructeur sans paramètre, je peux le déposer sur le formulaire, et le formulaire InitializeComponent
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 Visual Studio généré), j'ai essayé de déconner et j'ai passé des paramètres à InitializeComponent
afin que je puisse les passer 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 j'utilise un UserControl
avec des paramètres au constructeur, je dois ajouter un deuxième constructeur dont je n'ai pas besoin? Et instancier le contrôle deux fois?
J'ai l'impression de faire quelque chose de mal. Pensées? Des avis? Assurance (espérons-le)?
10 réponses
Les décisions de conception prises concernant le fonctionnement de Windows Forms excluent plus ou moins paramétré .ctors pour les composants Windows forms. Vous pouvez les utiliser, mais quand vous le faites, vous sortez des mécanismes généralement approuvés. Au contraire, 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 quelques avantages, cependant.
- Facilité d'utilisation pour les clients. Code Client n'a pas besoin d'un tas de données, il peut immédiatement créer quelque chose et juste le voir avec des résultats sensibles (si inintéressants).
- Facilité d'utilisation pour le concepteur. Le code du concepteur est plus clair et plus facile à analyser en général.
- décourage les dépendances de données inhabituelles au sein d'un seul composant. (Même si microsoft a soufflé celui-ci avec le
SplitContainer
)
Il y a beaucoup de soutien dans les formulaires pour travailler correctement avec le concepteur dans cette technique aussi. Des choses comme DefaultValueAttribute
, DesignerSerializationVisibilityAttribute
, et BrowsableAttribute
vous donner l'opportunité de fournir une riche expérience client avec un minimum d'effort.
(ce n'est pas le seul compromis qui a été fait pour l'expérience client dans Windows forms. Les composants de la classe de base abstraite peuvent aussi devenir poilus.)
Je suggère de coller avec un constructeur sans paramètre et de travailler dans les principes de conception de Windows forms. S'il existe de réelles conditions préalables que votre UserControl
doit appliquer, encapsulez-les dans une autre classe, puis assigner une instance de cette classe à votre contrôle via une propriété. Cela donnera un peu mieux la séparation de bien.
Il existe deux paradigmes concurrents pour concevoir des classes:
- Utilisez des constructeurs sans paramètres et définissez un tas de propriétés par la suite
- Utilisez des constructeurs paramétrés pour définir des propriétés dans le constructeur
Le Visual Studio Windows Forms Designer vous oblige à fournir un calculateur sans paramètre sur les contrôles afin de fonctionner correctement. En fait, il ne nécessite qu'un constructeur sans paramètre pour instancier les contrôles, mais pas pour concevoir eux (le concepteur analysera réellement 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 l'utiliser 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 de créer des constructeurs paramétrés, car ils ne sera pas utilisé. Même si vous instanciez par programme vos contrôles, vous pouvez fournir un constructeur sans paramètre afin qu'ils puissent toujours être utilisés dans le concepteur si nécessaire.
Quel que soit le paradigme que vous utilisez, il est également généralement judicieux 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 ne fonctionnera pas dans le constructeur.
Je recommande
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 alors surcharger le ctor public si nécessaire, en vous assurant que le contrôle est toujours instancié avec les valeurs correctes.
De toute façon, 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!
C'est malheureusement un problème de conception qui se produira fréquemment, pas seulement dans l'espace de contrôle.
Il y a souvent des situations où vous devez avoir 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ètres, mais il est impossible d'en créer un qui fonctionne de cette façon.
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 aider considérablement, puisque 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 bien, car vous pouvez à peu près tout faire jusqu'au temps de chargement en toute sécurité.
Encore une fois, je suis d'accord - ce n'est pas idéal, mais c'est juste quelque chose que nous avoir à vivre avec et travailler autour.
Eh bien, en bref, le concepteur est le genre de gars qui aime les constructeurs sans paramètres. Donc, au meilleur de ma connaissance, si vous voulez vraiment utiliser des constructeurs basés sur des paramètres, vous êtes probablement coincé avec le contourner d'une manière ou d'une autre.
Fournissez un constructeur sans paramètre pour le 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 pas clairement. Le concepteur doit exécuter le code dans InitializeComponent () et cela ne peut pas fonctionner si le constructeur est privé. Désolé à ce sujet. Il Ne fonctionne pour les formulaires, cependant.
Faites simplement 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();
}
}
Alors le constructeur 'réel' peut agir en conséquence.
Cela fait un certain temps que la question a été posée, mais peut-être que mon approche est utile à quelqu'un.
Personnellement, je préfère aussi utiliser les Constructeurs paramétrés pour éviter d'oublier de définir une certaine propriété.
Donc, au lieu d'utiliser le constructeur réel, je définis simplement un public void PostConstructor où toutes les choses sont mises que vous mettriez normalement dans le constructeur. Ainsi, le constructeur réel de UserControl ne contient toujours que InitializeComponent () . De cette façon, vous n'avez pas besoin d'ajuster votre paradigme de programmation préféré aux besoins de VisualStudios pour exécuter correctement le concepteur. Pour que ce schéma de programmation fonctionne, il doit être suivi tout en bas.
En pratique, ce PostConstructionalizm ressemblerait un peu à ceci: Commençons par un contrôle au bas de votre hiérarchie D'appels UserControl.
public partial class ChildControl : UserControl
{
public ChildControl()
{
InitializeComponent();
}
public void PostConstructor(YourParameters[])
{
//setting parameters/fillingdata into form
}
}
Donc, un UserControl contenant le ChildControl ressemblerait à quelque chose comme que:
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[]);
}
}
J'ai un moyen de le contourner.
- créez un contrôle A sur le formulaire avec le constructeur sans paramètre.
- créez un contrôle B avec un constructeur paramétré sous la forme contstructor.
- copier la position et la taille de A à B.
- Faire un invisible.
- Ajouter B au parent de A.
J'espère que cela aidera. Je viens de rencontrer la même question et j'ai 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 serait d'utiliser reflect pour copier les propriétés une par une de A à B. Pour le moment, je suis occupé et je ne le fais pas. Peut-être qu'à l'avenir, je reviendrai avec le code. Mais ce n'est pas si difficile et je crois que vous pouvez le faire vous-même.
J'ai eu un problème similaire en essayant de passer un objet créé dans le formulaire Windows principal à un formulaire UserControl personnalisé. Ce qui a fonctionné pour moi était d'ajouter une propriété avec une valeur par défaut à UserControl.Designer.cs et le mettre à jour après L'appel InitializeComponent() dans le formulaire principal. Ayant une valeur par défaut empêche WinForms concepteur de lancer un "Objet de référence non définie à une instance d'un objet" erreur.
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
...
}
Espérons que cela aide!