Composant non graphique qui détecte sur quelle form il a été posé

Contenu du snippet

Pour ceux qui ont déjà essayé de créer des composants ergonomiques, vous savez certainement qu'il en existe de plusieurs types :
- Les graphiques, qui ajoutent un contenu visuel à l'interface utilisateur, et qui dérivent de la classe UserControl. Par exemple, un bouton fait partie de cette catégorie de composants.
- Les non graphiques, qui ajoutent des fonctionnalités à votre logiciel, et qui la plus part du temps n'affichent rien de spécial. Le timer en est un bon exemple. Le NotifyIcon fait aussi partie de cette catégorie, et bien qu'il permette l'affichage d'une icône à côté de l'heure, il n'ajoute pas de contenu visuel à votre page.
- Et d'autres encore, comme les controles conteneurs, ...

Partons du principe que je veuille faire un composant qui, lorsque je modifie sa propriété "Couleur", modifie la propriété BackColor de ma form. Nous sommes d'accord : Le composant n'affiche rien de spécial dans la form, mais met simplement à jour une propriété de la form en elle même. Le composant a besoin de savoir sur qu'elle form il doit travailler, mais je n'ai pas envie de le spécifier à celui-ci quand je l'utilise : je considère que déposer un composant sur une form est assez explicite pour lui dire sur quelle form travailler.

En créant un UserControl, il est facile de récupérer la form parent. Ceci ce fait par un simple this.ParentForm. OR, dans le cas présent, nous voulons dériver de la classe System.ComponentModel.Component, puisque le composant n'est pas graphique. "this.ParentForm" n'est alors plus accessible ! Comment faire pour savoir sur quelle form a été posée notre contrôle ? Et bien pour répondre à cette question, je vous propose de regarder le code fourni ici.

Explications :
Tout se joue au moment du design ! Rien n'est détecté au moment de l'exécution !!! En fait, lorsque je dépose le composant que j'ai créé sur une form, le designer tente de récupérer toutes les valeurs des propriétés de mon composant (en tout cas celles dont une variable par défaut n'est pas définie, mais c'est une autre histoire), puis génère des lignes de code dans le InitializeComponent. Les lignes de code générées sont de type :
[NomDuComposant].[Propriété] = [ValeurRetournéeParLeGetDeLaPropriété];

Dans mon get, je vais alors chercher au moyen du DesignerHost (qui fournit un point d'accès au designer) un truc qui s'appèle le RootComponent. Je ne fais ceci que lorsque je suis en mode design. Ainsi, lorsque je pose mon composant ChangeCouleur (c'est con nom) sur ma form, le designer ajoute la ligne de code suivante dans le InitializeComponent :
//
// changeCouleur1
//
this.changeCouleur1.ParentForm = this;

Et voilà, c'est gagné ! La solution est plus que chelou, mais c'est celle qui est utilisée par exemple pour le timer, qui a semble-t-il besoin d'un handle vers la fenêtre sur laquelle il a été posé.

Source / Exemple :


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel.Design;

namespace FreshNotes
{
    public class ChangeCouleur : Component
    {
        private Form _ParentForm; // Page sur laquelle a été déposée le contrôle

        /// <summary>
        /// Permet de définir/récupérer le contrôle parent
        /// </summary>
        [Browsable(false)]
        public Form ParentForm
        {
            // Accesseur en lecture
            get
            {
                // Si l'on est en mode design, alors on demande au designer de définir la propriété ParentForm avec
                // le this de la form contenant l'instance du composant.
                if (this.Site.DesignMode)
                {
                    IDesignerHost dh = (IDesignerHost)(this.GetService(typeof(IDesignerHost)));
                    if (dh != null)
                    {
                        object obj = dh.RootComponent;
                        if (obj != null)
                        {
                            _ParentForm = (Form)obj;
                        }
                    }
                }
                return _ParentForm;
            }

            // Accesseur en écriture
            set
            {
                if (value != null) _ParentForm = value;
            }
        }

        /// <summary>
        /// Ma propriété couleur
        /// </summary>
        public Color Couleur
        {
            set {
                if (_ParentForm == null) return;
                _ParentForm.BackColor = value;
            }
            get { return _ParentForm.BackColor; }
        }

        /// <summary>
        /// Constructeur
        /// </summary>
        public ChangeCouleur()
        {
        }
    }
}

Conclusion :


Pour info, je n'ai pas trouvé cette astuce tout seul, mais j'ai trouvé un excélent article qui décrit la méthode en détail :
http://www.code-magazine.com/articleprint.aspx?quickid=0401091&printmode=true

Le gars (Ken Getz), a réussi à trouver l'astuce en décompilant le composant Timer.

On sent quand même ici une petite lacune du framework (le 1 comme le 2), et j'ai l'impression que même les mecs de Microsoft qui ont développé le Timer ont dû contourner cette lacune :D

NOTE IMPORTANTE : Merci de ne pas faire de commentaire du genre, je n'ai pas besoin de faire un composant qui modifie la couleur de ma form, je peux très bien manipuler le BackColor de ma form directement. Oui, OK, je suis d'accord avec ça, mais il n'empèche, dans certains cas, cette méthode s'avère utile. Par exemple, en ce moment, j'essaye de développer un composant non graphique qui ajoute un bouton Tray dans la barre de titre de la form sur laquelle il est posé. Et bien mon composant à besoin du ParentForm, et c'est ici que mon petit bout de code peut m'être utile ;-) Autre info, le dit composant ne peut pas dériver de UserControl, puisque je n'ajoute pas de contenu graphique dans le corps de la fenêtre mais plutôt dans la barre de titre, ce qui est totallement différent, puisqu'un UserControl ne peut pas s'y loger.

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.