Procédure pas à pas : création d'un contrôle Windows Forms qui tire parti des fonctionnalités au moment du design de Visual Studio

Mise à jour : novembre 2007

L'expérience au moment du design pour un contrôle personnalisé peut être améliorée en créant un concepteur personnalisé associé.

Cette procédure pas à pas illustre comment créer un concepteur personnalisé pour un contrôle personnalisé. Vous implémenterez un type MarqueeControl et une classe de concepteur associée, du nom MarqueeControlRootDesigner.

Le type MarqueeControl implémente un affichage semblable à un texte défilant de théâtre, avec des lumières animées et du texte qui clignote.

Le concepteur pour ce contrôle interagit avec l'environnement de design pour fournir une expérience au moment du design personnalisée. Avec le concepteur personnalisé, vous pouvez assembler une implémentation de MarqueeControl personnalisée avec des lumières animées et du texte qui clignote dans beaucoup de combinaisons. Vous pouvez utiliser le contrôle assemblé sur un formulaire comme n'importe quel autre contrôle Windows Forms.

Cette procédure pas à pas illustre les tâches suivantes :

  • Création du projet

  • Création d'un projet de bibliothèque de contrôles

  • Référence au projet de contrôle personnalisé

  • Définition d'un contrôle personnalisé et de son concepteur personnalisé

  • Création d'une instance de votre contrôle personnalisé

  • Configuration du projet pour le débogage au moment du design

  • Implémentation de votre contrôle personnalisé

  • Création d'un contrôle enfant pour votre contrôle personnalisé

  • Création du contrôle enfant MarqueeBorder

  • Création d'un concepteur personnalisé pour masquer et filtrer les propriétés

  • Gestion des modifications des composants

  • Ajout de verbes de concepteur à votre concepteur personnalisé

  • Création d'un CustomUITypeEditor

  • Test de votre contrôle personnalisé dans le concepteur

Lorsque vous avez terminé, votre contrôle personnalisé doit ressembler à ce qui suit :

Arrangement possible de MarqueeControl

Pour la liste de code complète, consultez Comment : créer un contrôle Windows Forms qui bénéficie des fonctionnalités au moment du design.

Remarque :

Selon vos paramètres actifs ou votre édition, les boîtes de dialogue et les commandes de menu que vous voyez peuvent différer de celles qui sont décrites dans l'aide. Pour modifier vos paramètres, sélectionnez Importation et exportation de paramètres dans le menu Outils. Pour plus d'informations, consultez Paramètres Visual Studio.

Composants requis

Pour exécuter cette procédure pas à pas, vous devrez :

  • Avoir des autorisations suffisantes pour être en mesure de créer et d'exécuter des projets d'application Windows Forms sur l'ordinateur où Visual Studio est installé.

Création du projet

La première étape consiste à créer le projet d'application. Vous utiliserez ce projet pour générer l'application qui héberge le contrôle personnalisé.

Pour créer le projet

Création d'un projet de bibliothèque de contrôles

La première étape consiste à créer le projet de bibliothèque de contrôles. Vous créerez un nouveau contrôle personnalisé et son concepteur personnalisé correspondant.

Pour créer le projet de bibliothèque de contrôles

  1. Ajoutez un projet de bibliothèque de contrôles Windows à la solution. Pour plus d'informations, consultez Ajouter un nouveau projet, boîte de dialogue. Nommez le projet "MarqueeControlLibrary".

  2. À l'aide de l'Explorateur de solutions, supprimez le contrôle par défaut du projet en supprimant le fichier source nommé "UserControl1.cs" ou "UserControl1.vb", selon le langage de votre choix. Pour plus d'informations, consultez Comment : supprimer et exclure des éléments.

  3. Ajoutez un nouvel élément UserControl au projet MarqueeControlLibrary. Donnez le nom de base "MarqueeControl" au nouveau fichier source.

  4. À l'aide de l'Explorateur de solutions, créez un nouveau dossier dans le projet MarqueeControlLibrary. Pour plus d'informations, consultez Comment : ajouter de nouveaux éléments de projet. Nommez le nouveau dossier "Design".

  5. Cliquez avec le bouton droit sur le dossier Design et ajoutez une nouvelle classe. Donnez le nom de base "MarqueeControlRootDesigner" au fichier source.

  6. Comme vous devrez utiliser des types de l'assembly System.Design, ajoutez cette référence au projet MarqueeControlLibrary. Pour plus d'informations, consultez Comment : ajouter et supprimer des références dans Visual Studio (C#).

Référence au projet de contrôle personnalisé

Vous utiliserez le projet MarqueeControlTest pour tester le contrôle personnalisé. Le projet de test sera informé de l'existence du contrôle personnalisé lorsque vous ajouterez une référence de projet à l'assembly MarqueeControlLibrary.

Pour référencer le projet de contrôle personnalisé

  • Dans le projet MarqueeControlTest, ajoutez une référence de projet à l'assembly MarqueeControlLibrary. Veillez à utiliser l'onglet Projets de la boîte de dialogue Ajouter une référence au lieu de référencer directement l'assembly MarqueeControlLibrary.

Définition d'un contrôle personnalisé et de son concepteur personnalisé

Votre contrôle personnalisé dérivera de la classe UserControl. Cela permet à votre contrôle de contenir d'autres contrôles, et donne une grande quantité de fonctionnalités par défaut à votre contrôle.

Votre contrôle personnalisé disposera d'un concepteur personnalisé associé. Cela vous permet de créer une expérience de conception unique spécifiquement adaptée à votre contrôle personnalisé.

Vous associez le contrôle à son concepteur en utilisant la classe DesignerAttribute. Dans la mesure où vous développez entièrement le comportement au moment du design de votre contrôle personnalisé, le concepteur personnalisé implémentera l'interface IRootDesigner.

Pour définir un contrôle personnalisé et son concepteur personnalisé

  1. Ouvrez le fichier source MarqueeControl dans l'éditeur de code. En haut du fichier, importez les espaces de noms suivants :

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  2. Ajoutez DesignerAttribute à la déclaration de classe MarqueeControl. Cela associe le contrôle personnalisé à son concepteur.

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
     [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
        public class MarqueeControl : UserControl
        {
    
  3. Ouvrez le fichier source MarqueeControlRootDesigner dans l'éditeur de code. En haut du fichier, importez les espaces de noms suivants :

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing.Design
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing.Design;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  4. Modifiez la déclaration de MarqueeControlRootDesigner pour hériter de la classe DocumentDesigner. Appliquez l'ToolboxItemFilterAttribute pour spécifier l'interaction du concepteur avec la Boîte à outils.

    Remarque   La définition de la classe MarqueeControlRootDesigner a été englobée dans un espace de noms appelé "MarqueeControlLibrary.Design". Cette déclaration place le concepteur dans un espace de noms spécial réservé aux types connexes au design.

    Namespace MarqueeControlLibrary.Design
    
        <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
        ToolboxItemFilterType.Require), _
        ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
        ToolboxItemFilterType.Require)> _
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeControlRootDesigner
            Inherits DocumentDesigner
    
    namespace MarqueeControlLibrary.Design
    {
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeControlRootDesigner : DocumentDesigner
        {
    
  5. Définissez le constructeur pour la classe MarqueeControlRootDesigner. Insérez une instruction WriteLine dans le corps du constructeur. Cela sera utile à des fins de débogage.

    Public Sub New()
        Trace.WriteLine("MarqueeControlRootDesigner ctor")
    End Sub
    
    public MarqueeControlRootDesigner()
    {
        Trace.WriteLine("MarqueeControlRootDesigner ctor");
    }
    

Création d'une instance de votre contrôle personnalisé

Pour observer le comportement personnalisé au moment du design de votre contrôle, vous placerez une instance de votre contrôle sur le formulaire dans le projet MarqueeControlTest.

Pour créer une instance de votre contrôle personnalisé

  1. Ajoutez un nouvel élément UserControl au projet MarqueeControlTest. Donnez le nom de base "DemoMarqueeControl" au nouveau fichier source.

  2. Ouvrez le fichier DemoMarqueeControl dans l'éditeur de code. En haut du fichier, importez l'espace de noms MarqueeControlLibrary :

Imports MarqueeControlLibrary
using MarqueeControlLibrary;
  1. Modifiez la déclaration de DemoMarqueeControl pour hériter de la classe MarqueeControl.

  2. Générez le projet.

  3. Ouvrez Form1 dans le Concepteur Windows Forms.

  4. Recherchez l'onglet Composants MarqueeControlTest dans la boîte à outils et ouvrez-le. Faites glisser un contrôle DemoMarqueeControl de la Boîte à outils jusqu'à votre formulaire.

  5. Générez le projet.

Configuration du projet pour le débogage au moment du design

Lorsque vous développez une expérience personnalisée au moment du design, il est nécessaire de déboguer vos contrôles et composants. Il existe une façon simple de définir votre projet pour autoriser le débogage au moment du design. Pour plus d'informations, consultez Procédure pas à pas : débogage des contrôles Windows Forms personnalisés au moment du design.

Pour configurer le projet pour le débogage au moment du design

  1. Cliquez avec le bouton droit sur le projet MarqueeControlLibrary, puis sélectionnez Propriétés.

  2. Dans la boîte de dialogue "Pages de propriétés MarqueeControlLibrary", sélectionnez la page Propriétés de configuration.

  3. Dans la section Action de démarrage, sélectionnez Démarrer le programme externe. Vous déboguerez une instance séparée de Visual Studio ; cliquez alors sur le bouton de sélection (Capture d'écran VisualStudioEllipsesButton) pour rechercher l'IDE de Visual Studio. Le nom du fichier exécutable est devenv.exe et, si vous avez effectué l'installation dans l'emplacement par défaut, son chemin d'accès est %programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.

  4. Cliquez sur OK pour fermer la boîte de dialogue.

  5. Cliquez avec le bouton droit sur le projet MarqueeControlLibrary et sélectionnez "Définir comme projet de démarrage" pour activer cette configuration de débogage.

Point de contrôle

Vous êtes maintenant prêt à déboguer le comportement au moment du design de votre contrôle personnalisé. Une fois que vous avez déterminé que l'environnement de débogage est installé correctement, vous testerez l'association entre le contrôle personnalisé et le concepteur personnalisé.

Pour tester l'environnement de débogage et l'association avec le concepteur

  1. Ouvrez le fichier source MarqueeControlRootDesigner dans l'éditeur de code, puis placez un point d'arrêt sur l'instruction WriteLine.

  2. Appuyez sur F5 pour démarrer la session de débogage. Notez qu'une nouvelle instance de Visual Studio est créée.

  3. Dans la nouvelle instance de Visual Studio, ouvrez la solution "MarqueeControlTest." Vous pouvez rechercher facilement la solution en sélectionnant Projets récents dans le menu Fichier. Le fichier solution "MarqueeControlTest.sln" sera répertorié comme étant le dernier fichier utilisé.

  4. Ouvrez le DemoMarqueeControl dans le concepteur. Notez que l'instance de débogage de Visual Studio acquiert le focus et l'exécution s'arrête à votre point d'arrêt. Appuyez sur F5 pour poursuivre la session de débogage.

À ce stade, tout est en place pour vous permettre de développer et déboguer votre contrôle personnalisé et son concepteur personnalisé associé. Le reste de cette procédure pas à pas portera sur les détails d'implémentation des fonctionnalités du contrôle et de son concepteur.

Implémentation de votre contrôle personnalisé

Le MarqueeControl est un UserControl avec un petit peu de personnalisation. Il expose deux méthodes : Start, qui démarre l'animation de texte défilant et Stop, qui arrête l'animation. Dans la mesure où le MarqueeControl contient les contrôles enfants qui implémentent l'interface IMarqueeWidget, Start et Stop énumèrent chaque contrôle enfant et appellent les méthodes StartMarquee et StopMarquee, respectivement, sur chaque contrôle enfant qui implémente IMarqueeWidget.

L'apparence des contrôles MarqueeBorder et MarqueeText est dépendante de la disposition, ce qui fait que MarqueeControl substitue la méthode OnLayout et appelle PerformLayout sur les contrôles enfants de ce type.

Il s'agit de l'étendue des personnalisations de MarqueeControl. Les fonctionnalités runtime sont implémentées par les contrôles MarqueeBorder et MarqueeText, alors que les fonctionnalités au moment du design sont implémentées par les classes MarqueeBorderDesigner et MarqueeControlRootDesigner.

Pour implémenter votre contrôle personnalisé

  1. Ouvrez le fichier source MarqueeControl dans l'éditeur de code. Implémentez les méthodes Start et Stop.

    Public Sub Start()
        ' The MarqueeControl may contain any number of 
        ' controls that implement IMarqueeWidget, so 
        ' find each IMarqueeWidget child and call its
        ' StartMarquee method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StartMarquee()
            End If
        Next cntrl
    End Sub
    
    
    Public Sub [Stop]()
        ' The MarqueeControl may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StopMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StopMarquee()
            End If
        Next cntrl
    End Sub
    
    public void Start()
    {
        // The MarqueeControl may contain any number of 
        // controls that implement IMarqueeWidget, so 
        // find each IMarqueeWidget child and call its
        // StartMarquee method.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StartMarquee();
            }
        }
    }
    
    public void Stop()
    {
        // The MarqueeControl may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StopMarquee
        // method.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StopMarquee();
            }
        }
    }
    
  2. Substituez la méthode OnLayout.

    Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs)
        MyBase.OnLayout(levent)
    
        ' Repaint all IMarqueeWidget children if the layout 
        ' has changed.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                cntrl.PerformLayout()
            End If
        Next cntrl
    End Sub
    
    protected override void OnLayout(LayoutEventArgs levent)
    {
        base.OnLayout (levent);
    
        // Repaint all IMarqueeWidget children if the layout 
        // has changed.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                Control control = cntrl as Control; 
    
                control.PerformLayout();
            }
        }
    }
    

Création d'un contrôle enfant pour votre contrôle personnalisé

Le MarqueeControl hébergera deux types de contrôle enfant : le contrôle MarqueeBorder et le contrôle MarqueeText.

  • MarqueeBorder : ce contrôle peint une bordure de « lumières » autour de ses bords. Les lumières clignotent dans l'ordre, d'où l'impression qu'elles se déplacent autour de la bordure. La vitesse à laquelle les mémoires clignotent est contrôlée par une propriété appelée UpdatePeriod. Plusieurs autres propriétés personnalisées déterminent les autres aspects de l'apparence du contrôle. Deux méthodes, appelées StartMarquee et StopMarquee, déterminent quand l'animation démarre et s'arrête.

  • MarqueeText : ce contrôle peint une chaîne clignotante. Comme le contrôle MarqueeBorder, la vitesse à laquelle les mémoires clignotent est contrôlée par la propriété UpdatePeriod. Le contrôle MarqueeText a également les méthodes StartMarquee et StopMarquee en commun avec le contrôle MarqueeBorder.

Au moment du design, le MarqueeControlRootDesigner autorise l'ajout de ces deux types de contrôle à un MarqueeControl dans toute combinaison.

Les fonctionnalités communes des deux contrôles sont prises en charge dans une interface appelée IMarqueeWidget. Cela permet au MarqueeControl de découvrir tous les contrôles enfants connexes au texte défilant et de leur réserver un traitement spécial.

Pour implémenter la fonctionnalité d'animation périodique, vous utiliserez des objets BackgroundWorker de l'espace de noms System.ComponentModel. Vous pourriez utiliser des objets Timer, mais lorsque beaucoup d'objets IMarqueeWidget sont présents, le thread d'interface utilisateur seul peut-être incapable de suivre l'animation.

Pour créer un contrôle enfant pour votre contrôle personnalisé

  1. Ajoutez un nouvel élément de classe au projet MarqueeControlLibrary. Donnez le nom de base "IMarqueeWidget" au nouveau fichier source.

  2. Ouvrez le fichier source IMarqueeWidget dans l'éditeur de code et modifiez la déclaration de class en interface :

    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
    
  3. Ajoutez le code suivant à l'interface IMarqueeWidget pour exposer deux méthodes et une propriété qui manipulent l'animation de texte défilant :

    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
       ' This method starts the animation. If the control can 
       ' contain other classes that implement IMarqueeWidget as
       ' children, the control should call StartMarquee on all
       ' its IMarqueeWidget child controls.
       Sub StartMarquee()
    
       ' This method stops the animation. If the control can 
       ' contain other classes that implement IMarqueeWidget as
       ' children, the control should call StopMarquee on all
       ' its IMarqueeWidget child controls.
       Sub StopMarquee()
    
       ' This method specifies the refresh rate for the animation,
       ' in milliseconds.
       Property UpdatePeriod() As Integer
    
    End Interface
    
    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
        // This method starts the animation. If the control can 
        // contain other classes that implement IMarqueeWidget as
        // children, the control should call StartMarquee on all
        // its IMarqueeWidget child controls.
        void StartMarquee();
    
        // This method stops the animation. If the control can 
        // contain other classes that implement IMarqueeWidget as
        // children, the control should call StopMarquee on all
        // its IMarqueeWidget child controls.
        void StopMarquee();
    
        // This method specifies the refresh rate for the animation,
        // in milliseconds.
        int UpdatePeriod
        {
            get;
            set;
        }
    }
    
  4. Ajoutez un nouvel élément Contrôle personnalisé au projet MarqueeControlLibrary. Donnez le nom de base "MarqueeText" au nouveau fichier source.

  5. Faites glisser un composant BackgroundWorker de la Boîte à outils sur votre contrôle MarqueeText. Ce composant permettra au contrôle MarqueeText de se mettre à jour de façon asynchrone.

  6. Dans la fenêtre Propriétés, affectez la valeur true aux propriétés WorkerReportsProgess et WorkerSupportsCancellation du contrôle BackgroundWorker. Ces paramètres permettent au composant BackgroundWorker de déclencher périodiquement l'événement ProgressChanged et d'annuler les mises à jour asynchrones. Pour plus d'informations, consultez BackgroundWorker, composant.

  7. Ouvrez le fichier source MarqueeText dans l'éditeur de code. En haut du fichier, importez les espaces de noms suivants :

    Imports System
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  8. Modifiez la déclaration de MarqueeText pour hériter de Label et implémenter l'interface IMarqueeWidget :

    <ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeText
        Inherits Label
        Implements IMarqueeWidget
    
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    public partial class MarqueeText : Label, IMarqueeWidget
    {
    
  9. Déclarez les variables d'instance qui correspondent aux propriétés exposées et initialisez-les dans le constructeur. Le champ isLit détermine si le texte doit être peint dans la couleur donnée par la propriété LightColor.

    ' When isLit is true, the text is painted in the light color;
    ' When isLit is false, the text is painted in the dark color.
    ' This value changes whenever the BackgroundWorker component
    ' raises the ProgressChanged event.
    Private isLit As Boolean = True
    
    ' These fields back the public properties.
    Private updatePeriodValue As Integer = 50
    Private lightColorValue As Color
    Private darkColorValue As Color
    
    ' These brushes are used to paint the light and dark
    ' colors of the text.
    Private lightBrush As Brush
    Private darkBrush As Brush
    
    ' This component updates the control asynchronously.
    Private WithEvents backgroundWorker1 As BackgroundWorker
    
    
    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()
    
        ' Initialize light and dark colors 
        ' to the control's default values.
        Me.lightColorValue = Me.ForeColor
        Me.darkColorValue = Me.BackColor
        Me.lightBrush = New SolidBrush(Me.lightColorValue)
        Me.darkBrush = New SolidBrush(Me.darkColorValue)
    End Sub 'New
    
    // When isLit is true, the text is painted in the light color;
    // When isLit is false, the text is painted in the dark color.
    // This value changes whenever the BackgroundWorker component
    // raises the ProgressChanged event.
    private bool isLit = true;
    
    // These fields back the public properties.
    private int updatePeriodValue = 50;
    private Color lightColorValue;
    private Color darkColorValue;
    
    // These brushes are used to paint the light and dark
    // colors of the text.
    private Brush lightBrush;
    private Brush darkBrush;
    
    // This component updates the control asynchronously.
    private BackgroundWorker backgroundWorker1;
    
    public MarqueeText()
    {
        // This call is required by the Windows.Forms Form Designer.
        InitializeComponent();
    
        // Initialize light and dark colors 
        // to the control's default values.
        this.lightColorValue = this.ForeColor;
        this.darkColorValue = this.BackColor;
        this.lightBrush = new SolidBrush(this.lightColorValue);
        this.darkBrush = new SolidBrush(this.darkColorValue);
    }
    
  10. Implémenter l'interface IMarqueeWidget.

    Les méthodes StartMarquee et StopMarquee appellent les méthodes RunWorkerAsync et CancelAsync du composant BackgroundWorker pour démarrer et arrêter l'animation.

    Les attributs Category et Browsable sont appliqués à la propriété UpdatePeriod afin qu'elle apparaisse dans une section personnalisée de la fenêtre Propriétés appelée "Texte défilant".

    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' Start the updating thread and pass it the UpdatePeriod.
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub
    
    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' Stop the updating thread.
        Me.backgroundWorker1.CancelAsync()
    End Sub
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod
    
        Get
            Return Me.updatePeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.updatePeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0")
            End If
        End Set
    
    End Property
    
    public virtual void StartMarquee()
    {
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
  11. Implémentez les accesseurs de la propriété. Vous exposerez deux propriétés aux clients : LightColor et DarkColor. Les attributs Category et Browsable sont appliqués à ces propriétés afin qu'elles apparaissent dans une section personnalisée de la fenêtre Propriétés appelée "Texte défilant".

    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color
    
        Get
            Return Me.lightColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If
        End Set
    
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color
    
        Get
            Return Me.darkColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If
        End Set
    
    End Property
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color LightColor
    {
        get
        {
            return this.lightColorValue;
        }
        set
        {
            // The LightColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.lightColorValue.ToArgb() != value.ToArgb())
            {
                this.lightColorValue = value;
                this.lightBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color DarkColor
    {
        get
        {
            return this.darkColorValue;
        }
        set
        {
            // The DarkColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.darkColorValue.ToArgb() != value.ToArgb())
            {
                this.darkColorValue = value;
                this.darkBrush = new SolidBrush(value);
            }
        }
    }
    
  12. Implémentez les gestionnaires pour les événements DoWork et ProgressChanged du composant BackgroundWorker.

    Le gestionnaire d'événements DoWork reste en veille le nombre de millisecondes spécifié par UpdatePeriod, puis déclenche ensuite l'événement ProgressChanged, jusqu'à ce que votre code arrête l'animation en appelant CancelAsync.

    Le gestionnaire d'événements ProgressChanged bascule le texte entre son état clair et sombre pour donner l'apparence d'un clignotement.

    ' This method is called in the worker thread's context, 
    ' so it must not make any calls into the MarqueeText control.
    ' Instead, it communicates to the control using the 
    ' ProgressChanged event.
    '
    ' The only work done in this event handler is
    ' to sleep for the number of milliseconds specified 
    ' by UpdatePeriod, then raise the ProgressChanged event.
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
        ' This event handler will run until the client cancels
        ' the background task by calling CancelAsync.
        While Not worker.CancellationPending
            ' The Argument property of the DoWorkEventArgs
            ' object holds the value of UpdatePeriod, which 
            ' was passed as the argument to the RunWorkerAsync
            ' method. 
            Thread.Sleep(Fix(e.Argument))
    
            ' The DoWork eventhandler does not actually report
            ' progress; the ReportProgress event is used to 
            ' periodically alert the control to update its state.
            worker.ReportProgress(0)
        End While
    End Sub
    
    
    ' The ProgressChanged event is raised by the DoWork method.
    ' This event handler does work that is internal to the
    ' control. In this case, the text is toggled between its
    ' light and dark state, and the control is told to 
    ' repaint itself.
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
        Me.isLit = Not Me.isLit
        Me.Refresh()
    End Sub
    
    // This method is called in the worker thread's context, 
    // so it must not make any calls into the MarqueeText control.
    // Instead, it communicates to the control using the 
    // ProgressChanged event.
    //
    // The only work done in this event handler is
    // to sleep for the number of milliseconds specified 
    // by UpdatePeriod, then raise the ProgressChanged event.
    private void backgroundWorker1_DoWork(
        object sender,
        System.ComponentModel.DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
    
        // This event handler will run until the client cancels
        // the background task by calling CancelAsync.
        while (!worker.CancellationPending)
        {
            // The Argument property of the DoWorkEventArgs
            // object holds the value of UpdatePeriod, which 
            // was passed as the argument to the RunWorkerAsync
            // method. 
            Thread.Sleep((int)e.Argument);
    
            // The DoWork eventhandler does not actually report
            // progress; the ReportProgress event is used to 
            // periodically alert the control to update its state.
            worker.ReportProgress(0);
        }
    }
    
    // The ProgressChanged event is raised by the DoWork method.
    // This event handler does work that is internal to the
    // control. In this case, the text is toggled between its
    // light and dark state, and the control is told to 
    // repaint itself.
    private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        this.isLit = !this.isLit;
        this.Refresh();
    }
    
    
  13. Substituez la méthode OnPaint pour activer l'animation.

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        ' The text is painted in the light or dark color,
        ' depending on the current value of isLit.
        Me.ForeColor = IIf(Me.isLit, Me.lightColorValue, Me.darkColorValue)
    
        MyBase.OnPaint(e)
    End Sub
    
    protected override void OnPaint(PaintEventArgs e)
    {
        // The text is painted in the light or dark color,
        // depending on the current value of isLit.
        this.ForeColor =
            this.isLit ? this.lightColorValue : this.darkColorValue;
    
        base.OnPaint(e);
    }
    
  14. Appuyez sur F6 pour générer la solution.

Création du contrôle enfant MarqueeBorder

Le contrôle MarqueeBorder est légèrement plus sophistiqué que le contrôle MarqueeText. Il a plus de propriétés et l'animation dans la méthode OnPaint est plus impliquée. En principe, il est assez semblable au contrôle MarqueeText.

Dans la mesure où le contrôle MarqueeBorder peut avoir des contrôles enfants, il doit être informé des événements Layout.

Pour créer le contrôle MarqueeBorder

  1. Ajoutez un nouvel élément Contrôle personnalisé au projet MarqueeControlLibrary. Donnez le nom de base "MarqueeBorder" au nouveau fichier source.

  2. Faites glisser un composant BackgroundWorker de la Boîte à outils sur votre contrôle MarqueeBorder. Ce composant permettra au contrôle MarqueeBorder de se mettre à jour de façon asynchrone.

  3. Dans la fenêtre Propriétés, affectez la valeur true aux propriétés WorkerReportsProgess et WorkerSupportsCancellation du contrôle BackgroundWorker. Ces paramètres permettent au composant BackgroundWorker de déclencher périodiquement l'événement ProgressChanged et d'annuler les mises à jour asynchrones. Pour plus d'informations, consultez BackgroundWorker, composant.

  4. Dans la fenêtre Propriétés, cliquez sur le bouton Événements. Associez des gestionnaires aux événements DoWork et ProgressChanged.

  5. Ouvrez le fichier source MarqueeBorder dans l'éditeur de code. En haut du fichier, importez les espaces de noms suivants :

    Imports System
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Drawing.Design
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  6. Modifiez la déclaration de MarqueeBorder pour hériter de Panel et implémenter l'interface IMarqueeWidget.

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _
    ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeBorder
        Inherits Panel
        Implements IMarqueeWidget
    
    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {
    
  7. Déclarez deux énumérations pour gérer l'état du contrôle MarqueeBorder : MarqueeSpinDirection qui détermine la direction dans laquelle les lumières « tournent » autour de la bordure et MarqueeLightShape, qui détermine la forme des lumières (carrée ou circulaire). Placez ces déclarations avant la déclaration de classe MarqueeBorder.

    ' This defines the possible values for the MarqueeBorder
    ' control's SpinDirection property.
    Public Enum MarqueeSpinDirection
       CW
       CCW
    End Enum
    
    ' This defines the possible values for the MarqueeBorder
    ' control's LightShape property.
    Public Enum MarqueeLightShape
        Square
        Circle
    End Enum
    
    // This defines the possible values for the MarqueeBorder
    // control's SpinDirection property.
    public enum MarqueeSpinDirection
    {
        CW,
        CCW
    }
    
    // This defines the possible values for the MarqueeBorder
    // control's LightShape property.
    public enum MarqueeLightShape
    {
        Square,
        Circle
    }
    
  8. Déclarez les variables d'instance qui correspondent aux propriétés exposées et initialisez-les dans le constructeur.

    Public Shared MaxLightSize As Integer = 10
    
    ' These fields back the public properties.
    Private updatePeriodValue As Integer = 50
    Private lightSizeValue As Integer = 5
    Private lightPeriodValue As Integer = 3
    Private lightSpacingValue As Integer = 1
    Private lightColorValue As Color
    Private darkColorValue As Color
    Private spinDirectionValue As MarqueeSpinDirection = MarqueeSpinDirection.CW
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
    ' These brushes are used to paint the light and dark
    ' colors of the marquee lights.
    Private lightBrush As Brush
    Private darkBrush As Brush
    
    ' This field tracks the progress of the "first" light as it
    ' "travels" around the marquee border.
    Private currentOffset As Integer = 0
    
    ' This component updates the control asynchronously.
    Private WithEvents backgroundWorker1 As System.ComponentModel.BackgroundWorker
    
    
    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()
    
        ' Initialize light and dark colors 
        ' to the control's default values.
        Me.lightColorValue = Me.ForeColor
        Me.darkColorValue = Me.BackColor
        Me.lightBrush = New SolidBrush(Me.lightColorValue)
        Me.darkBrush = New SolidBrush(Me.darkColorValue)
    
        ' The MarqueeBorder control manages its own padding,
        ' because it requires that any contained controls do
        ' not overlap any of the marquee lights.
        Dim pad As Integer = 2 * (Me.lightSizeValue + Me.lightSpacingValue)
        Me.Padding = New Padding(pad, pad, pad, pad)
    
        SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
    End Sub
    
    public static int MaxLightSize = 10;
    
    // These fields back the public properties.
    private int updatePeriodValue = 50;
    private int lightSizeValue = 5;
    private int lightPeriodValue = 3;
    private int lightSpacingValue = 1;
    private Color lightColorValue;
    private Color darkColorValue;
    private MarqueeSpinDirection spinDirectionValue = MarqueeSpinDirection.CW;
    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    // These brushes are used to paint the light and dark
    // colors of the marquee lights.
    private Brush lightBrush;
    private Brush darkBrush;
    
    // This field tracks the progress of the "first" light as it
    // "travels" around the marquee border.
    private int currentOffset = 0;
    
    // This component updates the control asynchronously.
    private System.ComponentModel.BackgroundWorker backgroundWorker1;
    
    public MarqueeBorder()
    {
        // This call is required by the Windows.Forms Form Designer.
        InitializeComponent();
    
        // Initialize light and dark colors 
        // to the control's default values.
        this.lightColorValue = this.ForeColor;
        this.darkColorValue = this.BackColor;
        this.lightBrush = new SolidBrush(this.lightColorValue);
        this.darkBrush = new SolidBrush(this.darkColorValue);
    
        // The MarqueeBorder control manages its own padding,
        // because it requires that any contained controls do
        // not overlap any of the marquee lights.
        int pad = 2 * (this.lightSizeValue + this.lightSpacingValue);
        this.Padding = new Padding(pad, pad, pad, pad);
    
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    }
    
  9. Implémenter l'interface IMarqueeWidget.

    Les méthodes StartMarquee et StopMarquee appellent les méthodes RunWorkerAsync et CancelAsync du composant BackgroundWorker pour démarrer et arrêter l'animation.

    Dans la mesure où le contrôle MarqueeBorder peut contenir des contrôles enfants, la méthode StartMarquee énumère tous les contrôles enfants et appelle StartMarquee sur ceux qui implémentent IMarqueeWidget. La méthode StopMarquee a une implémentation semblable.

    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' The MarqueeBorder control may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StartMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StartMarquee()
            End If
        Next cntrl
    
        ' Start the updating thread and pass it the UpdatePeriod.
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub
    
    
    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' The MarqueeBorder control may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StopMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StopMarquee()
            End If
        Next cntrl
    
        ' Stop the updating thread.
        Me.backgroundWorker1.CancelAsync()
    End Sub
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Overridable Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod
    
        Get
            Return Me.updatePeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.updatePeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("UpdatePeriod", _
                "must be > 0")
            End If
        End Set
    
    End Property
    
    public virtual void StartMarquee()
    {
        // The MarqueeBorder control may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StartMarquee
        // method.
        foreach (Control cntrl in this.Controls)
        {
            if (cntrl is IMarqueeWidget)
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StartMarquee();
            }
        }
    
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // The MarqueeBorder control may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StopMarquee
        // method.
        foreach (Control cntrl in this.Controls)
        {
            if (cntrl is IMarqueeWidget)
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StopMarquee();
            }
        }
    
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public virtual int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
    
  10. Implémentez les accesseurs de la propriété. Le contrôle MarqueeBorder a plusieurs propriétés pour contrôler son apparence.

    <Category("Marquee"), Browsable(True)> _
    Public Property LightSize() As Integer
        Get
            Return Me.lightSizeValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 AndAlso Value <= MaxLightSize Then
                Me.lightSizeValue = Value
                Me.DockPadding.All = 2 * Value
            Else
                Throw New ArgumentOutOfRangeException("LightSize", _
                "must be > 0 and < MaxLightSize")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightPeriod() As Integer
        Get
            Return Me.lightPeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.lightPeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("LightPeriod", _
                "must be > 0 ")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color
        Get
            Return Me.lightColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color
        Get
            Return Me.darkColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightSpacing() As Integer
        Get
            Return Me.lightSpacingValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value >= 0 Then
                Me.lightSpacingValue = Value
            Else
                Throw New ArgumentOutOfRangeException("LightSpacing", _
                "must be >= 0")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True), _
    EditorAttribute(GetType(LightShapeEditor), _
    GetType(System.Drawing.Design.UITypeEditor))> _
    Public Property LightShape() As MarqueeLightShape
    
        Get
            Return Me.lightShapeValue
        End Get
    
        Set(ByVal Value As MarqueeLightShape)
            Me.lightShapeValue = Value
        End Set
    
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property SpinDirection() As MarqueeSpinDirection
    
        Get
            Return Me.spinDirectionValue
        End Get
    
        Set(ByVal Value As MarqueeSpinDirection)
            Me.spinDirectionValue = Value
        End Set
    
    End Property
    
    [Category("Marquee")]
    [Browsable(true)]
    public int LightSize
    {
        get
        {
            return this.lightSizeValue;
        }
    
        set
        {
            if (value > 0 && value <= MaxLightSize)
            {
                this.lightSizeValue = value;
                this.DockPadding.All = 2 * value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightSize", "must be > 0 and < MaxLightSize");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int LightPeriod
    {
        get
        {
            return this.lightPeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.lightPeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightPeriod", "must be > 0 ");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color LightColor
    {
        get
        {
            return this.lightColorValue;
        }
    
        set
        {
            // The LightColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.lightColorValue.ToArgb() != value.ToArgb())
            {
                this.lightColorValue = value;
                this.lightBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color DarkColor
    {
        get
        {
            return this.darkColorValue;
        }
    
        set
        {
            // The DarkColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.darkColorValue.ToArgb() != value.ToArgb())
            {
                this.darkColorValue = value;
                this.darkBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int LightSpacing
    {
        get
        {
            return this.lightSpacingValue;
        }
    
        set
        {
            if (value >= 0)
            {
                this.lightSpacingValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightSpacing", "must be >= 0");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    [EditorAttribute(typeof(LightShapeEditor), 
         typeof(System.Drawing.Design.UITypeEditor))]
    public MarqueeLightShape LightShape
    {
        get
        {
            return this.lightShapeValue;
        }
    
        set
        {
            this.lightShapeValue = value;
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public MarqueeSpinDirection SpinDirection
    {
        get
        {
            return this.spinDirectionValue;
        }
    
        set
        {
            this.spinDirectionValue = value;
        }
    }
    
    
  11. Implémentez les gestionnaires pour les événements DoWork et ProgressChanged du composant BackgroundWorker.

    Le gestionnaire d'événements DoWork reste en veille le nombre de millisecondes spécifié par UpdatePeriod, puis déclenche ensuite l'événement ProgressChanged, jusqu'à ce que votre code arrête l'animation en appelant CancelAsync.

    Le gestionnaire d'événements ProgressChanged incrémente la position de la lumière « de base », qui permet de déterminer l'état clair/sombre des autres lumières, et appelle la méthode Refresh pour que le contrôle se repeigne lui-même.

    ' This method is called in the worker thread's context, 
    ' so it must not make any calls into the MarqueeBorder
    ' control. Instead, it communicates to the control using 
    ' the ProgressChanged event.
    '
    ' The only work done in this event handler is
    ' to sleep for the number of milliseconds specified 
    ' by UpdatePeriod, then raise the ProgressChanged event.
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
        ' This event handler will run until the client cancels
        ' the background task by calling CancelAsync.
        While Not worker.CancellationPending
            ' The Argument property of the DoWorkEventArgs
            ' object holds the value of UpdatePeriod, which 
            ' was passed as the argument to the RunWorkerAsync
            ' method. 
            Thread.Sleep(Fix(e.Argument))
    
            ' The DoWork eventhandler does not actually report
            ' progress; the ReportProgress event is used to 
            ' periodically alert the control to update its state.
            worker.ReportProgress(0)
        End While
    End Sub
    
    
    ' The ProgressChanged event is raised by the DoWork method.
    ' This event handler does work that is internal to the
    ' control. In this case, the currentOffset is incremented,
    ' and the control is told to repaint itself.
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
        Me.currentOffset += 1
        Me.Refresh()
    End Sub
    
    // This method is called in the worker thread's context, 
    // so it must not make any calls into the MarqueeBorder
    // control. Instead, it communicates to the control using 
    // the ProgressChanged event.
    //
    // The only work done in this event handler is
    // to sleep for the number of milliseconds specified 
    // by UpdatePeriod, then raise the ProgressChanged event.
    private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
    
        // This event handler will run until the client cancels
        // the background task by calling CancelAsync.
        while (!worker.CancellationPending)
        {
            // The Argument property of the DoWorkEventArgs
            // object holds the value of UpdatePeriod, which 
            // was passed as the argument to the RunWorkerAsync
            // method. 
            Thread.Sleep((int)e.Argument);
    
            // The DoWork eventhandler does not actually report
            // progress; the ReportProgress event is used to 
            // periodically alert the control to update its state.
            worker.ReportProgress(0);
        }
    }
    
    // The ProgressChanged event is raised by the DoWork method.
    // This event handler does work that is internal to the
    // control. In this case, the currentOffset is incremented,
    // and the control is told to repaint itself.
    private void backgroundWorker1_ProgressChanged(
        object sender,
        System.ComponentModel.ProgressChangedEventArgs e)
    {
        this.currentOffset++;
        this.Refresh();
    }
    
  12. Implémentez les méthodes d'assistance, IsLit et DrawLight.

    La méthode IsLit détermine la couleur d'une lumière à une position donnée. Les lumières qui sont allumées sont dessinées dans la couleur indiquée par la propriété LightColor, et celles qui sont « sombres » sont dessinées dans la couleur fournie par la propriété DarkColor.

    La méthode DrawLight dessine une lumière à l'aide de la couleur, de forme et la position appropriée.

    ' This method determines if the marquee light at lightIndex
    ' should be lit. The currentOffset field specifies where
    ' the "first" light is located, and the "position" of the
    ' light given by lightIndex is computed relative to this 
    ' offset. If this position modulo lightPeriodValue is zero,
    ' the light is considered to be on, and it will be painted
    ' with the control's lightBrush. 
    Protected Overridable Function IsLit(ByVal lightIndex As Integer) As Boolean
        Dim directionFactor As Integer = _
        IIf(Me.spinDirectionValue = MarqueeSpinDirection.CW, -1, 1)
    
        Return (lightIndex + directionFactor * Me.currentOffset) Mod Me.lightPeriodValue = 0
    End Function
    
    
    Protected Overridable Sub DrawLight( _
    ByVal g As Graphics, _
    ByVal brush As Brush, _
    ByVal xPos As Integer, _
    ByVal yPos As Integer)
    
        Select Case Me.lightShapeValue
            Case MarqueeLightShape.Square
                g.FillRectangle( _
                brush, _
                xPos, _
                yPos, _
                Me.lightSizeValue, _
                Me.lightSizeValue)
                Exit Select
            Case MarqueeLightShape.Circle
                g.FillEllipse( _
                brush, _
                xPos, _
                yPos, _
                Me.lightSizeValue, _
                Me.lightSizeValue)
                Exit Select
            Case Else
                Trace.Assert(False, "Unknown value for light shape.")
                Exit Select
        End Select
    
    End Sub
    
    // This method determines if the marquee light at lightIndex
    // should be lit. The currentOffset field specifies where
    // the "first" light is located, and the "position" of the
    // light given by lightIndex is computed relative to this 
    // offset. If this position modulo lightPeriodValue is zero,
    // the light is considered to be on, and it will be painted
    // with the control's lightBrush. 
    protected virtual bool IsLit(int lightIndex)
    {
        int directionFactor =
            (this.spinDirectionValue == MarqueeSpinDirection.CW ? -1 : 1);
    
        return (
            (lightIndex + directionFactor * this.currentOffset) % this.lightPeriodValue == 0
            );
    }
    
    protected virtual void DrawLight(
        Graphics g,
        Brush brush,
        int xPos,
        int yPos)
    {
        switch (this.lightShapeValue)
        {
            case MarqueeLightShape.Square:
                {
                    g.FillRectangle(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue);
                    break;
                }
            case MarqueeLightShape.Circle:
                {
                    g.FillEllipse(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue);
                    break;
                }
            default:
                {
                    Trace.Assert(false, "Unknown value for light shape.");
                    break;
                }
        }
    }
    
  13. Substituez les méthodes OnLayout et OnPaint.

    La méthode OnPaint dessine les lumières le long des bords du contrôle MarqueeBorder.

    Dans la mesure où la méthode OnPaint dépend des dimensions du contrôle MarqueeBorder, vous devez l'appeler toutes les fois que la disposition change. Pour accomplir ceci, substituez OnLayout et appelez Refresh.

    Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs)
        MyBase.OnLayout(levent)
    
        ' Repaint when the layout has changed.
        Me.Refresh()
    End Sub
    
    
    ' This method paints the lights around the border of the 
    ' control. It paints the top row first, followed by the
    ' right side, the bottom row, and the left side. The color
    ' of each light is determined by the IsLit method and
    ' depends on the light's position relative to the value
    ' of currentOffset.
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim g As Graphics = e.Graphics
        g.Clear(Me.BackColor)
    
        MyBase.OnPaint(e)
    
        ' If the control is large enough, draw some lights.
        If Me.Width > MaxLightSize AndAlso Me.Height > MaxLightSize Then
            ' The position of the next light will be incremented 
            ' by this value, which is equal to the sum of the
            ' light size and the space between two lights.
            Dim increment As Integer = _
            Me.lightSizeValue + Me.lightSpacingValue
    
            ' Compute the number of lights to be drawn along the
            ' horizontal edges of the control.
            Dim horizontalLights As Integer = _
            (Me.Width - increment) / increment
    
            ' Compute the number of lights to be drawn along the
            ' vertical edges of the control.
            Dim verticalLights As Integer = _
            (Me.Height - increment) / increment
    
            ' These local variables will be used to position and
            ' paint each light.
            Dim xPos As Integer = 0
            Dim yPos As Integer = 0
            Dim lightCounter As Integer = 0
            Dim brush As Brush
    
            ' Draw the top row of lights.
            Dim i As Integer
            For i = 0 To horizontalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                xPos += increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the right edge of the control.
            xPos = Me.Width - Me.lightSizeValue
    
            ' Draw the right column of lights.
            'Dim i As Integer
            For i = 0 To verticalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                yPos += increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the bottom edge of the control.
            yPos = Me.Height - Me.lightSizeValue
    
            ' Draw the bottom row of lights.
            'Dim i As Integer
            For i = 0 To horizontalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                xPos -= increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the left edge of the control.
            xPos = 0
    
            ' Draw the left column of lights.
            'Dim i As Integer
            For i = 0 To verticalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                yPos -= increment
                lightCounter += 1
            Next i
        End If
    End Sub
    
    protected override void OnLayout(LayoutEventArgs levent)
    {
        base.OnLayout(levent);
    
        // Repaint when the layout has changed.
        this.Refresh();
    }
    
    // This method paints the lights around the border of the 
    // control. It paints the top row first, followed by the
    // right side, the bottom row, and the left side. The color
    // of each light is determined by the IsLit method and
    // depends on the light's position relative to the value
    // of currentOffset.
    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        g.Clear(this.BackColor);
    
        base.OnPaint(e);
    
        // If the control is large enough, draw some lights.
        if (this.Width > MaxLightSize &&
            this.Height > MaxLightSize)
        {
            // The position of the next light will be incremented 
            // by this value, which is equal to the sum of the
            // light size and the space between two lights.
            int increment =
                this.lightSizeValue + this.lightSpacingValue;
    
            // Compute the number of lights to be drawn along the
            // horizontal edges of the control.
            int horizontalLights =
                (this.Width - increment) / increment;
    
            // Compute the number of lights to be drawn along the
            // vertical edges of the control.
            int verticalLights =
                (this.Height - increment) / increment;
    
            // These local variables will be used to position and
            // paint each light.
            int xPos = 0;
            int yPos = 0;
            int lightCounter = 0;
            Brush brush;
    
            // Draw the top row of lights.
            for (int i = 0; i < horizontalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                xPos += increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the right edge of the control.
            xPos = this.Width - this.lightSizeValue;
    
            // Draw the right column of lights.
            for (int i = 0; i < verticalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                yPos += increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the bottom edge of the control.
            yPos = this.Height - this.lightSizeValue;
    
            // Draw the bottom row of lights.
            for (int i = 0; i < horizontalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                xPos -= increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the left edge of the control.
            xPos = 0;
    
            // Draw the left column of lights.
            for (int i = 0; i < verticalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                yPos -= increment;
                lightCounter++;
            }
        }
    }
    

Création d'un concepteur personnalisé pour masquer et filtrer les propriétés

La classe MarqueeControlRootDesigner fournit l'implémentation pour le concepteur racine. En plus de ce concepteur, qui fonctionne sur le MarqueeControl, vous aurez besoin d'un concepteur personnalisé qui est associé spécifiquement au contrôle MarqueeBorder. Ce concepteur fournit un comportement personnalisé qui est approprié dans le contexte du concepteur racine personnalisé.

Spécifiquement, le MarqueeBorderDesigner masquera et filtrera certaines propriétés sur le contrôle MarqueeBorder, en modifiant leur interaction avec l'environnement de design.

L'interception des appels destinés à l'accesseur de propriété d'un composant est un phénomène appelé « occultation ». Il permet à un concepteur de suivre la valeur définie par l'utilisateur et, éventuellement, de passer cette valeur au composant qui est conçu.

Pour cet exemple, les propriétés Visible et Enabled seront occultées par le MarqueeBorderDesigner, qui empêche l'utilisateur de rendre le contrôle MarqueeBorder invisible ou désactivé au moment du design.

Les concepteurs peuvent également ajouter et supprimer des propriétés. Pour cet exemple, la propriété Padding sera supprimée au moment du design, parce que le contrôle MarqueeBorder définit par programme le remplissage selon la taille des lumières spécifiées par la propriété LightSize.

La classe de base pour MarqueeBorderDesigner est ComponentDesigner, qui a des méthodes qui peuvent modifier les attributs, les propriétés et les événements exposés par un contrôle au moment du design :

Lorsque vous modifiez l'interface publique d'un composant qui utilise ces méthodes, vous devez suivre ces règles :

  • Ajoutez ou supprimez des éléments uniquement dans les méthodes PreFilter

  • Modifiez des éléments existants uniquement dans les méthodes PostFilter

  • Toujours appelez l'implémentation de base en premier dans les méthodes PreFilter

  • Toujours appelez l'implémentation de base en dernier dans les méthodes PostFilter

En adhérant à ces règles, vous garantissez que tous les concepteurs dans l'environnement au moment du design ont un affichage cohérent de tous les composants qui sont conçus.

La classe ComponentDesigner fournit un dictionnaire pour gérer les valeurs des propriétés occultées, ce qui vous évite d'avoir à créer des variables d'instance spécifiques.

Pour créer un concepteur personnalisé destiné à occulter et filtrer des propriétés

  1. Cliquez avec le bouton droit sur le dossier Design et ajoutez une nouvelle classe. Donnez le nom de base "MarqueeBorderDesigner" au fichier source.

  2. Ouvrez le fichier source MarqueeBorderDesigner dans l'éditeur de code. En haut du fichier, importez les espaces de noms suivants :

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  3. Modifiez la déclaration de MarqueeBorderDesigner pour hériter de ParentControlDesigner.

    Dans la mesure où le contrôle MarqueeBorder peut contenir des contrôles enfants, MarqueeBorderDesigner hérite de ParentControlDesigner, qui gère l'interaction parent-enfant.

    Namespace MarqueeControlLibrary.Design
    
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
    namespace MarqueeControlLibrary.Design
    {
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
  4. Substituez l'implémentation de base de PreFilterProperties.

    Protected Overrides Sub PreFilterProperties( _
    ByVal properties As IDictionary)
    
        MyBase.PreFilterProperties(properties)
    
        If properties.Contains("Padding") Then
            properties.Remove("Padding")
        End If
    
        properties("Visible") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Visible"), PropertyDescriptor), _
        New Attribute(-1) {})
    
        properties("Enabled") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Enabled"), _
        PropertyDescriptor), _
        New Attribute(-1) {})
    
    End Sub
    
    protected override void PreFilterProperties(IDictionary properties)
    {
        base.PreFilterProperties(properties);
    
        if (properties.Contains("Padding"))
        {
            properties.Remove("Padding");
        }
    
        properties["Visible"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Visible"],
            new Attribute[0]);
    
        properties["Enabled"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Enabled"],
            new Attribute[0]);
    }
    
  5. Implémentez les propriétés Enabled et Visible. Ces implémentations occultent les propriétés du contrôle.

    Public Property Visible() As Boolean
        Get
            Return CBool(ShadowProperties("Visible"))
        End Get
        Set(ByVal Value As Boolean)
            Me.ShadowProperties("Visible") = Value
        End Set
    End Property
    
    
    Public Property Enabled() As Boolean
        Get
            Return CBool(ShadowProperties("Enabled"))
        End Get
        Set(ByVal Value As Boolean)
            Me.ShadowProperties("Enabled") = Value
        End Set
    End Property
    
    public bool Visible
    {
        get
        {
            return (bool)ShadowProperties["Visible"];
        }
        set
        {
            this.ShadowProperties["Visible"] = value;
        }
    }
    
    public bool Enabled
    {
        get
        {
            return (bool)ShadowProperties["Enabled"];
        }
        set
        {
            this.ShadowProperties["Enabled"] = value;
        }
    }
    

Gestion des modifications des composants

La classe MarqueeControlRootDesigner fournit l'expérience au moment du design personnalisée pour vos instances de MarqueeControl. La plupart des fonctionnalités au moment du design sont héritées de la classe DocumentDesigner ; votre code implémentera deux personnalisations spécifiques : gestion des modifications des composants et ajout de verbes de concepteur.

Lorsque les utilisateurs conçoivent leurs instances de MarqueeControl, votre concepteur racine suit les modifications apportées au MarqueeControl et à ses contrôles enfants. L'environnement au moment du design offre un service commode, IComponentChangeService, pour le suivi des modifications dans l'état du composant.

Vous acquérez une référence à ce service en interrogeant l'environnement avec la méthode GetService. Si la requête est réussie, votre concepteur peut joindre un gestionnaire pour l'événement ComponentChanged et exécuter toutes les tâches sont requises pour maintenir un état cohérent au moment du design.

Dans le cas de la classe MarqueeControlRootDesigner, vous appellerez la méthode Refresh sur chaque objet IMarqueeWidget contenu par le MarqueeControl. L'objet IMarqueeWidget se repeindra alors lorsque des propriétés, comme Size, de son parent sont modifiées.

Pour gérer les modifications des composants

  1. Ouvrez le fichier source MarqueeControlRootDesigner dans l'éditeur de code et substituez la méthode Initialize. Appelez l'implémentation de base de Initialize et interrogez pour le IComponentChangeService.

    MyBase.Initialize(component)
    
    Dim cs As IComponentChangeService = _
    CType(GetService(GetType(IComponentChangeService)), _
    IComponentChangeService)
    
    If (cs IsNot Nothing) Then
        AddHandler cs.ComponentChanged, AddressOf OnComponentChanged
    End If
    
    base.Initialize(component);
    
    IComponentChangeService cs =
        GetService(typeof(IComponentChangeService)) 
        as IComponentChangeService;
    
    if (cs != null)
    {
        cs.ComponentChanged +=
            new ComponentChangedEventHandler(OnComponentChanged);
    }
    
  2. Implémentez le gestionnaire d'événements OnComponentChanged. Testez le type du composant d'émission, et s'il s'agit d'un IMarqueeWidget, appelez sa méthode Refresh.

    Private Sub OnComponentChanged( _
    ByVal sender As Object, _
    ByVal e As ComponentChangedEventArgs)
        If TypeOf e.Component Is IMarqueeWidget Then
            Me.Control.Refresh()
        End If
    End Sub
    
    private void OnComponentChanged(
        object sender,
        ComponentChangedEventArgs e)
    {
        if (e.Component is IMarqueeWidget)
        {
            this.Control.Refresh();
        }
    }
    

Ajout de verbes de concepteur à votre concepteur personnalisé

Un verbe de concepteur est une commande de menu liée à un gestionnaire d'événements. Les verbes de concepteur sont ajoutés au menu contextuel d'un composant au moment du design. Pour plus d'informations, consultez DesignerVerb.

Vous ajouterez deux verbes de concepteur à vos concepteurs : Exécuter le test et Arrêter le test. Ces verbes vous permettront de consulter le comportement au moment de l'exécution du MarqueeControl au moment du design. Ces verbes seront ajoutés au MarqueeControlRootDesigner.

Lorsque le verbe Exécuter le test est appelé, le gestionnaire d'événements de verbe appelle la méthode StartMarquee sur le MarqueeControl. Lorsque le verbe Arrêter le test est appelé, le gestionnaire d'événements de verbe appelle la méthode StopMarquee sur le MarqueeControl. L'implémentation des méthodes StartMarquee et StopMarquee appelle ces méthodes sur les contrôles contenus qui implémentent IMarqueeWidget ; tout contrôle IMarqueeWidget contenu participe ainsi au test.

Pour ajouter des verbes de concepteur à vos concepteurs personnalisés

  1. Dans la classe MarqueeControlRootDesigner, ajoutez les gestionnaires d'événements nommés OnVerbRunTest et OnVerbStopTest.

    Private Sub OnVerbRunTest( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
        c.Start()
    
    End Sub
    
    Private Sub OnVerbStopTest( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
        c.Stop()
    
    End Sub
    
    private void OnVerbRunTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Start();
    }
    
    private void OnVerbStopTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Stop();
    }
    
  2. Connectez ces gestionnaires d'événements à leurs verbes de concepteur correspondants. MarqueeControlRootDesigner hérite d'un DesignerVerbCollection de sa classe de base. Vous créerez deux nouveaux objets DesignerVerb et les ajouterez à cette collection dans la méthode Initialize.

    Me.Verbs.Add(New DesignerVerb("Run Test", _
    New EventHandler(AddressOf OnVerbRunTest)))
    
    Me.Verbs.Add(New DesignerVerb("Stop Test", _
    New EventHandler(AddressOf OnVerbStopTest)))
    
    this.Verbs.Add(
        new DesignerVerb("Run Test",
        new EventHandler(OnVerbRunTest))
        );
    
    this.Verbs.Add(
        new DesignerVerb("Stop Test",
        new EventHandler(OnVerbStopTest))
        );
    

Création d'un CustomUITypeEditor

Lorsque vous créez une expérience personnalisée au moment du design pour les utilisateurs, il est souvent souhaitable de créer une interaction personnalisée avec la fenêtre Propriétés. Vous pouvez accomplir ceci en créant un UITypeEditor. Pour plus d'informations, consultez Comment : créer un éditeur de types d'interface utilisateur.

Le contrôle MarqueeBorder expose plusieurs propriétés dans la fenêtre Propriétés. Deux de ces propriétés, MarqueeSpinDirection et MarqueeLightShape sont représentés par les énumérations. Pour illustrer l'utilisation d'un éditeur de types muni d'une interface utilisateur, la propriété MarqueeLightShape aura une classe associée UITypeEditor.

Pour créer un éditeur de types muni d'une interface utilisateur personnalisé

  1. Ouvrez le fichier source MarqueeBorder dans l'éditeur de code.

  2. Dans la définition de la classe MarqueeBorder, déclarez une classe appelée LightShapeEditor qui dérive de UITypeEditor.

    ' This class demonstrates the use of a custom UITypeEditor. 
    ' It allows the MarqueeBorder control's LightShape property
    ' to be changed at design time using a customized UI element
    ' that is invoked by the Properties window. The UI is provided
    ' by the LightShapeSelectionControl class.
    Friend Class LightShapeEditor
        Inherits UITypeEditor
    
    // This class demonstrates the use of a custom UITypeEditor. 
    // It allows the MarqueeBorder control's LightShape property
    // to be changed at design time using a customized UI element
    // that is invoked by the Properties window. The UI is provided
    // by the LightShapeSelectionControl class.
    internal class LightShapeEditor : UITypeEditor
    {
    
  3. Déclarez une variable d'instance de IWindowsFormsEditorService appelée editorService.

    Private editorService As IWindowsFormsEditorService = Nothing
    
    private IWindowsFormsEditorService editorService = null;
    
  4. Substituez la méthode GetEditStyle. Cette implémentation retourne DropDown qui indique à l'environnement de design comment afficher le LightShapeEditor.

    Public Overrides Function GetEditStyle( _
    ByVal context As System.ComponentModel.ITypeDescriptorContext) _
    As UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function
    
    
    public override UITypeEditorEditStyle GetEditStyle(
    System.ComponentModel.ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    
  5. Substituez la méthode EditValue. Cette implémentation interroge l'environnement de design pour un objet IWindowsFormsEditorService. En cas de succès, elle crée un LightShapeSelectionControl. La méthode DropDownControl est appelée pour lancer LightShapeEditor. La valeur de retour de cet appel est retournée à l'environnement de design.

    Public Overrides Function EditValue( _
    ByVal context As ITypeDescriptorContext, _
    ByVal provider As IServiceProvider, _
    ByVal value As Object) As Object
        If (provider IsNot Nothing) Then
            editorService = _
            CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
            IWindowsFormsEditorService)
        End If
    
        If (editorService IsNot Nothing) Then
            Dim selectionControl As _
            New LightShapeSelectionControl( _
            CType(value, MarqueeLightShape), _
            editorService)
    
            editorService.DropDownControl(selectionControl)
    
            value = selectionControl.LightShape
        End If
    
        Return value
    End Function
    
    public override object EditValue(
        ITypeDescriptorContext context,
        IServiceProvider provider,
        object value)
    {
        if (provider != null)
        {
            editorService =
                provider.GetService(
                typeof(IWindowsFormsEditorService))
                as IWindowsFormsEditorService;
        }
    
        if (editorService != null)
        {
            LightShapeSelectionControl selectionControl =
                new LightShapeSelectionControl(
                (MarqueeLightShape)value,
                editorService);
    
            editorService.DropDownControl(selectionControl);
    
            value = selectionControl.LightShape;
        }
    
        return value;
    }
    

Création d'un contrôle d'affichage pour votre CustomUITypeEditor

  1. La propriété MarqueeLightShape prend en charge deux types de formes légères : Square et Circle. Vous créerez un contrôle personnalisé utilisé uniquement en vue d'afficher graphiquement ces valeurs dans la fenêtre Propriétés. Ce contrôle personnalisé sera utilisé par votre UITypeEditor pour interagir avec la fenêtre Propriétés.

Pour créer un contrôle d'affichage pour votre éditeur de types muni d'une interface utilisateur personnalisé

  1. Ajoutez un nouvel élément UserControl au projet MarqueeControlLibrary. Donnez le nom de base "LightShapeSelectionControl" au nouveau fichier source.

  2. Faites glisser deux contrôles Panel de la Boîte à outils vers le LightShapeSelectionControl. Nommez-les squarePanel et circlePanel. Disposez-les côte à côte. Affectez à la propriété Size des deux contrôles Panel la valeur (60, 60). Affectez à la propriété Location du contrôle squarePanel la valeur (8, 10). Affectez à la propriété Location du contrôle circlePanel la valeur (80, 10). Enfin, affectez à la propriété Size du LightShapeSelectionControl la valeur (150, 80).

  3. Ouvrez le fichier source LightShapeSelectionControl dans l'éditeur de code. En haut du fichier, importez l'espace de noms System.Windows.Forms.Design :

Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
  1. Implémentez les gestionnaires d'événements Click pour les contrôles squarePanel et circlePanel. Ces méthodes appellent CloseDropDown pour terminer la session d'édition UITypeEditor personnalisée.

    Private Sub squarePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Me.lightShapeValue = MarqueeLightShape.Square
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()
    
    End Sub
    
    
    Private Sub circlePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Me.lightShapeValue = MarqueeLightShape.Circle
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()
    
    End Sub
    
            private void squarePanel_Click(object sender, EventArgs e)
            {
                this.lightShapeValue = MarqueeLightShape.Square;
    
                this.Invalidate( false );
    
                this.editorService.CloseDropDown();
            }
    
            private void circlePanel_Click(object sender, EventArgs e)
            {
                this.lightShapeValue = MarqueeLightShape.Circle;
    
                this.Invalidate( false );
    
                this.editorService.CloseDropDown();
            }
    
  2. Déclarez une variable d'instance de IWindowsFormsEditorService appelée editorService.

Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
  1. Déclarez une variable d'instance de MarqueeLightShape appelée lightShapeValue.

    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
  2. Dans le constructeur LightShapeSelectionControl, attachez les gestionnaires d'événements Click aux événements Click des contrôles squarePanel et circlePanel. Définissez également une surcharge de constructeur qui affecte au champ lightShapeValue la valeur MarqueeLightShape issue de l'environnement de design.

    ' This constructor takes a MarqueeLightShape value from the
    ' design-time environment, which will be used to display
    ' the initial state.
     Public Sub New( _
     ByVal lightShape As MarqueeLightShape, _
     ByVal editorService As IWindowsFormsEditorService)
         ' This call is required by the Windows.Forms Form Designer.
         InitializeComponent()
    
         ' Cache the light shape value provided by the 
         ' design-time environment.
         Me.lightShapeValue = lightShape
    
         ' Cache the reference to the editor service.
         Me.editorService = editorService
    
         ' Handle the Click event for the two panels. 
         AddHandler Me.squarePanel.Click, AddressOf squarePanel_Click
         AddHandler Me.circlePanel.Click, AddressOf circlePanel_Click
     End Sub
    
            // This constructor takes a MarqueeLightShape value from the
            // design-time environment, which will be used to display
            // the initial state.
            public LightShapeSelectionControl( 
                MarqueeLightShape lightShape,
                IWindowsFormsEditorService editorService )
            {
                // This call is required by the designer.
                InitializeComponent();
    
                // Cache the light shape value provided by the 
                // design-time environment.
                this.lightShapeValue = lightShape;
    
                // Cache the reference to the editor service.
                this.editorService = editorService;
    
                // Handle the Click event for the two panels. 
                this.squarePanel.Click += new EventHandler(squarePanel_Click);
                this.circlePanel.Click += new EventHandler(circlePanel_Click);
            }
    
  3. Dans la méthode Dispose, détachez les gestionnaires d'événements Click.

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
    
            ' Be sure to unhook event handlers
            ' to prevent "lapsed listener" leaks.
            RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click
            RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click
    
            If (components IsNot Nothing) Then
                components.Dispose()
            End If
    
        End If
        MyBase.Dispose(disposing)
    End Sub
    
            protected override void Dispose( bool disposing )
            {
                if( disposing )
                {
                    // Be sure to unhook event handlers
                    // to prevent "lapsed listener" leaks.
                    this.squarePanel.Click -= 
                        new EventHandler(squarePanel_Click);
                    this.circlePanel.Click -= 
                        new EventHandler(circlePanel_Click);
    
                    if(components != null)
                    {
                        components.Dispose();
                    }
                }
                base.Dispose( disposing );
            }
    
  4. Dans l'Explorateur de solutions, cliquez sur le bouton Afficher tous les fichiers. Ouvrez le fichier LightShapeSelectionControl.Designer.cs ou LightShapeSelectionControl.Designer.vb, puis supprimez la définition par défaut de la méthode Dispose.

  5. Implémentez la propriété LightShape.

    ' LightShape is the property for which this control provides
    ' a custom user interface in the Properties window.
    Public Property LightShape() As MarqueeLightShape
    
        Get
            Return Me.lightShapeValue
        End Get
    
        Set(ByVal Value As MarqueeLightShape)
            If Me.lightShapeValue <> Value Then
                Me.lightShapeValue = Value
            End If
        End Set
    
    End Property
    
            // LightShape is the property for which this control provides
            // a custom user interface in the Properties window.
            public MarqueeLightShape LightShape
            {
                get
                {
                    return this.lightShapeValue;
                }
    
                set
                {
                    if( this.lightShapeValue != value )
                    {
                        this.lightShapeValue = value;
                    }
                }
            }
    
  6. Substituez la méthode OnPaint. Cette implémentation dessine un carré et un cercle remplis. Elle met également en surbrillance la valeur sélectionnée en dessinant une bordure autour d'une forme.

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        MyBase.OnPaint(e)
    
        Dim gCircle As Graphics = Me.circlePanel.CreateGraphics()
        Try
            Dim gSquare As Graphics = Me.squarePanel.CreateGraphics()
            Try
                ' Draw a filled square in the client area of
                ' the squarePanel control.
                gSquare.FillRectangle( _
                Brushes.Red, _
                0, _
                0, _
                Me.squarePanel.Width, _
                Me.squarePanel.Height)
    
                ' If the Square option has been selected, draw a 
                ' border inside the squarePanel.
                If Me.lightShapeValue = MarqueeLightShape.Square Then
                    gSquare.DrawRectangle( _
                    Pens.Black, _
                    0, _
                    0, _
                    Me.squarePanel.Width - 1, _
                    Me.squarePanel.Height - 1)
                End If
    
                ' Draw a filled circle in the client area of
                ' the circlePanel control.
                gCircle.Clear(Me.circlePanel.BackColor)
                gCircle.FillEllipse( _
                Brushes.Blue, _
                0, _
                0, _
                Me.circlePanel.Width, _
                Me.circlePanel.Height)
    
                ' If the Circle option has been selected, draw a 
                ' border inside the circlePanel.
                If Me.lightShapeValue = MarqueeLightShape.Circle Then
                    gCircle.DrawRectangle( _
                    Pens.Black, _
                    0, _
                    0, _
                    Me.circlePanel.Width - 1, _
                    Me.circlePanel.Height - 1)
                End If
            Finally
                gSquare.Dispose()
            End Try
        Finally
            gCircle.Dispose()
        End Try
    End Sub
    
            protected override void OnPaint(PaintEventArgs e)
            {
                base.OnPaint (e);
    
                using( 
                    Graphics gSquare = this.squarePanel.CreateGraphics(),
                    gCircle = this.circlePanel.CreateGraphics() )
                {   
                    // Draw a filled square in the client area of
                    // the squarePanel control.
                    gSquare.FillRectangle(
                        Brushes.Red, 
                        0,
                        0,
                        this.squarePanel.Width,
                        this.squarePanel.Height
                        );
    
                    // If the Square option has been selected, draw a 
                    // border inside the squarePanel.
                    if( this.lightShapeValue == MarqueeLightShape.Square )
                    {
                        gSquare.DrawRectangle( 
                            Pens.Black,
                            0,
                            0,
                            this.squarePanel.Width-1,
                            this.squarePanel.Height-1);
                    }
    
                    // Draw a filled circle in the client area of
                    // the circlePanel control.
                    gCircle.Clear( this.circlePanel.BackColor );
                    gCircle.FillEllipse( 
                        Brushes.Blue, 
                        0,
                        0,
                        this.circlePanel.Width, 
                        this.circlePanel.Height
                        );
    
                    // If the Circle option has been selected, draw a 
                    // border inside the circlePanel.
                    if( this.lightShapeValue == MarqueeLightShape.Circle )
                    {
                        gCircle.DrawRectangle( 
                            Pens.Black,
                            0,
                            0,
                            this.circlePanel.Width-1,
                            this.circlePanel.Height-1);
                    }
                }   
            }
    

Test de votre contrôle personnalisé dans le concepteur

À présent, vous pouvez générer le projet MarqueeControlLibrary. Testez votre implémentation en créant un contrôle qui hérite de la classe MarqueeControl et en l'utilisant sur un formulaire.

Pour créer une implémentation MarqueeControl personnalisée

  1. Ouvrez DemoMarqueeControl dans le Concepteur Windows Forms. Cela crée une instance du type DemoMarqueeControl et l'affiche dans une instance du type MarqueeControlRootDesigner.

  2. Dans la boîte à outils, ouvrez l'onglet Composants MarqueeControlLibrary. Vous y verrez les contrôles MarqueeBorder et MarqueeText disponibles pour la sélection.

  3. Faites glisser une instance du contrôle MarqueeBorder sur l'aire de conception DemoMarqueeControl. Ancrez ce contrôle MarqueeBorder au contrôle parent.

  4. Faites glisser une instance du contrôle MarqueeText sur l'aire de conception DemoMarqueeControl.

  5. Générez la solution.

  6. Cliquez avec le bouton droit sur le DemoMarqueeControl et, dans le menu contextuel, sélectionnez l'option Exécuter le test pour démarrer l'animation. Cliquez sur Arrêter le test pour arrêter l'animation.

  7. Ouvrez Form1 en mode Design.

  8. Placez deux contrôles Button sur le formulaire. Nommez-les startButton et stopButton, et modifiez les valeurs de propriété Text en Démarrer et Arrêter, respectivement.

  9. Implémentez des gestionnaires d'événements Click pour les deux contrôles Button.

  10. Dans la Boîte à outils, ouvrez l'onglet Composants MarqueeControlTest. Vous y verrez le DemoMarqueeControl disponible pour la sélection.

  11. Faites glisser une instance de DemoMarqueeControl sur l'aire de conception Form1.

  12. Dans les gestionnaires d'événements Click, appelez les méthodes Start et Stop sur le DemoMarqueeControl.

Private Sub startButton_Click(sender As Object, e As System.EventArgs)
    Me.demoMarqueeControl1.Start()
End Sub 'startButton_Click

Private Sub stopButton_Click(sender As Object, e As System.EventArgs)
Me.demoMarqueeControl1.Stop()
End Sub 'stopButton_Click
private void startButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Start();
}

private void stopButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Stop();
}
  1. Définissez le projet MarqueeControlTest comme projet de démarrage et exécutez-le. Le formulaire contenant votre DemoMarqueeControl s'affiche alors. Cliquez sur le bouton Démarrer pour lancer l'animation. Le texte clignote et les lumières se déplacent autour de la bordure.

Étapes suivantes

Le MarqueeControlLibrary montre une implémentation simple de contrôles personnalisés et des concepteurs associés. Vous pouvez rendre cet exemple plus élaboré de plusieurs façons :

  • Modifiez les valeurs de propriété pour le DemoMarqueeControl dans le concepteur. Ajoutez plus de contrôles MarqueBorder et ancrez-les dans leurs instances parentes pour créer un effet imbriqué. Effectuez des essais avec différents paramètres pour UpdatePeriod et les propriétés liées à lumière.

  • Créez vos propres implémentations pour IMarqueeWidget. Par exemple, vous pourriez créer une « enseigne au néon » éclatante ou un signe animé avec plusieurs images.

  • Personnalisez davantage l'expérience au moment du design. Vous pourriez essayer d'occulter plus de propriétés que Enabled et Visible, et vous pourriez ajouter de nouvelles propriétés. Ajoutez de nouveaux verbes de concepteur pour simplifier des tâches courantes comme l'ancrage de contrôles enfants.

  • Autorisez le MarqueeControl. Pour plus d'informations, consultez Comment : accorder la licence d'utilisation de composants et de contrôles.

  • Contrôle comment vos contrôles sont sérialisés et comment le code est généré pour eux. Pour plus d'informations, consultez Génération et compilation de code source dynamique.

Voir aussi

Tâches

Comment : créer un contrôle Windows Forms qui bénéficie des fonctionnalités au moment du design

Référence

UserControl

ParentControlDesigner

DocumentDesigner

IRootDesigner

DesignerVerb

UITypeEditor

BackgroundWorker

Autres ressources

Extension de la prise en charge au moment du design

Concepteurs personnalisés

.NET Shape Library: A Sample Designer