[C#] DesignOnly Constructor

Je suis en train de créer plusieurs custom controls qui doivent être instanciés en leur passant un objet dans le constructeur.

Je ne veux donc pas avoir de constructeur par défaut sans paramètre, le problème c’est que si je fais ça, le designer de VS n’arrive plus à se lancer parce qu’il l’utilise.

Du coup, j’ai été obligé d’implémenter un constructeur par défaut sans paramètres mais qui ne sert que pour le designer.

Est-ce qu’il y a un moyen de dire que ce constructeur ne doit pas être utilisé autrement que par le designer? J’ai trouvé le paramètre [DesignOnly(true)] mais je suis pas certain que ce soit pour ce genre de choses et ça ne semble fonctionner que sur des properties.

Bref, est-ce que je fais fausse route ou est-ce qu’il y a un moyen?

Non, DesignOnly ne sert qu’aux propriétés (je pense que l’attribut possède cette contrainte).

Ton premier problème est qu’effectivement, tu ne pourras pas utiliser le designer pour concevoir ton contrôle (tu pourrais toujours feinter en laissant le constructeur, et une fois ton contrôle terminé, déplacer l’appel de InitializeComponent() au bon endroit et supprimer le constructeur par défaut).

Mais, malgré ça, tu auras un autre problème ensuite : si tu glisses ton contrôle dans un formulaire, le designer ne sait pas faire autre chose qu’appeller le constructeur par défaut. Donc t’es cuit : il te faudra ajouter le usercontrol à la mano dans tous tes formulaires.

Si tu as bien cette contrainte en tête, la magouille d’utiliser le designer puis supprimer le constructeur vide peut marcher.

En passant, un workaround mignon pour ceux qui aimeraient avoir des usercontrol/form en abstract est décrit dans cet article, avec une bonne explication sur les raisons qui font que le designer fonctionne ainsi.

public MonControle() { if(!DesignMode) { throw new NotSupportedException(); } }

?

[quote=“bishop, post:3, topic: 46758”]public MonControle() { if(!DesignMode) { throw new NotSupportedException(); } }[/quote]
Nope, marchera pas, la propriété DesignMode n’est pas assignée avec la correcte valeur à ce moment là… de toutes façons, ça serait impossible à faire: comment le designer pourrait-il indiquer à l’objet qu’il est en design mode alors que ce dernier est encore en train d’être construit?
Généralement, il vaut mieux ne rien mettre dans le constructeur, à part des trucs vraiment de base, et mettre son code “de production” (exclu avec un if(!DesignMode)) dans le OnLoad (en plus ça a d’autres avantages de faire comme ça).

Mais alors dans ce cas comment tu fais si tu dois passer au contrôle un objet qu’il devra par la suite utiliser?

Dans mon cas, j’ai un contrôleur d’UI par qui doit passer le contrôle pour envoyer des messages. Typiquement appeler des méthodes sur ce contrôle qui vont par la suite modifier d’autres contrôles dans ma Form, mais aussi pour aller chercher des informations qu’il a besoin pour afficher les informations qu’il contient.

Je crée ce contrôle dans le contrôleur et le moyen le plus logique de passer l’information au contrôle c’était de lui passer dans le constructeur en faisant un « new MonControle(this); ». Comme ça je suis sûr qu’un contrôle de ce type ne peut pas être instancié sans connaître le contrôleur auquel il appartient.

Mais du coup, à cause du Designer, je suis obligé d’avoir un constructeur sans paramètres, et s’il est utilisé ça va être la fête au NullPointerException :).

2 petites choses…
Il me semble que le designer est capable d’invoker des constructeurs privés (pas vérifiable, car pas de VS sous la main maintenant). Si c’est bien le cas, tu mets le constructeur avec InitializeComponent en privé, et dans ton constructeur tu déclare:
public MyControl(Controller ctrl) : this()
{
}

Ensuite, un modèle ultra-simple: Tu ajoutes à ton contrôle une propriété read / write pour le controlleur, et sur le OnLoad, tu fais un test genre :
if (this.Controller == null && !DesignMode)

2 petites choses…
Il me semble que le designer est capable d’invoker des constructeurs privés (pas vérifiable, car pas de VS sous la main maintenant). Si c’est bien le cas, tu mets le constructeur avec InitializeComponent en privé, et dans ton constructeur tu déclare:
public MyControl(Controller ctrl) : this()
{
}

Ensuite, un modèle ultra-simple: Tu ajoutes à ton contrôle une propriété read / write pour le controlleur, et sur le OnLoad, tu fais un test genre :
if (this.Controller == null && !DesignMode)
throw new InvalidOperationException(“The control cannot be loaded without an associated controller”);

Le OnLoad n’est appelé qu’au moment ou ton contrôle entre dans l’arbre visuel.

Nope, ca me paraitrait douteux puisque tout ce que fait VS c’est de generer du code (dans le fichier FormName.designer.cs), avec une ligne de code qui appelle le constructeur par defaut. Donc si le constructeur par defaut est prive, ca ne compilera pas… enfin a moins qu’il y ait un truc plus complique dans l’histoire, mais bon…

Sinon, oui, comme le suggere girafologue, une solution est de tester la validite du controlleur dans le OnLoad. Demander aux clients de faire un form.Controller = myCtrl; avant d’afficher le formulaire est raisonnable, et comme ca y’aura pas de probleme de NullReferenceException. L’autre solution c’est d’utiliser des Dependency Injection Frameworks…

J’ai essayé en vitesse, effectivement la propriété DesignMode n’est pas assignée. De même, avec on peut effectivement mettre un constructeur privé mais ca ne marche pas mieux, vu que le Designer ne le voit pas.

Sinon girafologue m’a inspiré une autre idée, en partant à l’inverse de ce qu’il proposait :

[code]public class MonControle
{
public MonControl(string tutu, int kikoo /* plus tous tes params de construction*/)
{

	   }

	   public MonControl() : this("kikoolol", 42 /* plus tous les parametres par défaut */) { }

}[/code]

[quote=“bishop, post:9, topic: 46758”][code]public class MonControle
{
public MonControl(string tutu, int kikoo /* plus tous tes params de construction*/)
{

	   }

	   public MonControl() : this("kikoolol", 42 /* plus tous les parametres par défaut */) { }

}[/code][/quote]

Ouais, j’ai pensé à ce genre de chose aussi, mais dans mon cas les paramètres par défaut n’auraient pas vraiment de sens.

Je pense que je vais partir sur la solution d’avoir un constructeur par défaut qui ne fait que d’initialiser les composants et de vérifier dans le OnLoad() que l’utilisateur a bien passé un UIController à mon contrôle.

J’ai testé sous VS 2008, le constructeur privé n’a pas l’air de le gèner (pas de 2005 sous la main…), ce qui ne m’étonne guère, car il invoque le constructeur par réflection (et qu’on peut tout à fait récupérer un constructeur privé et l’éxécuter via reflection).

On est par contre bien d’accord qu’aucun formulaire ne pourra utiliser cet usercontrol dans le designer, étant donné qu’il ne sait qu’appeller le constructeur par défaut.

Ca ne semble pas être une problème dans ce cas là, vu qu’il est question d’instancier les contrôles via un controlleur spécifique.