Procedura dettagliata: Creare un controllo che sfrutta le funzionalità della fase di progettazione
L'esperienza di progettazione per un controllo personalizzato può essere migliorata creando un progettista personalizzato associato.
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 tipo di MarqueeControl
e una classe di progettazione associata denominata MarqueeControlRootDesigner
.
Il tipo MarqueeControl
implementa un display simile a un riquadro teatrale con luci animate e testo lampeggiante.
Il progettista per questo controllo interagisce con l'ambiente di progettazione per offrire un'esperienza personalizzata in fase di progettazione. Con la finestra di progettazione personalizzata, è possibile assemblare un'implementazione di MarqueeControl
personalizzata con luci animate e testo lampeggiante in molte combinazioni. È possibile utilizzare il controllo assemblato in un form come qualsiasi altro controllo Windows Forms.
Al termine di questa procedura dettagliata, il controllo personalizzato avrà un aspetto simile al seguente:
Per l'elenco di codice completo, vedere Procedura: Creare un controllo Windows Forms che trae vantaggio dalle funzionalità di Design-Time.
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
Aggiungere un progetto di Libreria di controlli Windows Forms alla soluzione. Denominare il progetto MarqueeControlLibrary.
Utilizzando Solution Explorer, eliminate il controllo predefinito del progetto eliminando il file sorgente denominato "UserControl1.cs" o "UserControl1.vb", a seconda della lingua scelta.
Aggiungere un nuovo elemento UserControl al progetto
MarqueeControlLibrary
. Assegnare al nuovo file di origine un nome di base di MarqueeControl.Usa Esplora Soluzioniper creare una nuova cartella nel progetto
MarqueeControlLibrary
.Fare clic con il pulsante destro del mouse sulla cartella Design e aggiungere una nuova classe. Assegna il nome MarqueeControlRootDesigner.
È necessario usare i tipi dell'assembly System.Design, quindi aggiungere questo riferimento al progetto
MarqueeControlLibrary
.
Fare riferimento al progetto relativo al controllo personalizzato
Si userà il progetto MarqueeControlTest
per testare il controllo personalizzato. Il progetto di test riconoscerà il controllo personalizzato quando si aggiunge un riferimento all'assembly MarqueeControlLibrary
nel progetto.
Nel progetto MarqueeControlTest
, aggiungere un riferimento del progetto all'assembly MarqueeControlLibrary
. Assicurarsi di usare la scheda Progetti nella finestra di dialogo Aggiungi riferimento anziché fare riferimento direttamente all'assembly MarqueeControlLibrary
.
Definire un controllo personalizzato e il relativo designer personalizzato
Il tuo controllo personalizzato deriverà dalla classe UserControl. In questo modo, il controllo può contenere altri controlli, offrendo un'ampia gamma di funzionalità predefinite.
Il controllo personalizzato avrà un designer personalizzato associato. In questo modo è possibile creare un'esperienza di progettazione unica su misura per il controllo personalizzato.
Associa il controllo al suo progettista usando la classe DesignerAttribute. Poiché stai sviluppando l'intero comportamento del controllo personalizzato nella fase di design, il designer personalizzato implementerà l'interfaccia IRootDesigner.
Per definire un controllo personalizzato e il suo designer personalizzato
Aprire il file di origine
MarqueeControl
nell'editor di codice . In cima al file, importa i seguenti namespace: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
Aggiungere il DesignerAttribute alla dichiarazione di classe
MarqueeControl
. In questo modo il controllo personalizzato viene associato al relativo designer.[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : UserControl {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControl
Aprire il file di origine
MarqueeControlRootDesigner
nell'editor di codice . Nella parte superiore del file importare i namespace 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
Modificare la dichiarazione di
MarqueeControlRootDesigner
per ereditare dalla classe DocumentDesigner. Applicare il ToolboxItemFilterAttribute per specificare l'interazione del designer con la Toolbox .Nota
La definizione per la classe
MarqueeControlRootDesigner
è stata racchiusa in uno spazio dei nomi denominato MarqueeControlLibrary.Design. Questa dichiarazione inserisce il designer in uno spazio dei nomi speciale riservato ai 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
Definire il costruttore per la classe
MarqueeControlRootDesigner
. 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
Aggiungere un nuovo elemento UserControl al progetto
MarqueeControlTest
. Assegnare al nuovo file di origine un nome di base di DemoMarqueeControl.Apri il file
DemoMarqueeControl
nell'editor di codice . All'inizio del file, importa lo spazio dei nomiMarqueeControlLibrary
:Imports MarqueeControlLibrary
using MarqueeControlLibrary;
Modificare la dichiarazione di
DemoMarqueeControl
per ereditare dalla classeMarqueeControl
.Costruire il progetto.
Apri Form1 nel Designer di Windows Form.
Trova la scheda dei componenti MarqueeControlTest nella Toolbox e aprila. Trascinare un
DemoMarqueeControl
dalla casella degli strumenti nel form.Costruire il progetto.
Configurare il progetto per il debug di Design-Time
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 Windows Form personalizzati in fase di progettazione.
Fare clic con il pulsante destro del mouse sul progetto
MarqueeControlLibrary
e selezionare Proprietà.Nella finestra di dialogo Pagine delle proprietà MarqueeControlLibrary, seleziona la pagina Debug.
Nella sezione Avvia azione, selezionare Avvia programma esterno. Si eseguirà il debug di un'istanza separata di Visual Studio, quindi fare clic sui puntini di sospensione (
) per cercare l'IDE di Visual Studio. Il nome del file eseguibile è devenv.exee, se è stato installato nel percorso predefinito, il percorso è %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.
Selezionare OK per chiudere la finestra di dialogo.
Fare clic con il pulsante destro del mouse sul progetto MarqueeControlLibrary e selezionare Imposta come progetto di avvio per abilitare questa configurazione di debug.
Posto di blocco
È 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 il designer personalizzato.
Per testare l'ambiente di debug e l'associazione del progettista
Aprire il file di origine MarqueeControlRootDesigner nell'editor di codice e posizionare un punto di interruzione nell'istruzione WriteLine.
Premere F5 per avviare la sessione di debug.
Viene creata una nuova istanza di Visual Studio.
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.
Apri il
DemoMarqueeControl
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. Il resto dell'articolo si concentra sui dettagli dell'implementazione delle funzionalità del controllo e del designer.
Implementare il controllo personalizzato
Il MarqueeControl
è un UserControl con un po' di personalizzazione. Espone due metodi: Start
, che avvia l'animazione di selezione e Stop
, che arresta l'animazione. Poiché il MarqueeControl
contiene controlli figlio che implementano l'interfaccia IMarqueeWidget
, Start
e Stop
enumerano ogni controllo figlio e chiamano rispettivamente i metodi StartMarquee
e StopMarquee
su ogni controllo figlio che implementa IMarqueeWidget
.
L'aspetto dei controlli MarqueeBorder
e MarqueeText
dipende dal layout, pertanto MarqueeControl
esegue l'override del metodo OnLayout e chiama PerformLayout sui controlli figlio di questo tipo.
Questa è la portata delle personalizzazioni MarqueeControl
. Le funzionalità di runtime vengono implementate dai controlli MarqueeBorder
e MarqueeText
e le funzionalità in fase di progettazione vengono implementate dalle classi MarqueeBorderDesigner
e MarqueeControlRootDesigner
.
Per implementare il controllo personalizzato
Aprire il file di origine
MarqueeControl
nell'editor di codice . Implementare i metodiStart
eStop
.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
Sovrascrivere il 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 tuo controllo personalizzato
Il MarqueeControl
ospiterà due tipi di controlli secondari: il controllo MarqueeBorder
e il controllo MarqueeText
.
MarqueeBorder
: Questo controllo disegna una cornice di "luci" intorno ai bordi. Le luci lampeggiano in sequenza, quindi sembrano muoversi intorno al bordo. La velocità con cui le luci lampeggiano è controllata da una proprietà denominataUpdatePeriod
. Diverse altre proprietà personalizzate determinano altri aspetti dell'aspetto del controllo. Due metodi, denominatiStartMarquee
eStopMarquee
, controllano quando l'animazione inizia e si ferma.MarqueeText
: questo controllo disegna una stringa lampeggiante. Come il controlloMarqueeBorder
, la velocità con cui il testo lampeggia è controllato dalla proprietàUpdatePeriod
. Il controlloMarqueeText
include anche i metodiStartMarquee
eStopMarquee
in comune con il controlloMarqueeBorder
.
In fase di progettazione, il MarqueeControlRootDesigner
consente di aggiungere questi due tipi di controllo a un MarqueeControl
in qualsiasi combinazione.
Le funzionalità comuni dei due controlli vengono inserite in un'interfaccia denominata IMarqueeWidget
. In questo modo consente all'MarqueeControl
di individuare i controlli figlio correlati a Marquee e di riservare loro un trattamento speciale.
Per implementare la funzionalità di animazione periodica, utilizzerai oggetti BackgroundWorker dallo spazio dei nomi System.ComponentModel. È possibile usare Timer oggetti, ma quando sono presenti molti oggetti IMarqueeWidget
, 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
Aggiungere un nuovo elemento di classe al progetto
MarqueeControlLibrary
. Assegnare al nuovo file di origine un nome di base "IMarqueeWidget".Aprire il file di origine
IMarqueeWidget
nell'editor di codice e modificare la dichiarazione daclass
ainterface
:// 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
Aggiungere il codice seguente all'interfaccia
IMarqueeWidget
per esporre due metodi e una proprietà che manipolano l'animazione marquee:// 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
Aggiungere un nuovo elemento Controllo personalizzato al progetto
MarqueeControlLibrary
. Assegnare al nuovo file di origine un nome di base "MarqueeText".Trascina un componente BackgroundWorker dalla Toolbox al controllo
MarqueeText
. Questo componente consentirà al controlloMarqueeText
di aggiornarsi in modo asincrono.Nella finestra Proprietà , impostare le proprietà
WorkerReportsProgress
e WorkerSupportsCancellation del componente BackgroundWorker su vero. Queste impostazioni consentono al componente BackgroundWorker di generare periodicamente l'evento ProgressChanged e di annullare gli aggiornamenti asincroni.Per altre informazioni, vedere componente BackgroundWorker.
Aprire il file di origine
MarqueeText
nell'editor di codice . Nella parte superiore del file, importare i namespace 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
Modificare la dichiarazione di
MarqueeText
per ereditare da Label e implementare l'interfacciaIMarqueeWidget
:[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {
<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
Dichiarare le variabili di istanza che corrispondono alle proprietà esposte e inizializzarle nel costruttore. Il campo
isLit
determina se il testo deve essere disegnato nel colore specificato dalla proprietàLightColor
.// When isLit is true, the text is painted in the light color; // When isLit is false, the text is painted in the dark color. // This value changes whenever the BackgroundWorker component // raises the ProgressChanged event. private 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
Implementare l'interfaccia
IMarqueeWidget
.I metodi
StartMarquee
eStopMarquee
richiamano i metodi RunWorkerAsync e CancelAsync del componente BackgroundWorker per avviare e arrestare l'animazione.Gli attributi Category e Browsable vengono applicati alla proprietà
UpdatePeriod
in modo che venga visualizzata 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
Implementare le funzioni di accesso alle proprietà. Verranno esposte due proprietà ai clienti:
LightColor
eDarkColor
. Gli attributi Category e Browsable vengono applicati a queste proprietà, quindi le proprietà vengono visualizzate in una sezione personalizzata della 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
Implementare i gestori per gli eventi DoWork e ProgressChanged del componente BackgroundWorker.
Il gestore eventi DoWork rimane in sospensione per il numero di millisecondi specificato da
UpdatePeriod
quindi genera l'evento ProgressChanged, fino a quando il codice non arresta l'animazione chiamando CancelAsync.Il gestore eventi ProgressChanged alterna il testo tra il suo stato chiaro e scuro per simulare un effetto di lampeggiamento.
// 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
Eseguire l'override del metodo OnPaint 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
Premere F6 per compilare la soluzione.
Creare il controllo figlio MarqueeBorder
Il controllo MarqueeBorder
è leggermente più sofisticato del controllo MarqueeText
. Ha più proprietà e l'animazione nel metodo OnPaint è più coinvolta. In linea di principio, è molto simile al controllo MarqueeText
.
Poiché il controllo MarqueeBorder
può avere controlli figlio, deve essere a conoscenza degli eventi Layout.
Per creare il controllo MarqueeBorder
Aggiungere un nuovo elemento controllo personalizzato al progetto
MarqueeControlLibrary
. Assegnare al nuovo file di origine un nome di base "MarqueeBorder".Trascina un componente BackgroundWorker dalla Toolbox sul controllo
MarqueeBorder
. Questo componente consentirà al controlloMarqueeBorder
di aggiornarsi in modo asincrono.Nella finestra Proprietà
impostare le proprietà e del componente su true . Queste impostazioni consentono al componente BackgroundWorker di generare periodicamente l'evento ProgressChanged e di annullare gli aggiornamenti asincroni. Per altre informazioni, vedere componente BackgroundWorker.Nella finestra Proprietà, selezionare il pulsante Eventi. Collegare gestori per gli eventi DoWork e ProgressChanged.
Aprire il file di origine
MarqueeBorder
nell'editor di codice . Nella parte superiore del file, importa i namespace 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
Modificare la dichiarazione di
MarqueeBorder
per ereditare da Panel e implementare l'interfacciaIMarqueeWidget
.[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
Dichiara due enumerazioni per la gestione dello stato del controllo
MarqueeBorder
:MarqueeSpinDirection
, che determina la direzione in cui le luci "girano" intorno al bordo eMarqueeLightShape
, che determina la forma delle luci (quadrate o circolari). Inserire queste dichiarazioni prima della dichiarazione di classeMarqueeBorder
.// 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
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
Implementare l'interfaccia
IMarqueeWidget
.I metodi
StartMarquee
eStopMarquee
richiamano i metodi RunWorkerAsync e CancelAsync del componente BackgroundWorker per avviare e arrestare l'animazione.Poiché il controllo
MarqueeBorder
può contenere controlli figlio, il metodoStartMarquee
enumera tutti i controlli figlio e chiamaStartMarquee
su quelli che implementanoIMarqueeWidget
. Il metodoStopMarquee
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
Implementare le funzioni di accesso alle proprietà. Il controllo
MarqueeBorder
ha 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
Implementare i gestori per gli eventi DoWork e ProgressChanged del componente BackgroundWorker.
Il gestore eventi DoWork rimane in sospensione per il numero di millisecondi specificato da
UpdatePeriod
quindi genera l'evento ProgressChanged, fino a quando il codice non arresta l'animazione chiamando CancelAsync.Il gestore eventi ProgressChanged incrementa la posizione della luce "base", da cui viene determinato lo stato di luce/ombra delle altre luci e chiama il metodo Refresh per far sì che il controllo si ridisegni.
// 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
Implementare i metodi helper,
IsLit
eDrawLight
.Il metodo
IsLit
determina il colore di una luce in una determinata posizione. Le luci "accese" vengono disegnate nel colore specificato dalla proprietàLightColor
e quelle "scure" vengono disegnate nel colore specificato dalla proprietàDarkColor
.Il metodo
DrawLight
disegna una luce utilizzando il colore, la forma e la posizione appropriati.// This method determines if the marquee light at lightIndex // should be lit. The currentOffset field specifies where // the "first" light is located, and the "position" of the // light given by lightIndex is computed relative to this // offset. If this position modulo lightPeriodValue is zero, // the light is considered to be on, and it will be painted // with the control's lightBrush. protected 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
Eseguire l'override dei metodi OnLayout e OnPaint.
Il metodo OnPaint disegna le luci lungo i bordi del controllo
MarqueeBorder
.Poiché il metodo OnPaint dipende dalle dimensioni del controllo
MarqueeBorder
, è necessario chiamarlo ogni volta che il layout cambia. A tale scopo, effettuare l'override di 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 un progettista personalizzato per oscurare e filtrare le proprietà
La classe MarqueeControlRootDesigner
fornisce l'implementazione per il designer radice. Oltre a questo progettista, che opera sul MarqueeControl
, è necessario un progettista personalizzato specificatamente associato al controllo MarqueeBorder
. Questa finestra di progettazione fornisce un comportamento personalizzato appropriato nel contesto della finestra di progettazione radice personalizzata.
In particolare, il MarqueeBorderDesigner
"ombreggerà" e filtra determinate proprietà sul controllo MarqueeBorder
, 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 un progettista di tenere traccia del valore impostato dall'utente e, facoltativamente, passare tale valore al componente in fase di progettazione.
Per questo esempio, le proprietà Visible e Enabled verranno ombreggiate dal MarqueeBorderDesigner
, che impedisce all'utente di rendere invisibile o disabilitato il controllo MarqueeBorder
durante la fase di progettazione.
I progettisti possono anche aggiungere e rimuovere proprietà. Per questo esempio, la proprietà Padding verrà rimossa in fase di progettazione, perché il controllo MarqueeBorder
imposta il padding in base alle dimensioni delle luci specificate dalla proprietà LightSize
.
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 metodi
PreFilter
Modifica solo gli elementi esistenti nei metodi
PostFilter
Chiamare sempre l'implementazione di base prima nei metodi di
PreFilter
Chiamare sempre per ultima l'implementazione di base nei metodi
PostFilter
.
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 classe ComponentDesigner 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à
Fare clic con il pulsante destro del mouse sulla cartella progettazione e aggiungere una nuova classe. Dai al file di origine un nome base MarqueeBorderDesigner.
Aprire il file di origine MarqueeBorderDesigner nell'editor di codice . Nella parte superiore del file, importare i namespace 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
Modificare la dichiarazione di
MarqueeBorderDesigner
per ereditare da ParentControlDesigner.Poiché il controllo
MarqueeBorder
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
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
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 classe MarqueeControlRootDesigner
offre l'esperienza progettuale personalizzata per le istanze di MarqueeControl
. La maggior parte delle funzionalità della fase di progettazione viene ereditata dalla classe DocumentDesigner. Il codice implementerà due personalizzazioni specifiche: la gestione delle modifiche ai componenti e l'aggiunta di verbi del progettista.
Quando gli utenti progettano le istanze di MarqueeControl
, il progettista radice tiene traccia delle modifiche apportate al MarqueeControl
e ai suoi controlli figlio. L'ambiente in fase di progettazione offre un servizio pratico, IComponentChangeService, per tenere traccia delle modifiche apportate allo stato del componente.
Si ottiene un riferimento a questo servizio eseguendo una query sull'ambiente con il metodo GetService. Se la query ha esito positivo, il tuo progettista può collegare un gestore per l'evento ComponentChanged ed eseguire tutte le attività necessarie per mantenere uno stato coerente in fase di progettazione.
Nel caso della classe MarqueeControlRootDesigner
, si chiama il metodo Refresh su ogni oggetto IMarqueeWidget
contenuto nel MarqueeControl
. In questo modo, l'oggetto IMarqueeWidget
verrà ridisegnato in modo appropriato quando vengono modificate proprietà come il Size padre.
Per gestire le modifiche dei componenti
Aprire il file di origine
MarqueeControlRootDesigner
nell'editor di codice ed eseguire l'override del metodo Initialize. Chiamare l'implementazione di base di Initialize ed eseguire la query per l'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
Implementare il gestore eventi OnComponentChanged. Testare il tipo del componente di invio e, se si tratta di un
IMarqueeWidget
, chiamare il relativo metodo Refresh.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 comando "designer verb" è un'opzione di menu collegata a un gestore eventi. I verbi progettuali vengono aggiunti al menu di scelta rapida di un componente in fase di progettazione. Per altre informazioni, vedere DesignerVerb.
Verranno aggiunti due verbi designer ai tuoi progettisti: Esegui il test e Arresta il test. Questi verbi consentono di visualizzare il comportamento di runtime del MarqueeControl
in fase di progettazione. Questi verbi verranno aggiunti a MarqueeControlRootDesigner
.
Quando viene richiamato Esegui test, il gestore dell'evento verbale chiamerà il metodo StartMarquee
su MarqueeControl
. Quando il Stop Test viene richiamato su StartMarquee
e StopMarquee
chiama questi metodi sui controlli contenuti che implementano IMarqueeWidget
, in modo che tutti i controlli IMarqueeWidget
contenuti partecipino anche al test.
Per aggiungere verbi di progettazione ai designer personalizzati
Nella classe
MarqueeControlRootDesigner
aggiungere gestori eventi denominatiOnVerbRunTest
eOnVerbStopTest
.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
Collega questi gestori eventi ai verbi del progettista corrispondenti.
MarqueeControlRootDesigner
eredita un DesignerVerbCollection dalla sua classe di base. Verranno creati due nuovi oggetti DesignerVerb e aggiunti a questa raccolta nel metodo Initialize.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 la finestra Proprietà. A tale scopo, è possibile creare un UITypeEditor.
Il controllo MarqueeBorder
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 proprietà MarqueeLightShape
avrà una classe UITypeEditor associata.
Per creare un editor di tipi di interfaccia utente personalizzato
Aprire il file di origine
MarqueeBorder
nell'editor di codice .Nella definizione della classe
MarqueeBorder
dichiarare una classe denominataLightShapeEditor
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
Dichiarare una variabile di istanza di IWindowsFormsEditorService denominata
editorService
.private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
Sovrascrivere il metodo GetEditStyle. Questa implementazione restituisce DropDown, che indica all'ambiente di progettazione come visualizzare il
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
Sovrascrivere il metodo EditValue. Questa implementazione esegue una query sull'ambiente di progettazione per un oggetto IWindowsFormsEditorService. In caso di esito positivo, crea un
LightShapeSelectionControl
. Il metodo DropDownControl viene richiamato per avviare ilLightShapeEditor
. 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 di visualizzazione per il UITypeEditor personalizzato
La proprietà MarqueeLightShape
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 dal UITypeEditor per interagire con la finestra Proprietà.
Per creare un controllo di visualizzazione per l'editor dei tipi di interfaccia utente personalizzato
Aggiungere un nuovo elemento UserControl nel progetto
MarqueeControlLibrary
. Assegnare al nuovo file di origine un nome di base di LightShapeSelectionControl.Trascinare due controlli Panel dalla casella degli strumenti all'
LightShapeSelectionControl
. ChiamalisquarePanel
ecirclePanel
. Disponili affiancati. Impostare la proprietà Size di entrambi i controlli Panel su (60, 60). Impostare la proprietà Location del controllosquarePanel
su (8, 10). Impostare la proprietà Location del controllocirclePanel
su (80, 10). Impostare infine la proprietà Size delLightShapeSelectionControl
su (150, 80).Aprire il file di origine
LightShapeSelectionControl
nell'editor di codice . Nella parte superiore del file importare lo spazio dei nomi System.Windows.Forms.Design:Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
Implementare i gestori eventi Click per i controlli
squarePanel
ecirclePanel
. Questi metodi richiamano CloseDropDown per terminare la sessione di modifica UITypeEditor personalizzata.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
Dichiara una variabile di istanza di IWindowsFormsEditorService denominata
editorService
.Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
Dichiara una variabile di istanza
MarqueeLightShape
chiamatalightShapeValue
.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
Nel costruttore
LightShapeSelectionControl
, associare i gestori eventi Click agli eventi Click dei controllisquarePanel
ecirclePanel
. Definire inoltre un overload del costruttore che assegna il valoreMarqueeLightShape
dall'ambiente di progettazione al campolightShapeValue
.// 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
Nel metodo Dispose scollegare i gestori eventi Click.
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
Nel riquadro 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 metodo Dispose.
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
Sovrascrivere il 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 nel Designer
A questo punto, è possibile compilare il progetto MarqueeControlLibrary
. Testare l'implementazione creando un controllo che eredita dalla classe MarqueeControl
e utilizzandolo in un modulo.
Per creare un'implementazione personalizzata di MarqueeControl
Aprire
DemoMarqueeControl
nel Designer di Windows Forms. Viene creata un'istanza del tipo diDemoMarqueeControl
e la viene visualizzata in un'istanza del tipo diMarqueeControlRootDesigner
.Nella casella degli strumenti , aprire la scheda MarqueeControlLibrary Components. Verranno visualizzati i controlli
MarqueeBorder
eMarqueeText
disponibili per la selezione.Trascinare un'istanza del controllo
MarqueeBorder
nell'area di progettazioneDemoMarqueeControl
. Ancorare questo controlloMarqueeBorder
al controllo padre.Trascinare un'istanza del controllo
MarqueeText
nell'area di progettazioneDemoMarqueeControl
.Costruire la soluzione.
Fare clic con il pulsante destro del mouse sul
DemoMarqueeControl
e scegliere l'opzione Esegui test dal menu di scelta rapida per avviare l'animazione. Fare clic su Ferma la prova per fermare l'animazione.Apri Form1 nella visualizzazione Struttura.
Posizionare due controlli Button sul modulo. Denominali
startButton
estopButton
e modifica i valori della proprietà Text in Start e Stop, rispettivamente.Implementare Click gestori eventi per entrambi i controlli Button.
Nella casella degli strumenti , apri la scheda componenti MarqueeControlTest. Vedrai il
DemoMarqueeControl
disponibile per la selezione.Trascinare un'istanza di
DemoMarqueeControl
nell'area di progettazione Form1.Nei gestori di eventi Click, si richiamano i metodi
Start
eStop
nelDemoMarqueeControl
.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(); }
Impostare il progetto
MarqueeControlTest
come progetto di avvio ed eseguirlo. Verrà visualizzato il modulo che mostra ilDemoMarqueeControl
. Selezionare il pulsante Start per avviare l'animazione. Dovresti vedere il testo lampeggiare e le luci muoversi intorno al bordo.
Passaggi successivi
Il MarqueeControlLibrary
illustra una semplice implementazione di controlli personalizzati e designer associati. È possibile rendere questo esempio più sofisticato in diversi modi:
Modificare i valori delle proprietà per il
DemoMarqueeControl
nella finestra di progettazione. Aggiungere altri controlliMarqueBorder
e inserirli all'interno delle istanze padre per creare un effetto a cascata. Sperimenta impostazioni diverse per ilUpdatePeriod
e le proprietà legate alla luce.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à rispetto a Enabled e Visiblee è possibile aggiungere nuove proprietà. Aggiungere nuovi verbi della finestra di progettazione per semplificare attività comuni come l'ancoraggio dei controlli figlio.
Licenziare il
MarqueeControl
.Controlla come i tuoi controlli sono serializzati e come il codice è generato per essi. Per altre informazioni, vedere generazione e compilazione di codice sorgente dinamico.
Vedere anche
.NET Desktop feedback