Share via


Procedura dettagliata: creazione di un controllo di Windows Form che usufruisca delle funzionalità offerte da Visual Studio in fase di progettazione

Aggiornamento: novembre 2007

La fase di progettazione di un controllo personalizzato può essere migliorata mediante la creazione di una finestra di progettazione personalizzata associata.

In questa procedura dettagliata viene illustrato come creare una finestra di progettazione personalizzata per un controllo personalizzato. Verrà implementato il tipo MarqueeControl e la classe di una finestra di progettazione associata denominata MarqueeControlRootDesigner.

Il tipo MarqueeControl implementa una visualizzazione simile all'insegna di un teatro con luci animate e testo intermittente.

La finestra di progettazione per questo controllo interagisce con l'ambiente di progettazione per consentire di personalizzare la fase di progettazione. Grazie alla finestra di progettazione personalizzata è possibile assemblare un'implementazione di MarqueeControl personalizzata con luci animate e testo intermittente in varie combinazioni. È possibile utilizzare il controllo assemblato in un form come qualsiasi altro controllo di Windows Form.

Di seguito sono elencate le attività illustrate nella procedura dettagliata:

  • Creazione del progetto

  • Creazione di un progetto di libreria di controlli

  • Aggiunta di un riferimento al progetto del controllo personalizzato

  • Definizione di un controllo personalizzato e della relativa finestra di progettazione personalizzata

  • Creazione di un'istanza del controllo personalizzato

  • Impostazione del progetto per il debug in fase di progettazione

  • Implementazione del controllo personalizzato

  • Creazione di un controllo figlio per il controllo personalizzato

  • Creazione del controllo figlio di MarqueeBorder

  • Creazione di una finestra di progettazione personalizzata per eseguire lo shadowing e filtrare le proprietà

  • Gestione delle modifiche ai componenti

  • Aggiunta di verbi di progettazione alla finestra di progettazione personalizzata

  • Creazione di un UITypeEditor personalizzato

  • Test del controllo personalizzato nella finestra di progettazione

Al termine, il controllo personalizzato risulterà simile al seguente:

Possibile disposizione di MarqueeControl

Per l'elenco di codice completo, vedere Procedura: creare un controllo Windows Form che utilizza le funzionalità di progettazione.

Nota:

Le finestre di dialogo e i comandi di menu visualizzati possono differire da quelli descritti nella Guida a seconda delle impostazioni attive o dell'edizione in uso. Per modificare le impostazioni, scegliere Importa/Esporta impostazioni dal menu Strumenti. Per ulteriori informazioni, vedere Impostazioni di Visual Studio.

Prerequisiti

Per completare questa procedura dettagliata, è necessario disporre dei seguenti elementi:

  • Disporre di autorizzazioni sufficienti per creare ed eseguire progetti di applicazioni Windows Form nel computer dove è installato Visual Studio.

Creazione del progetto

Il primo passaggio consiste nella creazione del progetto di applicazione, che verrà utilizzato per compilare l'applicazione che contiene il controllo personalizzato.

Per creare il progetto

Creazione di un progetto di libreria di controlli

Il passaggio successivo consiste nella creazione di un progetto di libreria di controlli. Verrà creato un nuovo controllo personalizzato insieme alla finestra di progettazione personalizzata corrispondente.

Per creare il progetto di libreria di controlli

  1. Aggiungere alla soluzione un progetto Libreria di controlli Windows. Per ulteriori informazioni, vedere Finestra di dialogo Aggiungi nuovo progetto. Denominare il progetto "MarqueeControlLibrary".

  2. In Esplora soluzioni eliminare il controllo predefinito del progetto rimuovendo il file di origine "UserControl1.cs" o "UserControl1.vb", a seconda del linguaggio scelto. Per ulteriori informazioni, vedere Procedura: rimuovere, eliminare ed escludere elementi.

  3. Aggiungere un nuovo elemento UserControl al progetto MarqueeControlLibrary. Assegnare al nuovo file di origine il nome di base "MarqueeControl".

  4. In Esplora soluzioni creare una nuova cartella nel progetto MarqueeControlLibrary. Per ulteriori informazioni, vedere Procedura: aggiungere elementi di progetto nuovi. Assegnare il nome "Design" alla nuova cartella.

  5. Fare clic con il pulsante destro del mouse sulla cartella Design, quindi aggiungere una nuova classe. Assegnare al file di origine il nome di base "MarqueeControlRootDesigner".

  6. Sarà necessario utilizzare i tipi dell'assembly System.Design e quindi aggiungere il riferimento al progetto MarqueeControlLibrary. Per ulteriori informazioni, vedere Procedura: aggiungere o rimuovere riferimenti in Visual Studio (C#).

Aggiunta di un riferimento al progetto del controllo personalizzato

Per eseguire il test del controllo personalizzato si utilizzerà il progetto MarqueeControlTest. Il progetto di test riconoscerà il controllo personalizzato quando viene aggiunto un riferimento al progetto nell'assembly MarqueeControlLibrary.

Per aggiungere un riferimento al progetto del controllo personalizzato

  • Nel progetto MarqueeControlTest aggiungere all'assembly MarqueeControlLibrary un riferimento al progetto. Utilizzare la scheda Progetti della finestra di dialogo Aggiungi riferimento invece di inserire il riferimento direttamente nell'assembly MarqueeControlLibrary.

Definizione di un controllo personalizzato e della relativa finestra di progettazione personalizzata

Il controllo personalizzato verrà derivato dalla classe UserControl e potrà quindi contenere altri controlli e utilizzare molte funzionalità predefinite.

Il controllo personalizzato disporrà di una finestra di progettazione personalizzata associata che consente di definire il proprio ambiente di progettazione, personalizzandolo appositamente per il controllo personalizzato.

È possibile associare il controllo alla rispettiva finestra di progettazione utilizzando la classe DesignerAttribute. Poiché si sta sviluppando l'intero comportamento in fase di progettazione del controllo personalizzato, la finestra di progettazione personalizzata implementerà l'interfaccia IRootDesigner.

Per definire un controllo personalizzato e la relativa finestra di progettazione personalizzata

  1. Aprire il file di origine MarqueeControl nell'editor di codice. Importare all'inizio del file i seguenti spazi dei nomi:

    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. Aggiungere DesignerAttribute alla dichiarazione della classe MarqueeControl, in modo da associare il controllo personalizzato alla relativa finestra di progettazione.

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
     [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
        public class MarqueeControl : UserControl
        {
    
  3. Aprire il file di origine MarqueeControlRootDesigner nell'editor di codice. Importare all'inizio del file i seguenti spazi dei nomi:

    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. Modificare la dichiarazione di MarqueeControlRootDesigner in modo che erediti dalla classe DocumentDesigner. Applicare ToolboxItemFilterAttribute per specificare l'interazione della finestra di progettazione con la Casella degli strumenti.

    Nota   La definizione della classe MarqueeControlRootDesigner è stata racchiusa in uno spazio dei nomi denominato "MarqueeControlLibrary.Design". Tale dichiarazione inserisce la finestra di progettazione in uno speciale spazio dei nomi riservato ai tipi correlati alla progettazione.

    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. Definire il costruttore per la classe MarqueeControlRootDesigner. Inserire nel corpo del costruttore un'istruzione WriteLine che sarà utile ai fini del debug.

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

Creazione di un'istanza del controllo personalizzato

Per osservare il comportamento personalizzato del controllo in fase di progettazione, si inserirà un'istanza del controllo nel form del progetto MarqueeControlTest.

Per creare un'istanza del controllo personalizzato

  1. Aggiungere un nuovo elemento UserControl al progetto MarqueeControlTest. Assegnare al nuovo file di origine il nome di base "DemoMarqueeControl".

  2. Aprire il file DemoMarqueeControl nell'editor di codice. Importare all'inizio del file lo spazio dei nomi MarqueeControlLibrary:

Imports MarqueeControlLibrary
using MarqueeControlLibrary;
  1. Modificare la dichiarazione di DemoMarqueeControl in modo che erediti dalla classe MarqueeControl.

  2. Generare il progetto.

  3. Aprire Form1 in Progettazione Windows Form.

  4. Trovare la scheda Componenti MarqueeControlTest nella Casella degli strumenti e quindi aprirla. Trascinare un DemoMarqueeControl dalla Casella degli strumenti nel form.

  5. Generare il progetto.

Impostazione del progetto per il debug in fase di progettazione

Quando si sviluppa una fase di progettazione personalizzata, è necessario eseguire il debug dei controlli e dei componenti. Esiste un modo semplice per impostare il progetto in modo da consentire il debug in fase di progettazione. Per ulteriori informazioni, vedere Procedura dettagliata: debug di controlli di Windows Form personalizzati in fase di progettazione.

Per impostare il progetto per il debug in fase di progettazione

  1. Fare clic con il pulsante destro del mouse sul progetto MarqueeControlLibrary e scegliere Proprietà.

  2. Nella finestra di dialogo "Pagine delle proprietà di MarqueeControlLibrary" selezionare la pagina Proprietà di configurazione.

  3. Nella sezione Azione di avvio selezionare Avvia programma esterno. Verrà eseguito il debug di un'istanza distinta di Visual Studio, quindi fare clic sul pulsante con i puntini di sospensione (Schermata VisualStudioEllipsesButton) per individuare l'IDE di Visual Studio. Il nome del file eseguibile è devenv.exe e, se è stato installato nel percorso predefinito, il relativo percorso %programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.

  4. Scegliere OK per chiudere la finestra di dialogo.

  5. Fare clic con il pulsante destro del mouse sul progetto MarqueeControlLibrary e scegliere "Imposta come progetto di avvio" per attivare la configurazione di debug.

Checkpoint

A questo punto è possibile eseguire il debug del comportamento in fase di progettazione del controllo personalizzato. Una volta verificato che l'ambiente di debug è stato configurato correttamente, si eseguirà il test dell'associazione tra il controllo personalizzato e la finestra di progettazione personalizzata.

Per eseguire il test dell'ambiente di debug e dell'associazione della finestra di progettazione

  1. Aprire il file di origine MarqueeControlRootDesigner nell'editor di codice e inserire un punto di interruzione nell'istruzione WriteLine.

  2. Premere F5 per avviare la sessione di debug. Si noti che verrà creata una nuova istanza di Visual Studio.

  3. Aprire la soluzione "MarqueeControlTest" nella nuova istanza di Visual Studio. È possibile trovare facilmente la soluzione selezionando Progetti recenti dal menu File. Il file della soluzione "MarqueeControlTest.sln" corrisponderà al file utilizzato più di recente nell'elenco.

  4. Aprire DemoMarqueeControl nella finestra di progettazione. Si noti che l'istanza di debug di Visual Studio acquisisce lo stato attivo e l'esecuzione si interrompe in corrispondenza del punto di interruzione. Premere F5 per continuare la sessione di debug.

A questo punto è possibile sviluppare ed eseguire il debug del controllo personalizzato e della finestra di progettazione personalizzata associata. Nella parte restante della presente procedura verranno descritti i dettagli per l'implementazione delle funzioni del controllo e della finestra di progettazione.

Implementazione del controllo personalizzato

MarqueeControl è un controllo UserControl leggermente personalizzato che espone due metodi: il metodo Start avvia l'animazione e il metodo Stop la interrompe. Poiché MarqueeControl contiene controlli figlio che implementano l'interfaccia IMarqueeWidget, i metodi Start e Stop enumerano i controlli figlio e chiamano rispettivamente i metodi StartMarquee e StopMarquee per ogni controllo figlio che implementa IMarqueeWidget.

L'aspetto dei controlli MarqueeBorder e MarqueeText dipende dal layout, quindi MarqueeControl esegue l'override del metodo OnLayout e chiama PerformLayout per i controlli figlio di questo tipo.

Quelle descritte sono le possibili personalizzazioni offerte dal controllo MarqueeControl. Le funzioni della fase di esecuzione vengono implementate dai controlli MarqueeBorder e MarqueeText e le funzioni della fase di progettazione vengono implementate dalle classi MarqueeBorderDesigner e MarqueeControlRootDesigner.

Per implementare il controllo personalizzato

  1. Aprire il file di origine MarqueeControl nell'editor di codice. Implementare i metodi Start e 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. Eseguire l'override del metodo 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();
            }
        }
    }
    

Creazione di un controllo figlio per il controllo personalizzato

Il controllo MarqueeControl conterrà due tipi di controlli figlio, MarqueeBorder e MarqueeText.

  • MarqueeBorder: questo controllo disegna lungo i bordi una cornice di luci che lampeggiano in sequenza in modo da creare un effetto di spostamento lungo i bordi. La frequenza di intermittenza è controllata dalla proprietà UpdatePeriod. Diverse altre proprietà personalizzate determinano ulteriori fattori dell'aspetto del controllo. L'avvio e l'arresto dell'animazione sono controllati mediante i due metodi StartMarquee e StopMarquee.

  • MarqueeText: questo controllo disegna una stringa lampeggiante. Come per il controllo MarqueeBorder, la frequenza di intermittenza del testo è determinata dalla proprietà UpdatePeriod. Inoltre il controllo MarqueeText dispone dei due metodi StartMarquee e StopMarquee in comune con il controllo MarqueeBorder.

In fase di progettazione, MarqueeControlRootDesigner consente di aggiungere questi due tipi di controllo a un MarqueeControl in qualsiasi combinazione.

Poiché le funzioni comuni ai due controlli sono inserite nell'interfaccia IMarqueeWidget, MarqueeControl può individuare i controlli figlio correlati al controllo Marquee e comportarsi di conseguenza.

Per implementare la funzione di animazione periodica, si utilizzeranno gli oggetti BackgroundWorker appartenenti allo spazio dei nomi System.ComponentModel. Sebbene sia possibile utilizzare gli oggetti Timer, in presenza di molti oggetti IMarqueeWidget, il singolo thread dell'interfaccia utente potrebbe non essere in grado di gestire l'animazione.

Per creare un controllo figlio per il controllo personalizzato

  1. Aggiungere un nuovo elemento class al progetto MarqueeControlLibrary. Assegnare al nuovo file di origine il nome di base "IMarqueeWidget".

  2. Aprire il file di origine IMarqueeWidget nell'editor di codice e modificare la dichiarazione da class a 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. Aggiungere il seguente codice all'interfaccia IMarqueeWidget per esporre due metodi e una proprietà che gestiscono l'animazione:

    ' 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. Aggiungere un nuovo elemento Controllo personalizzato al progetto MarqueeControlLibrary. Assegnare al nuovo file di origine il nome di base "MarqueeText".

  5. Trascinare un componente BackgroundWorker dalla Casella degli strumenti nel controllo MarqueeText. Tale componente consentirà al controllo MarqueeText di aggiornarsi in modo asincrono.

  6. Nella finestra Proprietà impostare le proprietà WorkerReportsProgess e WorkerSupportsCancellation del componente BackgroundWorker su true. Queste impostazioni consentono al componente BackgroundWorker di generare periodicamente l'evento ProgressChanged e annullare gli aggiornamenti asincroni. Per ulteriori informazioni, vedere la classe Componente BackgroundWorker.

  7. Aprire il file di origine MarqueeText nell'editor di codice. Importare all'inizio del file i seguenti spazi dei nomi:

    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. Modificare la dichiarazione di MarqueeText in modo che erediti da Label e implementare l'interfaccia 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. Dichiarare le variabili di istanza che corrispondono alle proprietà esposte e inizializzarle nel costruttore. Il campo isLit determina se il testo deve essere visualizzato nel colore specificato dalla proprietà 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. Implementare l'interfaccia IMarqueeWidget.

    Per avviare e arrestare l'animazione, i metodi StartMarquee e StopMarquee richiamano i metodi RunWorkerAsync e CancelAsync del componente BackgroundWorker.

    Gli attributi Category e Browsable vengono applicati alla proprietà UpdatePeriod affinché venga riportata in una sezione personalizzata della finestra Proprietà denominata "Marquee".

    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. Implementare le funzioni di accesso della proprietà. È necessario esporre ai client le due proprietà LightColor e DarkColor. Gli attributi Category e Browsable vengono applicati a tali proprietà affinché vengano riportate in una sezione personalizzata della finestra Proprietà denominata "Marquee".

    <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. Implementare i gestori per gli eventi DoWork e ProgressChanged del componente BackgroundWorker.

    Il gestore dell'evento DoWork resta inattivo per il numero di millisecondi specificato da UpdatePeriod, quindi genera l'evento ProgressChanged finché l'animazione non viene arrestata mediante la chiamata del metodo CancelAsync nel codice.

    Il gestore dell'evento ProgressChanged attiva e disattiva l'illuminazione del testo per ottenere l'intermittenza.

    ' 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. Eseguire l'override del metodo OnPaint per attivare l'animazione.

    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. Premere F6 per compilare la soluzione.

Creazione del controllo figlio di MarqueeBorder

Il controllo MarqueeBorder è leggermente più sofisticato rispetto al controllo MarqueeText in quanto dispone di più proprietà e l'animazione nel metodo OnPaint è più complessa, anche se il principio è molto simile a quello del controllo MarqueeText.

Poiché il controllo MarqueeBorder può contenere controlli figlio, deve essere in grado di rilevare eventi Layout.

Per creare il controllo MarqueeBorder

  1. Aggiungere un nuovo elemento Controllo personalizzato al progetto MarqueeControlLibrary. Assegnare al nuovo file di origine il nome di base "MarqueeBorder".

  2. Trascinare un componente BackgroundWorker dalla Casella degli strumenti nel controllo MarqueeBorder. Tale componente consentirà al controllo MarqueeBorder di aggiornarsi in modo asincrono.

  3. Nella finestra Proprietà impostare le proprietà WorkerReportsProgess e WorkerSupportsCancellation del componente BackgroundWorker su true. Queste impostazioni consentono al componente BackgroundWorker di generare periodicamente l'evento ProgressChanged e annullare gli aggiornamenti asincroni. Per ulteriori informazioni, vedere la classe Componente BackgroundWorker.

  4. Nella finestra Proprietà fare clic sul pulsante Eventi. Collegare i gestori per gli eventi DoWork e ProgressChanged.

  5. Aprire il file di origine MarqueeBorder nell'editor di codice. Importare all'inizio del file i seguenti spazi dei nomi:

    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. Modificare la dichiarazione di MarqueeBorder in modo che erediti da Panel e implementare l'interfaccia 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. Per la gestione dello stato del controllo MarqueeBorder dichiarare le due enumerazioni MarqueeSpinDirection, che determina la direzione in cui le luci si muovono lungo i bordi, e MarqueeLightShape, che determina la forma delle luci, quadrata o circolare. Inserire queste dichiarazioni prima della dichiarazione della 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. Dichiarare le variabili di istanza che corrispondono alle proprietà esposte e inizializzarle nel costruttore.

    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. Implementare l'interfaccia IMarqueeWidget.

    Per avviare e arrestare l'animazione, i metodi StartMarquee e StopMarquee richiamano i metodi RunWorkerAsync e CancelAsync del componente BackgroundWorker.

    Poiché MarqueeBorder può contenere controlli figlio, il metodo StartMarquee enumera i controlli figlio e chiama StartMarquee per quelli che implementano IMarqueeWidget. L'implementazione del metodo StopMarquee è simile.

    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. Implementare le funzioni di accesso della proprietà. Il controllo MarqueeBorder dispone di diverse proprietà per la gestione dell'aspetto.

    <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. Implementare i gestori per gli eventi DoWork e ProgressChanged del componente BackgroundWorker.

    Il gestore dell'evento DoWork resta inattivo per il numero di millisecondi specificato da UpdatePeriod, quindi genera l'evento ProgressChanged finché l'animazione non viene arrestata mediante la chiamata del metodo CancelAsync nel codice.

    Il gestore dell'evento ProgressChanged incrementa la posizione della luce campione, dalla quale viene determino lo stato di illuminazione delle altre luci, e chiama il metodo Refresh affinché il controllo venga ridisegnato.

    ' 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. Implementare i metodi di supporto IsLit e DrawLight.

    Il metodo IsLit determina il colore di una luce in una determinata posizione. Le luci accese vengono disegnate nel colore specificato dalla proprietà LightColor e quelle spente vengono disegnate nel colore fornito dalla proprietà DarkColor.

    Il metodo DrawLight disegna una luce utilizzando il colore, la forma e la posizione appropriati.

    ' 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. Eseguire l'override dei metodi OnLayout e OnPaint.

    Il metodo OnPaint disegna le luci lungo i bordi del controllo MarqueeBorder.

    Poiché il metodo OnPaint dipende dalle dimensioni del controllo MarqueeBorder, è necessario chiamarlo ogni volta che il layout cambia. A tale scopo, eseguire l'override del metodo OnLayout e chiamare il metodo 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++;
            }
        }
    }
    

Creazione di una finestra di progettazione personalizzata per eseguire lo shadowing e filtrare le proprietà

La classe MarqueeControlRootDesigner fornisce l'implementazione della finestra di progettazione principale. Oltre a questa finestra di progettazione, che viene applicata al controllo MarqueeControl, è necessaria una finestra di progettazione personalizzata associata specificamente al controllo MarqueeBorder che fornisca il comportamento personalizzato appropriato al contesto della finestra di progettazione principale personalizzata.

In particolare, MarqueeBorderDesigner eseguirà lo shadowing e filtrerà determinate proprietà per il controllo MarqueeBorder alterando l'interazione con l'ambiente di progettazione.

Lo shadowing consiste nell'intercettazione delle chiamate alla funzione di accesso alla proprietà di un componente e consente di tenere traccia del valore impostato dall'utente ed eventualmente passarlo al componente che si sta progettando.

In questo esempio, verrà eseguito lo shadowing delle proprietà Visible e Enabled mediante MarqueeBorderDesigner, impedendo così all'utente di rendere il controllo MarqueeBorder invisibile o disattivarlo durante la fase di progettazione.

Nelle finestre di progettazione è inoltre possibile aggiungere e rimuovere proprietà. In questo esempio, la proprietà Padding verrà rimossa in fase di progettazione perché il controllo MarqueeBorder imposta a livello di codice la spaziatura interna in base alla dimensione delle luci specificata dalla proprietà LightSize.

La classe base di MarqueeBorderDesigner è ComponentDesigner, che dispone di metodi per modificare gli attributi, le proprietà e gli eventi esposti da un controllo in fase di progettazione:

Per modificare l'interfaccia pubblica di un componente utilizzando questi metodi, è necessario attenersi alle regole riportate di seguito:

  • Aggiungere o rimuovere elementi solo nei metodi PreFilter.

  • Modificare gli elementi esistenti solo nei metodi PostFilter.

  • Chiamare sempre l'implementazione di base per prima nei metodi PreFilter.

  • Chiamare sempre l'implementazione di base per ultima nei metodi PostFilter.

Il rispetto di queste regole garantisce una visualizzazione uniforme di tutti i componenti nelle finestre di progettazione nell'ambiente di progettazione.

Poiché la classe ComponentDesigner fornisce un dizionario per la gestione dei valori delle proprietà per le quali è stato eseguito lo shadowing, non è necessario creare variabili di istanza specifiche.

Per creare una finestra di progettazione personalizzata per eseguire lo shadowing e filtrare le proprietà

  1. Fare clic con il pulsante destro del mouse sulla cartella Design, quindi aggiungere una nuova classe. Assegnare al file di origine il nome di base "MarqueeBorderDesigner".

  2. Aprire il file di origine MarqueeBorderDesigner nell'editor di codice. Importare all'inizio del file i seguenti spazi dei nomi:

    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. Modificare la dichiarazione di MarqueeBorderDesigner in modo che erediti dalla classe ParentControlDesigner.

    Poiché MarqueeBorder può contenere controlli figlio, MarqueeBorderDesigner eredita da ParentControlDesigner che gestisce l'interazione tra controlli padre e figlio.

    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. Eseguire l'override dell'implementazione di base di 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. Implementare le proprietà Enabled e Visible. Queste implementazioni eseguono lo shadowing delle proprietà del controllo.

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

Gestione delle modifiche ai componenti

La classe MarqueeControlRootDesigner fornisce una fase di progettazione personalizzata per le istanze di MarqueeControl. La maggioranza delle funzionalità della fase di progettazione sono ereditate dalla classe DocumentDesigner. Mediante codice verranno implementate due personalizzazioni specifiche: la gestione delle modifiche ai componenti e l'aggiunta di verbi di progettazione.

Quando gli utenti progettano le proprie istanze di MarqueeControl, la finestra di progettazione principale tiene traccia delle modifiche apportate a MarqueeControl e ai relativi controlli figlio. L'ambiente della fase di progettazione offre un servizio utile, IComponentChangeService, per tenere traccia delle modifiche apportate allo stato dei componenti.

Il riferimento a questo servizio viene ottenuto tramite una query all'ambiente con il metodo GetService. Se la query ha esito positivo, la finestra di progettazione può collegare un gestore per l'evento ComponentChanged ed eseguire le attività richieste per mantenere uno stato coerente durante la fase di progettazione.

Nel caso della classe MarqueeControlRootDesigner, si chiamerà il metodo Refresh per ogni oggetto IMarqueeWidget contenuto nel controllo MarqueeControl. In tal modo l'oggetto IMarqueeWidget verrà ridisegnato correttamente se vengono modificate proprietà del controllo padre, ad esempio Size.

Per gestire le modifiche ai componenti

  1. Aprire il file di origine MarqueeControlRootDesigner nell'editor di codice ed eseguire l'override del metodo Initialize. Chiamare l'implementazione di base di Initialize ed eseguire una query per 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. Implementare il gestore eventi OnComponentChanged. Verificare il tipo del componente che è stato modificato e, se si tratta di IMarqueeWidget, chiamare il relativo metodo 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();
        }
    }
    

Aggiunta di verbi di progettazione alla finestra di progettazione personalizzata

Un verbo di progettazione è un comando di menu collegato a un gestore eventi che viene aggiunto al menu di scelta rapida di un componente in fase di progettazione. Per ulteriori informazioni, vedere DesignerVerb.

Alle finestre di progettazione verranno aggiunti i due verbi di progettazione Esegui test e Interrompi test che consentono di visualizzare in fase di progettazione il comportamento in fase di esecuzione del controllo MarqueeControl. Questi verbi verranno aggiunti a MarqueeControlRootDesigner.

Quando si richiama Esegui test, il gestore eventi del verbo chiamerà il metodo StartMarquee in MarqueeControl. Quando si richiama Interrompi test, il gestore eventi del verbo chiamerà il metodo StopMarquee in MarqueeControl. L'implementazione dei metodi StartMarquee e StopMarquee esegue la chiamata nei controlli contenuti che implementano IMarqueeWidget, in modo che il test venga eseguito anche per ogni controllo IMarqueeWidget contenuto.

Per aggiungere verbi di progettazione alle finestre di progettazione personalizzate

  1. Nella classe MarqueeControlRootDesigner aggiungere i gestori eventi OnVerbRunTest e 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. Collegare tali gestori eventi ai corrispondenti verbi di progettazione. MarqueeControlRootDesigner eredita l'insieme DesignerVerbCollection dalla classe base. Verranno creati due nuovi oggetti DesignerVerb e aggiunti all'insieme nel metodo 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))
        );
    

Creazione di un UITypeEditor personalizzato

Quando si crea una fase di progettazione personalizzata per gli utenti, è spesso opportuno realizzare un'interazione personalizzata con la finestra Proprietà. A tal fine è possibile creare un UITypeEditor. Per ulteriori informazioni, vedere Procedura: creare un editor di tipo con interfaccia utente.

Il controllo MarqueeBorder espone diverse proprietà nella finestra Proprietà, due delle quali, MarqueeSpinDirection e MarqueeLightShape, sono rappresentate da enumerazioni. Per illustrare l'utilizzo di un editor di tipi con interfaccia utente, alla proprietà MarqueeLightShape verrà associata una classe UITypeEditor.

Per creare un editor di tipi con interfaccia utente personalizzato

  1. Aprire il file di origine MarqueeBorder nell'editor di codice.

  2. Nella definizione della classe MarqueeBorder dichiarare una classe denominata LightShapeEditor derivita da 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. Dichiarare una variabile dell'istanza di IWindowsFormsEditorService denominata editorService.

    Private editorService As IWindowsFormsEditorService = Nothing
    
    private IWindowsFormsEditorService editorService = null;
    
  4. Eseguire l'override del metodo GetEditStyle. Questa implementazione restituisce DropDown, che indica all'ambiente di progettazione come visualizzare 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. Eseguire l'override del metodo EditValue. Questa implementazione richiede all'ambiente di progettazione un oggetto IWindowsFormsEditorService. Se la query ha esito positivo, viene creato un controllo LightShapeSelectionControl, quindi viene chiamato il metodo DropDownControl per avviare LightShapeEditor. Il valore restituito dalla chiamata viene passato all'ambiente di progettazione.

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

Creazione di un controllo di visualizzazione per l'editor di tipi con interfaccia utente personalizzato

  1. La proprietà MarqueeLightShape supporta due tipi di forme per le luci, Square e Circle. Verrà creato un controllo personalizzato utilizzato unicamente per visualizzare graficamente i valori nella finestra Proprietà. Questo controllo personalizzato verrà utilizzato da UITypeEditor per interagire con la finestra Proprietà.

Per creare un controllo di visualizzazione per l'editor di tipi con interfaccia utente personalizzato

  1. Aggiungere un nuovo elemento UserControl al progetto MarqueeControlLibrary. Assegnare al nuovo file di origine il nome di base "LightShapeSelectionControl".

  2. Trascinare due controlli Panel dalla Casella degli strumenti in LightShapeSelectionControl, assegnarvi i nomi squarePanel e circlePanel e sistemarli affiancati. Impostare la proprietà Size di entrambi i controlli Panel su (60, 60). Impostare la proprietà Location del controllo squarePanel su (8, 10). Impostare la proprietà Location del controllo circlePanel su (80, 10). Infine, impostare la proprietà Size del controllo LightShapeSelectionControl su (150, 80).

  3. Aprire il file di origine LightShapeSelectionControl nell'editor di codice. Importare all'inizio del file lo spazio dei nomi System.Windows.Forms.Design:

Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
  1. Implementare i gestori eventi Click dei controlli squarePanel e circlePanel. Questi metodi richiamano il metodo CloseDropDown per terminare la sessione di modifica personalizzata di UITypeEditor.

    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. Dichiarare una variabile dell'istanza di IWindowsFormsEditorService denominata editorService.

Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
  1. Dichiarare una variabile dell'istanza di MarqueeLightShape denominata lightShapeValue.

    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
  2. Nel costruttore LightShapeSelectionControl, collegare i gestori eventi Click agli eventi Click dei controlli squarePanel e circlePanel. Definire inoltre un overload del costruttore che assegna il valore MarqueeLightShape dell'ambiente di progettazione al campo lightShapeValue.

    ' 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. Nel metodo Dispose, scollegare i gestori eventi 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. In Esplora soluzioni fare clic sul pulsante Mostra tutti i file. Aprire il file LightShapeSelectionControl.Designer.cs o il file LightShapeSelectionControl.Designer.vb e rimuovere la definizione predefinita del metodo Dispose.

  5. Implementare la proprietà 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. Eseguire l'override del metodo OnPaint. Questa implementazione, oltre a disegnare un quadrato pieno e un cerchio, evidenzia il valore selezionato disegnando un bordo attorno a una delle due 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 del controllo personalizzato nella finestra di progettazione

A questo punto è possibile compilare il progetto MarqueeControlLibrary. Testare l'implementazione creando un controllo che eredita dalla classe MarqueeControl e utilizzandolo su un form.

Per creare un'implementazione di MarqueeControl personalizzata

  1. Aprire DemoMarqueeControl in Progettazione Windows Form. In questo modo verrà creata un'istanza del tipo DemoMarqueeControl visualizzata in un'istanza del tipo MarqueeControlRootDesigner.

  2. Nella Casella degli strumenti aprire la scheda Componenti MarqueeControlLibrary in cui sarà possibile selezionare i controlli MarqueeBorder e MarqueeText.

  3. Trascinare un'istanza del controllo MarqueeBorder nell'area di progettazione di DemoMarqueeControl. Ancorare il controllo MarqueeBorder al controllo padre.

  4. Trascinare un'istanza del controllo MarqueeText nell'area di progettazione di DemoMarqueeControl.

  5. Compilare la soluzione.

  6. Fare clic con il pulsante destro del mouse su DemoMarqueeControl e scegliere Esegui test dal menu di scelta rapida per avviare l'animazione. Fare clic su Interrompi test per arrestare l'animazione.

  7. Aprire Form1 nella visualizzazione Progettazione.

  8. Inserire due controlli Button nel form, denominarli startButton e stopButton e modificare i valori della proprietà Text rispettivamente in Start e Stop

  9. Implementare i gestori dell'evento Click per entrambi i controlli Button.

  10. Nella Casella degli strumenti aprire la scheda Componenti MarqueeControlTest in cui sarà possibile selezionare il controllo DemoMarqueeControl.

  11. Trascinare un'istanza del controllo DemoMarqueeControl nell'area di progettazione di Form1.

  12. Nei gestori dell'evento Click richiamare i metodi Start e Stop in 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. Impostare il progetto MarqueeControlTest come progetto di avvio ed eseguirlo. Verrà visualizzato il form contenente il controllo DemoMarqueeControl. Fare clic sul pulsante Start per avviare l'animazione. Verrà visualizzato il testo lampeggiante e lo spostamento delle luci lungo i bordi.

Passaggi successivi

MarqueeControlLibrary dimostra una semplice implementazione dei controlli personalizzati e delle finestre di progettazione associate. Questo esempio può essere reso più sofisticato in diversi modi.

  • Modificare i valori delle proprietà per il controllo DemoMarqueeControl nella finestra di progettazione. Aggiungere più controlli MarqueBorder e ancorarli nelle relative istanze padre per creare un effetto nidificato. Provare diverse impostazioni per le proprietà relative alle luci e UpdatePeriod.

  • Creare altre implementazioni di IMarqueeWidget, ad esempio per creare un'insegna al neon lampeggiante o un'insegna animata con più immagini.

  • Personalizzare ulteriormente la fase di progettazione provando a eseguire lo shadowing di altre proprietà oltre a Enabled e Visible e aggiungendo nuove proprietà. Aggiungere nuovi verbi di progettazione per semplificare attività comuni quali l'ancoraggio dei controlli figlio.

  • Creare una licenza per il controllo MarqueeControl. Per ulteriori informazioni, vedere Procedura: concedere in licenza componenti e controlli.

  • Verificare la serializzazione dei controlli e la generazione del relativo codice. Per ulteriori informazioni, vedere Generazione e compilazione dinamica di codice sorgente.

Vedere anche

Attività

Procedura: creare un controllo Windows Form che utilizza le funzionalità di progettazione

Riferimenti

UserControl

ParentControlDesigner

DocumentDesigner

IRootDesigner

DesignerVerb

UITypeEditor

BackgroundWorker

Altre risorse

Estensione del supporto in fase di progettazione

Finestre di progettazione personalizzate

.NET Shape Library: A Sample Designer (informazioni in lingua inglese)