Procedura dettagliata: Creare un controllo che sfrutta le funzionalità della fase di progettazione

L'esperienza in fase di progettazione per un controllo personalizzato può essere migliorata creando una finestra di progettazione personalizzata associata.

Attenzione

Questo contenuto è stato scritto per .NET Framework. Se si usa .NET 6 o una versione successiva, usare questo contenuto con cautela. Il sistema di progettazione è stato modificato per Windows Form ed è importante esaminare le modifiche apportate alla finestra di progettazione dall'articolo .NET Framework.

Questo articolo illustra come creare una finestra di progettazione personalizzata per un controllo personalizzato. Si implementerà un MarqueeControl tipo e una classe di progettazione associata denominata MarqueeControlRootDesigner.

Il MarqueeControl tipo implementa un display simile a un riquadro teatrale con luci animate e testo lampeggiante.

La finestra di progettazione per questo controllo interagisce con l'ambiente di progettazione per offrire un'esperienza di progettazione personalizzata. Con la finestra di progettazione personalizzata, è possibile assemblare un'implementazione personalizzata MarqueeControl con luci animate e testo lampeggiante in molte combinazioni. È possibile utilizzare il controllo assemblato in una maschera come qualsiasi altro controllo Windows Form.

Al termine di questa procedura dettagliata, il controllo personalizzato avrà un aspetto simile al seguente:

The app showing a marquee saying Text and a Start and Stop buttons.

Per l'elenco di codice completo, vedere Procedura: Creare un controllo Windows Form che sfrutta le funzionalità della fase di progettazione.

Prerequisiti

Per completare questa procedura dettagliata, è necessario Visual Studio.

Creare il progetto

Il primo passaggio consiste nel creare il progetto dell'applicazione. Questo progetto verrà usato per compilare l'applicazione che ospita il controllo personalizzato.

In Visual Studio creare un nuovo progetto di applicazione Windows Form e denominarlo MarqueeControlTest.

Creare il progetto della libreria di controlli

  1. Aggiungere un progetto libreria di controlli Windows Form alla soluzione. Assegnare al progetto il nome MarqueeControlLibrary.

  2. Usando Esplora soluzioni, eliminare il controllo predefinito del progetto eliminando il file di origine denominato "UserControl1.cs" o "UserControl1.vb", a seconda della lingua scelta.

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

  4. Usando Esplora soluzioni, creare una nuova cartella nel MarqueeControlLibrary progetto.

  5. Fare clic con il pulsante destro del mouse sulla cartella Progettazione e aggiungere una nuova classe. Denominarlo MarqueeControlRootDesigner.

  6. È necessario usare i tipi dell'assembly System.Design, quindi aggiungere questo riferimento al MarqueeControlLibrary progetto.

Fare riferimento al progetto di controllo personalizzato

Si userà il MarqueeControlTest progetto per testare il controllo personalizzato. Il progetto di test verrà a conoscenza del controllo personalizzato quando si aggiunge un riferimento al progetto all'assembly MarqueeControlLibrary .

MarqueeControlTest Nel progetto aggiungere un riferimento al progetto all'assemblyMarqueeControlLibrary. Assicurarsi di usare la scheda Progetti nella finestra di dialogo Aggiungi riferimento anziché fare riferimento direttamente all'assembly MarqueeControlLibrary .

Definire un controllo personalizzato e la relativa finestra di progettazione personalizzata

Il controllo personalizzato deriva dalla UserControl classe . In questo modo il controllo può contenere altri controlli e offre al controllo una grande quantità di funzionalità predefinite.

Il controllo personalizzato avrà una finestra di progettazione personalizzata associata. In questo modo è possibile creare un'esperienza di progettazione unica su misura per il controllo personalizzato.

Il controllo viene associato alla relativa finestra di progettazione usando la DesignerAttribute classe . Poiché si sviluppa 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 MarqueeControl file di origine nell'editor di codice. Nella parte superiore del file importare gli spazi dei nomi seguenti:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  2. Aggiungere l'oggetto DesignerAttribute alla dichiarazione di MarqueeControl classe. In questo modo il controllo personalizzato viene associato alla relativa finestra di progettazione.

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

    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;
    
    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
    
  4. Modificare la dichiarazione di MarqueeControlRootDesigner per ereditare dalla DocumentDesigner classe . ToolboxItemFilterAttribute Applicare per specificare l'interazione della finestra di progettazione con la casella degli strumenti.

    Nota

    La definizione per la MarqueeControlRootDesigner classe è stata racchiusa in uno spazio dei nomi denominato MarqueeControlLibrary.Design. Questa dichiarazione inserisce la finestra di progettazione in uno spazio dei nomi speciale riservato per i tipi correlati alla progettazione.

    namespace MarqueeControlLibrary.Design
    {
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
        public class MarqueeControlRootDesigner : DocumentDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
        ToolboxItemFilterType.Require), _
        ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
        ToolboxItemFilterType.Require)> _
        Public Class MarqueeControlRootDesigner
            Inherits DocumentDesigner
    
  5. Definire il costruttore per la MarqueeControlRootDesigner classe . Inserire un'istruzione WriteLine nel corpo del costruttore. Ciò sarà utile per il debug.

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

Creare un'istanza del controllo personalizzato

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

  2. Aprire il DemoMarqueeControl file nell'editor di codice. Nella parte superiore del file importare lo MarqueeControlLibrary spazio dei nomi:

    Imports MarqueeControlLibrary
    
    using MarqueeControlLibrary;
    
  3. Modificare la dichiarazione di DemoMarqueeControl per ereditare dalla MarqueeControl classe .

  4. Compilare il progetto.

  5. Aprire Form1 nella finestra di progettazione Windows Form.

  6. Trovare la scheda MarqueeControlTest Components (Componenti MarqueeControlTest) nella casella degli strumenti e aprirla. Trascinare un DemoMarqueeControl oggetto dalla casella degli strumenti nel form.

  7. Compilare il progetto.

Configurare il progetto per il debug in fase di progettazione

Quando si sviluppa un'esperienza personalizzata in fase di progettazione, sarà necessario eseguire il debug dei controlli e dei componenti. Esiste un modo semplice per configurare il progetto per consentire il debug in fase di progettazione. Per altre informazioni, vedere Procedura dettagliata: Debug di controlli personalizzati Windows Form in fase di progettazione.

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

  2. Nella finestra di dialogo Pagine delle proprietà MarqueeControlLibrary selezionare la pagina Debug.

  3. Nella sezione Avvia azione selezionare Avvia programma esterno. Si eseguirà il debug di un'istanza separata di Visual Studio, quindi fare clic sul pulsante con i puntini di sospensione (The Ellipsis button (...) in the Properties window of Visual Studio) per cercare l'IDE di Visual Studio. Il nome del file eseguibile è devenv.exe e, se è stato installato nel percorso predefinito, il percorso è %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.

  4. Selezionare 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 abilitare questa configurazione di debug.

Checkpoint

È ora possibile eseguire il debug del comportamento in fase di progettazione del controllo personalizzato. Dopo aver stabilito che l'ambiente di debug è configurato correttamente, si verificherà l'associazione tra il controllo personalizzato e la finestra di progettazione personalizzata.

Per testare l'ambiente di debug e l'associazione della finestra di progettazione

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

  2. Premere F5 per avviare la sessione di debug.

    Viene creata una nuova istanza di Visual Studio.

  3. Nella nuova istanza di Visual Studio aprire la soluzione MarqueeControlTest. È possibile trovare facilmente la soluzione selezionando Progetti recenti dal menu File. Il file di soluzione MarqueeControlTest.sln verrà elencato come file usato più di recente.

  4. DemoMarqueeControl Aprire nella finestra di progettazione.

    L'istanza di debug di Visual Studio ottiene lo stato attivo e l'esecuzione si arresta in corrispondenza del punto di interruzione. Premere F5 per continuare la sessione di debug.

A questo punto, tutto è disponibile per sviluppare ed eseguire il debug del controllo personalizzato e della finestra di progettazione personalizzata associata. La parte restante di questo articolo si concentra sui dettagli dell'implementazione delle funzionalità del controllo e della finestra di progettazione.

Implementare il controllo personalizzato

è MarqueeControl un oggetto UserControl con un po' di personalizzazione. Espone due metodi: Start, che avvia l'animazione di selezione e Stop, che arresta l'animazione. MarqueeControl Poiché contiene controlli figlio che implementano l'interfaccia StartIMarqueeWidget ed Stop enumerano ogni controllo figlio e chiamano rispettivamente i StartMarquee metodi e StopMarquee in ogni controllo figlio che implementa IMarqueeWidget.

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

Si tratta dell'estensione delle MarqueeControl personalizzazioni. Le funzionalità di runtime vengono implementate dai MarqueeBorder controlli e MarqueeText e le funzionalità in fase di progettazione vengono implementate dalle MarqueeBorderDesigner classi e MarqueeControlRootDesigner .

Per implementare il controllo personalizzato

  1. Aprire il MarqueeControl file di origine nell'editor di codice. Implementare i Start metodi e Stop .

    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();
            }
        }
    }
    
    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
    
  2. Eseguire l'override del metodo OnLayout.

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

Creare un controllo figlio per il controllo personalizzato

Ospiterà MarqueeControl due tipi di controllo figlio: il MarqueeBorder controllo e il MarqueeText controllo .

  • MarqueeBorder: questo controllo disegna un bordo di "luci" intorno ai bordi. Le luci lampeggiano in sequenza, quindi sembrano muoversi intorno al bordo. Velocità con cui il flash delle luci è controllato da una proprietà denominata UpdatePeriod. Diverse altre proprietà personalizzate determinano altri aspetti dell'aspetto del controllo. Due metodi, chiamati StartMarquee e StopMarquee, controllano all'avvio e all'arresto dell'animazione.

  • MarqueeText: questo controllo disegna una stringa lampeggiante. Analogamente al MarqueeBorder controllo, la velocità con cui il testo lampeggia è controllato dalla UpdatePeriod proprietà . Il MarqueeText controllo dispone inoltre dei StartMarquee metodi e StopMarquee in comune con il MarqueeBorder controllo .

In fase di progettazione, consente l'aggiunta MarqueeControlRootDesigner di questi due tipi di controllo a in MarqueeControl qualsiasi combinazione.

Le funzionalità comuni dei due controlli vengono inserite in un'interfaccia denominata IMarqueeWidget. In questo modo è MarqueeControl possibile individuare i controlli figlio correlati a Marquee e dare loro un trattamento speciale.

Per implementare la funzionalità di animazione periodica, userai BackgroundWorker gli oggetti dello spazio dei System.ComponentModel nomi . È possibile usare Timer oggetti, ma quando sono presenti molti IMarqueeWidget oggetti, il singolo thread dell'interfaccia utente potrebbe non essere in grado di tenere il passo con l'animazione.

Per creare un controllo figlio per il controllo personalizzato

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

  2. Aprire il IMarqueeWidget file di origine nell'editordi 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 codice seguente all'interfaccia IMarqueeWidget per esporre due metodi e una proprietà che modifica l'animazione di selezione:

    // 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;
        }
    }
    
    ' 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
    
  4. Aggiungere un nuovo elemento controllo personalizzato al MarqueeControlLibrary progetto. Assegnare al nuovo file di origine un nome di base "MarqueeText".

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

  6. Nella finestra Proprietà impostare le BackgroundWorker proprietà e WorkerSupportsCancellation del WorkerReportsProgress componente su true. Queste impostazioni consentono al BackgroundWorker componente di generare periodicamente l'evento ProgressChanged e di annullare gli aggiornamenti asincroni.

    Per altre informazioni, vedere Componente BackgroundWorker.

  7. Aprire il MarqueeText file di origine nell'editor di codice. Nella parte superiore del file importare gli spazi dei nomi seguenti:

    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;
    
    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
    
  8. Modificare la dichiarazione di MarqueeText per ereditare da Label e per implementare l'interfaccia IMarqueeWidget :

    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    public partial class MarqueeText : Label, IMarqueeWidget
    {
    
    <ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeText
        Inherits Label
        Implements IMarqueeWidget
    
  9. Dichiarare le variabili di istanza che corrispondono alle proprietà esposte e inizializzarle nel costruttore. Il isLit campo determina se il testo deve essere disegnato nel colore specificato dalla LightColor proprietà .

    // 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);
    }
    
    ' 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
    
  10. Implementare l'interfaccia IMarqueeWidget.

    I metodi e StopMarquee richiamano i StartMarqueeBackgroundWorker metodi e CancelAsync del RunWorkerAsync componente per avviare e arrestare l'animazione.

    Gli Category attributi e Browsable vengono applicati alla UpdatePeriod proprietà in modo che vengano visualizzati in una sezione personalizzata della Finestra Proprietà denominata "Marquee".

    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");
            }
        }
    }
    
    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
    
  11. Implementare le funzioni di accesso alle proprietà. Verranno esposte due proprietà ai client: LightColor e DarkColor. Gli Category attributi e Browsable vengono applicati a queste proprietà, quindi le proprietà vengono visualizzate in una sezione personalizzata del Finestra Proprietà denominata "Marquee".

    [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 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
    
  12. Implementare i gestori per gli eventi e ProgressChanged del DoWorkBackgroundWorker componente.

    Il DoWork gestore eventi rimane in sospensione per il numero di millisecondi specificato da UpdatePeriod e quindi genera l'evento, fino a quando il codice non arresta l'animazione ProgressChanged chiamando CancelAsync.

    Il ProgressChanged gestore eventi attiva/disattiva il testo tra il relativo stato chiaro e scuro per dare l'aspetto del flashing.

    // 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();
    }
    
    
    ' 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
    
  13. Eseguire l'override del OnPaint metodo per abilitare l'animazione.

    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);
    }
    
    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
    
  14. Premere F6 per compilare la soluzione.

Creare il controllo figlio MarqueeBorder

Il MarqueeBorder controllo è leggermente più sofisticato del MarqueeText controllo. Ha più proprietà e l'animazione nel OnPaint metodo è più coinvolta. In linea di principio, è molto simile al MarqueeText controllo.

Poiché il MarqueeBorder controllo può avere controlli figlio, deve essere a conoscenza degli Layout eventi.

Per creare il controllo MarqueeBorder

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

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

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

  4. Nella finestra Proprietà selezionare il pulsante Eventi. Collegare gestori per gli DoWork eventi e ProgressChanged .

  5. Aprire il MarqueeBorder file di origine nell'editor di codice. Nella parte superiore del file importare gli spazi dei nomi seguenti:

    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;
    
    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
    
  6. Modificare la dichiarazione di MarqueeBorder per ereditare da Panel e per implementare l'interfaccia IMarqueeWidget .

    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {
    
    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _
    ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeBorder
        Inherits Panel
        Implements IMarqueeWidget
    
  7. Dichiara due enumerazioni per la gestione MarqueeBorder dello stato del controllo: MarqueeSpinDirection, che determina la direzione in cui le luci "ruotano" intorno al bordo e MarqueeLightShape, che determina la forma delle luci (quadrate o circolari). Inserire queste dichiarazioni prima della dichiarazione di MarqueeBorder classe.

    // 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
    }
    
    ' 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
    
  8. Dichiarare le variabili di istanza che corrispondono alle proprietà esposte e inizializzarle nel costruttore.

    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);
    }
    
    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
    
  9. Implementare l'interfaccia IMarqueeWidget.

    I metodi e StopMarquee richiamano i StartMarqueeBackgroundWorker metodi e CancelAsync del RunWorkerAsync componente per avviare e arrestare l'animazione.

    Poiché il MarqueeBorder controllo può contenere controlli figlio, il StartMarquee metodo enumera tutti i controlli figlio e le chiamate StartMarquee a quelli che implementano IMarqueeWidget. Il StopMarquee metodo ha un'implementazione simile.

    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");
            }
        }
    }
    
    
    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
    
  10. Implementare le funzioni di accesso alle proprietà. Il MarqueeBorder controllo dispone di diverse proprietà per controllarne l'aspetto.

    [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;
        }
    }
    
    
    <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
    
  11. Implementare i gestori per gli eventi e ProgressChanged del DoWorkBackgroundWorker componente.

    Il DoWork gestore eventi rimane in sospensione per il numero di millisecondi specificato da UpdatePeriod e quindi genera l'evento, fino a quando il codice non arresta l'animazione ProgressChanged chiamando CancelAsync.

    Il ProgressChanged gestore eventi incrementa la posizione della luce "base", da cui viene determinato lo stato chiaro/scuro delle altre luci e chiama il metodo per causare il Refresh ripaint del controllo stesso.

    // 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();
    }
    
    ' 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
    
  12. Implementare i metodi IsLit helper e DrawLight.

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

    Il DrawLight metodo 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 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;
                }
        }
    }
    
    ' 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
    
  13. Eseguire l'override dei metodi OnLayout e OnPaint.

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

    Poiché il OnPaint metodo dipende dalle dimensioni del MarqueeBorder controllo, è necessario chiamarlo ogni volta che il layout cambia. A tale scopo, eseguire l'override OnLayout e chiamare Refresh.

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

Creare una finestra di progettazione personalizzata per nascondere e filtrare le proprietà

La MarqueeControlRootDesigner classe fornisce l'implementazione per la finestra di progettazione radice. Oltre a questa finestra di progettazione, che opera su MarqueeControl, è necessaria una finestra di progettazione personalizzata specificatamente associata al MarqueeBorder controllo . Questa finestra di progettazione fornisce un comportamento personalizzato appropriato nel contesto della finestra di progettazione radice personalizzata.

In particolare, l'oggetto MarqueeBorderDesigner "ombreggiatura" e filtra determinate proprietà sul MarqueeBorder controllo, modificandone l'interazione con l'ambiente di progettazione.

L'intercettazione delle chiamate alla funzione di accesso alle proprietà di un componente è nota come "shadowing". Consente a una finestra di progettazione di tenere traccia del valore impostato dall'utente e, facoltativamente, passare tale valore al componente progettato.

Per questo esempio, le Visible proprietà e Enabled verranno ombreggiate da MarqueeBorderDesigner, che impedisce all'utente di rendere il MarqueeBorder controllo invisibile o disabilitato durante la fase di progettazione.

I progettisti possono anche aggiungere e rimuovere proprietà. Per questo esempio, la Padding proprietà verrà rimossa in fase di progettazione, perché il MarqueeBorder controllo imposta a livello di codice la spaziatura interna in base alle dimensioni delle luci specificate dalla LightSize proprietà .

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

Quando si modifica l'interfaccia pubblica di un componente usando questi metodi, seguire queste regole:

  • Aggiungere o rimuovere elementi solo nei PreFilter metodi

  • Modificare solo gli elementi esistenti nei PostFilter metodi

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

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

L'adesione a queste regole garantisce che tutti i progettisti nell'ambiente in fase di progettazione abbiano una visualizzazione coerente di tutti i componenti da progettare.

La ComponentDesigner classe fornisce un dizionario per la gestione dei valori delle proprietà ombreggiate, che consente di ridurre la necessità di creare variabili di istanza specifiche.

Per creare una finestra di progettazione personalizzata per nascondere e filtrare le proprietà

  1. Fare clic con il pulsante destro del mouse sulla cartella Progettazione e aggiungere una nuova classe. Assegnare al file di origine un nome di base di MarqueeBorderDesigner.

  2. Aprire il file di origine MarqueeBorderDesigner nell'editor di codice. Nella parte superiore del file importare gli spazi dei nomi seguenti:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  3. Modificare la dichiarazione di MarqueeBorderDesigner per ereditare da ParentControlDesigner.

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

    namespace MarqueeControlLibrary.Design
    {
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
  4. Eseguire l'override dell'implementazione di base di PreFilterProperties.

    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]);
    }
    
    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
    
  5. Implementare le proprietà Enabled e Visible. Queste implementazioni oscurano le proprietà del controllo.

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

Gestire le modifiche dei componenti

La MarqueeControlRootDesigner classe fornisce l'esperienza di progettazione personalizzata per le MarqueeControl istanze. La maggior parte delle funzionalità della fase di progettazione viene ereditata dalla DocumentDesigner classe . Il codice implementerà due personalizzazioni specifiche: la gestione delle modifiche dei componenti e l'aggiunta di verbi della finestra di progettazione.

Quando gli utenti progettano le istanze MarqueeControl , la finestra di progettazione radice tengono traccia delle modifiche apportate ai MarqueeControl controlli figlio e . L'ambiente in fase di progettazione offre un servizio pratico, IComponentChangeService, per tenere traccia delle modifiche allo stato del componente.

Si acquisisce un riferimento a questo servizio eseguendo una query sull'ambiente con il GetService metodo . Se la query ha esito positivo, la finestra di progettazione può collegare un gestore per l'evento ComponentChanged ed eseguire le attività necessarie per mantenere uno stato coerente in fase di progettazione.

Nel caso della MarqueeControlRootDesigner classe , si chiamerà il Refresh metodo su ogni IMarqueeWidget oggetto contenuto da MarqueeControl. In questo modo l'oggetto IMarqueeWidget verrà ridisegnato in modo appropriato quando vengono modificate proprietà come le Size proprietà padre.

Per gestire le modifiche dei componenti

  1. Aprire il MarqueeControlRootDesigner file di origine nell'editor di codice ed eseguire l'override del Initialize metodo . Chiamare l'implementazione di base di Initialize e la query per .IComponentChangeService

    base.Initialize(component);
    
    IComponentChangeService cs =
        GetService(typeof(IComponentChangeService))
        as IComponentChangeService;
    
    if (cs != null)
    {
        cs.ComponentChanged +=
            new ComponentChangedEventHandler(OnComponentChanged);
    }
    
    MyBase.Initialize(component)
    
    Dim cs As IComponentChangeService = _
    CType(GetService(GetType(IComponentChangeService)), _
    IComponentChangeService)
    
    If (cs IsNot Nothing) Then
        AddHandler cs.ComponentChanged, AddressOf OnComponentChanged
    End If
    
  2. Implementare il OnComponentChanged gestore eventi. Testare il tipo del componente di invio e, se si tratta di , IMarqueeWidgetchiamare il relativo Refresh metodo.

    private void OnComponentChanged(
        object sender,
        ComponentChangedEventArgs e)
    {
        if (e.Component is IMarqueeWidget)
        {
            this.Control.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
    

Aggiungere verbi di progettazione alla finestra di progettazione personalizzata

Un verbo di progettazione è un comando di menu collegato a un gestore evento. I verbi della finestra di progettazione vengono aggiunti al menu di scelta rapida di un componente in fase di progettazione. Per ulteriori informazioni, vedere DesignerVerb.

Verranno aggiunti due verbi della finestra di progettazione alle finestre di progettazione: Esegui test e Arresta test. Questi verbi consentono di visualizzare il comportamento di runtime di in fase di MarqueeControl progettazione. Questi verbi verranno aggiunti a MarqueeControlRootDesigner.

Quando viene richiamato Il test di esecuzione, il gestore eventi verbo chiamerà il StartMarquee metodo su MarqueeControl. Quando viene richiamato Stop Test , il gestore eventi verbo chiamerà il StopMarquee metodo su MarqueeControl. L'implementazione dei StartMarquee metodi e StopMarquee chiama questi metodi su controlli contenuti che implementano IMarqueeWidget, in modo che tutti i controlli contenuti IMarqueeWidget partecipino anche al test.

Per aggiungere verbi di progettazione alle finestre di progettazione personalizzate

  1. MarqueeControlRootDesigner Nella classe aggiungere gestori eventi denominati OnVerbRunTest e OnVerbStopTest.

    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();
    }
    
    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
    
  2. Connessione questi gestori eventi ai verbi della finestra di progettazione corrispondenti. MarqueeControlRootDesigner eredita un oggetto DesignerVerbCollection dalla relativa classe di base. Verranno creati due nuovi DesignerVerb oggetti e aggiunti a questa raccolta nel Initialize metodo .

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

Creare un UITypeEditor personalizzato

Quando si crea un'esperienza di progettazione personalizzata per gli utenti, è spesso consigliabile creare un'interazione personalizzata con il Finestra Proprietà. A tale scopo, è possibile creare un oggetto UITypeEditor.

Il MarqueeBorder controllo espone diverse proprietà nella Finestra Proprietà. Due di queste proprietà MarqueeSpinDirection e MarqueeLightShape sono rappresentate da enumerazioni. Per illustrare l'uso di un editor dei tipi di interfaccia utente, la MarqueeLightShape proprietà avrà una classe associata UITypeEditor .

Per creare un editor di tipi di interfaccia utente personalizzato

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

  2. Nella definizione della MarqueeBorder classe dichiarare una classe denominata LightShapeEditor che deriva 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.
    internal class LightShapeEditor : 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
    
  3. Dichiarare una IWindowsFormsEditorService variabile di istanza denominata editorService.

    private IWindowsFormsEditorService editorService = null;
    
    Private editorService As IWindowsFormsEditorService = Nothing
    
  4. Eseguire l'override del metodo GetEditStyle. Questa implementazione restituisce DropDown, che indica all'ambiente di progettazione come visualizzare .LightShapeEditor

    public override UITypeEditorEditStyle GetEditStyle(
    System.ComponentModel.ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    
    Public Overrides Function GetEditStyle( _
    ByVal context As System.ComponentModel.ITypeDescriptorContext) _
    As UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function
    
    
  5. Eseguire l'override del metodo EditValue. Questa implementazione esegue una query sull'ambiente di progettazione per un IWindowsFormsEditorService oggetto . In caso di esito positivo, crea un oggetto LightShapeSelectionControl. Il DropDownControl metodo viene richiamato per avviare .LightShapeEditor Il valore restituito da questa chiamata viene restituito all'ambiente di progettazione.

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

Creare un controllo Visualizzazione per uiTypeEditor personalizzato

La MarqueeLightShape proprietà supporta due tipi di forme leggere: Square e Circle. Verrà creato un controllo personalizzato utilizzato esclusivamente allo scopo di visualizzare graficamente questi valori nella Finestra Proprietà. Questo controllo personalizzato verrà usato dall'utente UITypeEditor per interagire con il Finestra Proprietà.

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

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

  2. Trascinare due Panel controlli dalla casella degli strumenti all'oggetto LightShapeSelectionControl. Denominarli squarePanel e circlePanel. Disponili affiancati. Impostare la Size proprietà di entrambi Panel i controlli su (60, 60). Impostare la Location proprietà del squarePanel controllo su (8, 10). Impostare la Location proprietà del circlePanel controllo su (80, 10). Impostare infine la Size proprietà di LightShapeSelectionControl su (150, 80).

  3. Aprire il LightShapeSelectionControl file di origine nell'editor di codice. Nella parte superiore del file importare lo System.Windows.Forms.Design spazio dei nomi:

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

    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();
    }
    
    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
    
  5. Dichiarare una IWindowsFormsEditorService variabile di istanza denominata editorService.

    Private editorService As IWindowsFormsEditorService
    
    private IWindowsFormsEditorService editorService;
    
  6. Dichiarare una variabile di MarqueeLightShape istanza denominata lightShapeValue.

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

    // 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);
    }
    
    ' 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
    
  8. Dispose Nel metodo scollegare i Click gestori eventi.

    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 );
    }
    
    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
    
  9. In Esplora soluzioni fare clic sul pulsante Mostra tutti i file. Aprire il file LightShapeSelectionControl.Designer.cs o LightShapeSelectionControl.Designer.vb e rimuovere la definizione predefinita del Dispose metodo.

  10. Implementare la proprietà LightShape.

    // 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;
            }
        }
    }
    
    ' 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
    
  11. Eseguire l'override del metodo OnPaint. Questa implementazione disegna un quadrato pieno e un cerchio. Evidenzia anche il valore selezionato disegnando un bordo attorno a una forma o all'altra.

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

Testare il controllo personalizzato nella finestra di progettazione

A questo punto, è possibile compilare il MarqueeControlLibrary progetto. Testare l'implementazione creando un controllo che eredita dalla MarqueeControl classe e usandolo in un modulo.

Per creare un'implementazione personalizzata di MarqueeControl

  1. Aprire DemoMarqueeControl in Progettazione Windows Form. Viene creata un'istanza del DemoMarqueeControl tipo e la viene visualizzata in un'istanza del MarqueeControlRootDesigner tipo.

  2. Nella casella degli strumenti aprire la scheda Componenti di MarqueeControlLibrary. Verranno visualizzati i controlli e MarqueeText disponibili per la MarqueeBorder selezione.

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

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

  5. Compilare la soluzione.

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

  7. Aprire Form1 nella visualizzazione Progettazione.

  8. Posizionare due Button controlli nel form. Denominarli startButton e stopButtone modificare i valori delle Text proprietà rispettivamente in Start e Stop.

  9. Implementare Click i gestori eventi per entrambi Button i controlli.

  10. Nella casella degli strumenti aprire la scheda Componenti MarqueeControlTest. Verrà visualizzato il valore disponibile per la DemoMarqueeControl selezione.

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

  12. Click Nei gestori eventi richiamare i Start metodi 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();
    }
    
  13. Impostare il MarqueeControlTest progetto come progetto di avvio ed eseguirlo. Verrà visualizzato il modulo che DemoMarqueeControlvisualizza . Selezionare il pulsante Start per avviare l'animazione. Dovrebbe essere visualizzato il testo lampeggiante e le luci che si spostano intorno al bordo.

Passaggi successivi

Illustra MarqueeControlLibrary una semplice implementazione di controlli personalizzati e finestre di progettazione associate. È possibile rendere questo esempio più sofisticato in diversi modi:

  • Modificare i valori delle proprietà per DemoMarqueeControl nella finestra di progettazione. Aggiungere altri MarqueBorder controlli e ancorarli all'interno delle istanze padre per creare un effetto annidato. Sperimentare diverse impostazioni per le UpdatePeriod proprietà correlate alla luce e .

  • Creare implementazioni personalizzate di IMarqueeWidget. È possibile, ad esempio, creare un "segno neon" lampeggiante o un segno animato con più immagini.

  • Personalizzare ulteriormente l'esperienza in fase di progettazione. È possibile provare a nascondere più proprietà di Enabled e Visiblee è possibile aggiungere nuove proprietà. Aggiungere nuovi verbi della finestra di progettazione per semplificare attività comuni come l'ancoraggio dei controlli figlio.

  • Assegnare una licenza a MarqueeControl.

  • Controllare la modalità di serializzazione dei controlli e la modalità di generazione del codice. Per altre informazioni, vedere Generazione e compilazione di codice sorgente dinamico.

Vedi anche