Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Die Entwurfszeit für ein benutzerdefiniertes Steuerelement kann durch erstellen eines zugeordneten benutzerdefinierten Designers verbessert werden.
Vorsicht
Dieser Inhalt wurde für .NET Framework geschrieben. Wenn Sie .NET 6 oder eine höhere Version verwenden, verwenden Sie diesen Inhalt mit Vorsicht. Das Designersystem hat sich für Windows Forms geändert, und es ist wichtig, dass Sie die Änderungen im Designer seit dem .NET Framework Artikel überprüfen.
In diesem Artikel wird veranschaulicht, wie Sie einen benutzerdefinierten Designer für ein benutzerdefiniertes Steuerelement erstellen. Sie implementieren einen MarqueeControl
Typ und eine zugeordnete Designerklasse namens MarqueeControlRootDesigner
.
Der MarqueeControl
Typ implementiert eine Anzeige ähnlich wie ein Theaterzelt mit animierten Lichtern und blinkendem Text.
Der Designer für dieses Steuerelement interagiert mit der Entwurfsumgebung, um eine benutzerdefinierte Entwurfszeit zu ermöglichen. Mit dem benutzerdefinierten Designer können Sie eine benutzerdefinierte MarqueeControl
Implementierung mit animierten Lichtern und blinkenden Text in vielen Kombinationen zusammenstellen. Sie können das zusammengesetzte Steuerelement auf einem Formular wie jedes andere Windows Forms-Steuerelement verwenden.
Wenn Sie mit dieser exemplarischen Vorgehensweise fertig sind, sieht Ihr benutzerdefiniertes Steuerelement ungefähr wie folgt aus:
Die vollständige Codeauflistung finden Sie unter Wie erstellt man ein Windows Forms Steuerelement, das die Design-Time Features nutzt.
Voraussetzungen
Um diese Anleitung abzuschließen, benötigen Sie Visual Studio.
Erstelle das Projekt
Der erste Schritt besteht darin, das Anwendungsprojekt zu erstellen. Sie verwenden dieses Projekt, um die Anwendung zu erstellen, die das benutzerdefinierte Steuerelement hosten soll.
Erstellen Sie in Visual Studio ein neues Windows Forms-Anwendungsprojekt, und nennen Sie es MarqueeControlTest.
Erstellen des Projekts für die Steuerelementbibliothek
Fügen Sie der Lösungsdatei ein Windows Forms-Steuerelementbibliothek-Projekt hinzu. Nennen Sie das Projekt MarqueeControlLibrary.
Löschen Sie mithilfe des Projektmappen-Explorers das Standardsteuerelement des Projekts, indem Sie die Quelldatei mit dem Namen "UserControl1.cs" oder "UserControl1.vb" je nach Ihrer Wahl löschen.
Fügen Sie dem UserControl Projekt ein neues
MarqueeControlLibrary
Element hinzu. Geben Sie der neuen Quelldatei einen Basisnamen von MarqueeControl.Erstellen Sie mit dem Projektmappen-Explorer einen neuen Ordner im
MarqueeControlLibrary
Projekt.Klicken Sie mit der rechten Maustaste auf den Entwurfsordner , und fügen Sie eine neue Klasse hinzu. Nennen Sie ihn MarqueeControlRootDesigner.
Sie müssen Typen aus der System.Design-Assembly verwenden. Fügen Sie also diesen Verweis auf das
MarqueeControlLibrary
Projekt hinzu.
Verweisen auf das Projekt "Benutzerdefiniertes Steuerelement"
Sie verwenden das MarqueeControlTest
Projekt, um das benutzerdefinierte Steuerelement zu testen. Das Testprojekt wird das benutzerdefinierte Steuerelement kennen, wenn Sie der Assembly einen Projektverweis MarqueeControlLibrary
hinzufügen.
Fügen Sie im MarqueeControlTest
Projekt einen Projektverweis zur MarqueeControlLibrary
Assembly hinzu. Achten Sie darauf, die Registerkarte "Projekte " im Dialogfeld " Verweis hinzufügen " zu verwenden, anstatt direkt auf die MarqueeControlLibrary
Assembly zu verweisen.
Definieren eines benutzerdefinierten Steuerelements und des benutzerdefinierten Designers
Ihr benutzerdefiniertes Steuerelement wird von der UserControl Klasse abgeleitet. Auf diese Weise kann Ihr Steuerelement andere Steuerelemente enthalten und bietet Ihrem Steuerelement eine Vielzahl von Standardfunktionen.
Ihr benutzerdefiniertes Steuerelement verfügt über einen zugeordneten benutzerdefinierten Designer. Auf diese Weise können Sie eine einzigartige Designoberfläche erstellen, die speziell auf Ihr benutzerdefiniertes Steuerelement zugeschnitten ist.
Sie ordnen das Steuerelement dem Designer mithilfe der DesignerAttribute Klasse zu. Da Sie das gesamte Entwurfszeitverhalten Ihres benutzerdefinierten Steuerelements entwickeln, implementiert der benutzerdefinierte Designer die IRootDesigner Schnittstelle.
So definieren Sie ein benutzerdefiniertes Steuerelement und seinen benutzerdefinierten Designer
Öffnen Sie die
MarqueeControl
Quelldatei im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces: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
Fügen Sie der DesignerAttribute-Klassendeklaration die
MarqueeControl
hinzu. Dadurch wird das benutzerdefinierte Steuerelement dem Designer zugeordnet.[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : UserControl {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControl
Öffnen Sie die
MarqueeControlRootDesigner
Quelldatei im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces: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
Ändern Sie die Deklaration von
MarqueeControlRootDesigner
, sodass sie von der DocumentDesigner Klasse erbt. Wenden Sie die ToolboxItemFilterAttribute an, um die Designerinteraktion mit der Toolbox anzugeben.Hinweis
Die Definition für die
MarqueeControlRootDesigner
Klasse wurde in einen Namespace mit dem Namen MarqueeControlLibrary.Design eingeschlossen. Diese Deklaration platziert den Designer in einem speziellen Namespace, der für entwurfsbezogene Typen reserviert ist.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
Definieren Sie den Konstruktor für die
MarqueeControlRootDesigner
Klasse. Fügen Sie eine WriteLine Anweisung im Konstruktortext ein. Dies ist nützlich für das Debuggen.public MarqueeControlRootDesigner() { Trace.WriteLine("MarqueeControlRootDesigner ctor"); }
Public Sub New() Trace.WriteLine("MarqueeControlRootDesigner ctor") End Sub
Erstellen einer Instanz Ihres benutzerdefinierten Steuerelements
Fügen Sie dem UserControl Projekt ein neues
MarqueeControlTest
Element hinzu. Geben Sie der neuen Quelldatei einen Basisnamen von DemoMarqueeControl.Öffnen Sie die
DemoMarqueeControl
Datei im Code-Editor. Importieren Sie denMarqueeControlLibrary
Namespace zu Beginn der Datei.Imports MarqueeControlLibrary
using MarqueeControlLibrary;
Ändern Sie die Deklaration von
DemoMarqueeControl
, sodass sie von derMarqueeControl
Klasse erbt.Projekt erstellen.
Öffnen Sie Formular1 im Windows Forms-Designer.
Suchen Sie die Registerkarte "MarqueeControlTest-Komponenten " in der Toolbox , und öffnen Sie sie. Ziehen Sie
DemoMarqueeControl
aus der Toolbox auf Ihr Formular.Projekt erstellen.
Einrichten des Projekts für Design-Time Debuggen
Wenn Sie ein benutzerdefiniertes Entwurfserlebnis entwickeln, müssen Sie Ihre Steuerelemente und Komponenten debuggen. Es gibt eine einfache Möglichkeit, Ihr Projekt einzurichten, um das Debuggen während der Entwurfsphase zu ermöglichen. Weitere Informationen finden Sie unter Exemplarische Vorgehensweise: Debuggen von benutzerdefinierten Windows Forms-Steuerelementen zur Entwurfszeit.
Klicken Sie mit der rechten Maustaste auf das
MarqueeControlLibrary
Projekt, und wählen Sie "Eigenschaften" aus.Wählen Sie im Dialogfeld "MarqueeControlLibrary-Eigenschaftenseiten " die Seite "Debuggen " aus.
Wählen Sie im Abschnitt "Startaktion " die Option "Externes Programm starten" aus. Sie debuggen eine separate Instanz von Visual Studio, also klicken Sie auf die Schaltfläche mit Auslassungspunkten (
), um nach der Visual Studio-IDE zu suchen. Der Name der ausführbaren Datei ist devenv.exe, und wenn Sie den Standardspeicherort installiert haben, ist der Pfad %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.
Wählen Sie OK aus, um das Dialogfeld zu schließen.
Klicken Sie mit der rechten Maustaste auf das MarqueeControlLibrary-Projekt, und wählen Sie "Als Startprojekt festlegen " aus, um diese Debugkonfiguration zu aktivieren.
Kontrollpunkt
Jetzt können Sie das Entwurfszeitverhalten Ihres benutzerdefinierten Steuerelements debuggen. Nachdem Sie festgestellt haben, dass die Debugumgebung ordnungsgemäß eingerichtet ist, testen Sie die Zuordnung zwischen dem benutzerdefinierten Steuerelement und dem benutzerdefinierten Designer.
Um die Debugumgebung und die Zuordnung des Designers zu testen
Öffnen Sie die MarqueeControlRootDesigner-Quelldatei im Code-Editor , und platzieren Sie einen Haltepunkt für die WriteLine Anweisung.
Drücken Sie F5-, um die Debugsitzung zu starten.
Es wird eine neue Instanz von Visual Studio erstellt.
Öffnen Sie in der neuen Instanz von Visual Studio die MarqueeControlTest-Lösung. Sie können die Projektmappe ganz einfach finden, indem Sie im Menü Datei die Option Zuletzt verwendete Projekte auswählen. Die MarqueeControlTest.sln Lösungsdatei wird als zuletzt verwendete Datei aufgeführt.
Öffnen Sie den
DemoMarqueeControl
Designer.Die Debuginstanz von Visual Studio erhält den Fokus, und die Ausführung stoppt an Ihrem Haltepunkt. Drücken Sie F5 , um die Debugsitzung fortzusetzen.
An diesem Punkt ist alles für Sie vorhanden, um Ihr benutzerdefiniertes Steuerelement und den zugehörigen benutzerdefinierten Designer zu entwickeln und zu debuggen. Der rest dieses Artikels konzentriert sich auf die Details der Implementierungsfeatures des Steuerelements und des Designers.
Implementieren des benutzerdefinierten Steuerelements
Das MarqueeControl
ist ein UserControl mit ein wenig Anpassung. Sie macht zwei Methoden verfügbar: Start
, die die Animation des Laufrahmens startet und Stop
die Animation anhält. Da die MarqueeControl
untergeordnete Steuerelemente enthalten, die die IMarqueeWidget
-Schnittstelle implementieren, zählen Start
und Stop
jedes untergeordnete Steuerelement auf und rufen die StartMarquee
- und StopMarquee
-Methoden für jedes untergeordnete Steuerelement auf, das IMarqueeWidget
implementiert.
Das Erscheinungsbild der Steuerelemente MarqueeBorder
und MarqueeText
hängt vom Layout ab. Daher überschreibt MarqueeControl
die OnLayout-Methode und ruft PerformLayout bei untergeordneten Steuerelementen dieses Typs auf.
Dies ist der Umfang der MarqueeControl
Anpassungen. Die Laufzeitfeatures werden durch die Steuerelemente MarqueeBorder
und MarqueeText
implementiert, und die Entwurfszeitfeatures werden durch die Klassen MarqueeBorderDesigner
und MarqueeControlRootDesigner
implementiert.
So implementieren Sie Ihr benutzerdefiniertes Steuerelement
Öffnen Sie die
MarqueeControl
Quelldatei im Code-Editor. Implementieren Sie die MethodenStart
undStop
.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
Überschreiben Sie die OnLayout-Methode.
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
Erstellen eines untergeordneten Steuerelements für Ihr benutzerdefiniertes Steuerelement
Das MarqueeControl
Steuerelement hostet zwei Arten von untergeordneten Steuerelementen: das MarqueeBorder
Steuerelement und das MarqueeText
Steuerelement.
MarqueeBorder
: Dieses Steuerelement zeichnet einen Rahmen von "Lichtern" an seinen Rändern. Die Lichter blinken nacheinander, sodass sie sich um den Rand bewegen. Die Geschwindigkeit, mit der die Lichter blinken, wird durch eine Eigenschaft gesteuert, die genannt wirdUpdatePeriod
. Mehrere andere benutzerdefinierte Eigenschaften bestimmen andere Aspekte der Darstellung des Steuerelements. Zwei Methoden, namensStartMarquee
undStopMarquee
, steuern, wann die Animation beginnt und endet.MarqueeText
: Dieses Steuerelement zeichnet eine blinkende Zeichenfolge. Wie dasMarqueeBorder
Steuerelement wird die Geschwindigkeit, mit der der Text blinkt, durch dieUpdatePeriod
Eigenschaft gesteuert. DasMarqueeText
Steuerelement hat auch dieStartMarquee
Methoden undStopMarquee
Methoden, die mit demMarqueeBorder
Steuerelement gemeinsam sind.
Zur Entwurfszeit können diese beiden Steuerelementtypen in beliebigen Kombinationen einem MarqueeControlRootDesigner
hinzugefügt werden.
Gemeinsame Funktionen der beiden Steuerelemente sind in eine Schnittstelle namens IMarqueeWidget
einbezogen. Dies ermöglicht es, MarqueeControl
alle Marquee-bezogenen Kind-Steuerelemente zu entdecken und ihnen eine besondere Behandlung zu geben.
Um die periodische Animationsfunktion zu implementieren, verwenden Sie BackgroundWorker Objekte aus dem System.ComponentModel Namespace. Sie können Timer Objekte verwenden, aber wenn viele IMarqueeWidget
Objekte vorhanden sind, kann der einzelne UI-Thread möglicherweise nicht mit der Animation schritthalten.
So erstellen Sie ein untergeordnetes Steuerelement für Ihr benutzerdefiniertes Steuerelement
Fügen Sie dem
MarqueeControlLibrary
Projekt ein neues Klassenelement hinzu. Geben Sie der neuen Quelldatei den Basisnamen "IMarqueeWidget".Öffnen Sie die
IMarqueeWidget
Quelldatei im Code-Editor und ändern Sie die Deklaration vonclass
zuinterface
.// 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
Fügen Sie der
IMarqueeWidget
Schnittstelle den folgenden Code hinzu, um zwei Methoden und eine Eigenschaft bereitzustellen, die die Marquee-Animation manipulieren.// 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
Fügen Sie dem Projekt ein neues
MarqueeControlLibrary
hinzu. Geben Sie der neuen Quelldatei den Basisnamen "MarqueeText".Ziehen Sie eine BackgroundWorker Komponente aus der Toolbox auf Ihr
MarqueeText
Steuerelement. Diese Komponente ermöglicht es demMarqueeText
Steuerelement, sich asynchron zu aktualisieren.Legen Sie im Eigenschaftenfenster die Eigenschaften und BackgroundWorker Eigenschaften der
WorkerReportsProgress
Komponente WorkerSupportsCancellation auf "true" fest. Diese Einstellungen ermöglichen es der BackgroundWorker Komponente, das ProgressChanged Ereignis regelmäßig auszuheben und asynchrone Updates abzubrechen.Weitere Informationen finden Sie unter BackgroundWorker Component.
Öffnen Sie die
MarqueeText
Quelldatei im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces: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
Ändern Sie die Deklaration von
MarqueeText
, um von Label zu erben und dieIMarqueeWidget
Schnittstelle zu implementieren.[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {
<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
Deklarieren Sie die Instanzvariablen, die den verfügbar gemachten Eigenschaften entsprechen, und initialisieren Sie sie im Konstruktor. Das
isLit
Feld bestimmt, ob der Text in der von derLightColor
Eigenschaft angegebenen Farbe gezeichnet werden soll.// 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
Implementieren Sie die
IMarqueeWidget
-Schnittstelle.Die
StartMarquee
Methoden undStopMarquee
Methoden rufen die BackgroundWorker Komponenten RunWorkerAsync und CancelAsync Methoden auf, um die Animation zu starten und zu beenden.Die Category Eigenschaften und Browsable Attribute werden auf die
UpdatePeriod
Eigenschaft angewendet, sodass sie in einem benutzerdefinierten Abschnitt des Eigenschaftenfensters mit dem Namen "Marquee" angezeigt wird.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
Implementieren Sie die Eigenschaftszugriffe. Sie stellen zwei Eigenschaften für Clients zur Verfügung:
LightColor
undDarkColor
. Die Category Eigenschaften und Browsable Attribute werden auf diese Eigenschaften angewendet, sodass die Eigenschaften in einem benutzerdefinierten Abschnitt des Eigenschaftenfensters namens "Marquee" angezeigt werden.[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
Implementieren Sie die Handler für die BackgroundWorker-Komponente und deren DoWork- und ProgressChanged-Ereignisse.
Der DoWork-Ereignishandler wird für die Anzahl von Millisekunden in den Schlaf versetzt, die durch
UpdatePeriod
angegeben wird, und löst dann das ProgressChanged-Ereignis aus, bis Ihr Code die Animation stoppt, indem er CancelAsync aufruft.Der ProgressChanged Ereignishandler schaltet den Text zwischen seinem hellen und dunklen Zustand um, um den Eindruck von Blinken zu erzeugen.
// 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
Überschreiben Sie die OnPaint Methode, um die Animation zu aktivieren.
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
Drücken Sie F6 , um die Lösung zu erstellen.
Erstellen des untergeordneten Steuerelements "MarqueeBorder"
Das MarqueeBorder
Steuerelement ist etwas komplexer als das MarqueeText
Steuerelement. Es verfügt über mehr Eigenschaften, und die Animation in der OnPaint Methode ist stärker beteiligt. Grundsätzlich ist es dem Steuerelement ziemlich ähnlich MarqueeText
.
Da das MarqueeBorder
Steuerelement über untergeordnete Steuerelemente verfügen kann, muss es sich um Layout Ereignisse bewusst sein.
So erstellen Sie das MarqueeBorder-Steuerelement
Fügen Sie dem Projekt ein neues
MarqueeControlLibrary
hinzu. Geben Sie der neuen Quelldatei den Basisnamen "MarqueeBorder".Ziehen Sie eine BackgroundWorker Komponente aus der Toolbox auf Ihr
MarqueeBorder
Steuerelement. Diese Komponente ermöglicht es demMarqueeBorder
Steuerelement, sich asynchron zu aktualisieren.Legen Sie im Eigenschaftenfenster die Eigenschaften und BackgroundWorker Eigenschaften der
WorkerReportsProgress
Komponente WorkerSupportsCancellation auf "true" fest. Diese Einstellungen ermöglichen es der BackgroundWorker Komponente, das ProgressChanged Ereignis regelmäßig auszuheben und asynchrone Updates abzubrechen. Weitere Informationen finden Sie unter BackgroundWorker Component.Wählen Sie im Eigenschaftenfenster die Schaltfläche "Ereignisse " aus. Fügen Sie Handler für das DoWork- und das ProgressChanged-Ereignis an.
Öffnen Sie die
MarqueeBorder
Quelldatei im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces: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
Ändern Sie die Deklaration von
MarqueeBorder
, sodass sie von Panel erbt und dieIMarqueeWidget
Schnittstelle implementiert.[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
Deklarieren Sie zwei Enumerationen zum Verwalten des Zustands des
MarqueeBorder
Steuerelements:MarqueeSpinDirection
, wodurch die Richtung bestimmt wird, in der die Lichter um den Rahmen "drehen" undMarqueeLightShape
welche die Form der Lichter (quadratisch oder kreisförmig) bestimmt. Platzieren Sie diese Deklarationen vor derMarqueeBorder
Klassendeklaration.// 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
Deklarieren Sie die Instanzvariablen, die den verfügbar gemachten Eigenschaften entsprechen, und initialisieren Sie sie im Konstruktor.
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
Implementieren Sie die
IMarqueeWidget
-Schnittstelle.Die
StartMarquee
Methoden undStopMarquee
Methoden rufen die BackgroundWorker Komponenten RunWorkerAsync und CancelAsync Methoden auf, um die Animation zu starten und zu beenden.Da das
MarqueeBorder
Steuerelement untergeordnete Steuerelemente enthalten kann, enumeriert dieStartMarquee
Methode alle untergeordneten Steuerelemente und ruftStartMarquee
auf diejenigen auf, dieIMarqueeWidget
implementieren. DieStopMarquee
Methode hat eine ähnliche Implementierung.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
Implementieren Sie die Eigenschaftszugriffe. Das
MarqueeBorder
Steuerelement verfügt über mehrere Eigenschaften zum Steuern der Darstellung.[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
Implementieren Sie die Handler für die BackgroundWorker-Komponente und deren DoWork- und ProgressChanged-Ereignisse.
Der DoWork-Ereignishandler wird für die Anzahl von Millisekunden in den Schlaf versetzt, die durch
UpdatePeriod
angegeben wird, und löst dann das ProgressChanged-Ereignis aus, bis Ihr Code die Animation stoppt, indem er CancelAsync aufruft.Der ProgressChanged Ereignishandler erhöht die Position des "Basislichts", aus dem der Hell-/Dunkelzustand der anderen Lichter bestimmt wird, und ruft die Refresh Methode auf, die dazu führt, dass das Steuerelement sich selbst neu zeichnet.
// 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
Implementieren Sie die Hilfsmethoden
IsLit
undDrawLight
.Die
IsLit
Methode bestimmt die Farbe eines Lichts an einer bestimmten Position. Lichter, die "beleuchtet" sind, werden in der von derLightColor
Eigenschaft angegebenen Farbe gezeichnet, und diejenigen, die "dunkel" sind, werden in der Farbe gezeichnet, die von derDarkColor
Eigenschaft angegeben wird.Die
DrawLight
Methode rendert ein Licht mit der passenden Farbe, Form und Position.// 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
Überschreiben Sie die OnLayout und OnPaint Methoden.
Die OnPaint Methode zeichnet die Lichter entlang der Ränder des
MarqueeBorder
Steuerelements.Da die OnPaint Methode von den Abmessungen des
MarqueeBorder
Steuerelements abhängt, müssen Sie sie immer dann aufrufen, wenn sich das Layout ändert. Um dies zu erreichen, überschreiben Sie OnLayout und rufen Sie Refresh auf.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
Erstellen eines benutzerdefinierten Designers für Schatten- und Filtereigenschaften
Die MarqueeControlRootDesigner
Klasse stellt die Implementierung für den Stamm-Designer bereit. Zusätzlich zu diesem Designer, der auf dem MarqueeControl
Steuerelement arbeitet, benötigen Sie einen benutzerdefinierten Designer, der speziell dem MarqueeBorder
Steuerelement zugeordnet ist. Dieser Designer bietet benutzerdefiniertes Verhalten, das im Kontext des benutzerdefinierten Stamm-Designers geeignet ist.
Insbesondere werden bestimmte Eigenschaften des MarqueeBorderDesigner
Steuerelements "geschattet" und gefiltert, wodurch ihre Interaktion mit der Entwurfsumgebung verändert wird.
Das Abfangen von Aufrufen des Eigenschaftsaccessors einer Komponente wird als "Überschattung" bezeichnet. Es ermöglicht einem Entwickler, den vom Benutzer festgelegten Wert nachzuverfolgen und gegebenenfalls an die zu entwerfende Komponente weiterzugeben.
In diesem Beispiel werden die Visible- und Enabled-Eigenschaften durch das MarqueeBorderDesigner
-Element abgeschattet, wodurch verhindert wird, dass das MarqueeBorder
-Steuerelement während der Entwurfsphase unsichtbar oder deaktiviert wird.
Designer können auch Eigenschaften hinzufügen und entfernen. In diesem Beispiel wird die Padding Eigenschaft zur Entwurfszeit entfernt, da das MarqueeBorder
Steuerelement den Abstand programmgesteuert basierend auf der Größe der von der LightSize
Eigenschaft angegebenen Lichter festlegt.
Die Basisklasse für MarqueeBorderDesigner
ist ComponentDesigner, die über Methoden verfügt, mit denen die Attribute, Eigenschaften und Ereignisse geändert werden können, die von einem Steuerelement zur Entwurfszeit bereitgestellt werden:
Wenn Sie die öffentliche Schnittstelle einer Komponente mithilfe dieser Methoden ändern, befolgen Sie die folgenden Regeln:
Hinzufügen oder Entfernen von Elementen nur in den
PreFilter
MethodenÄndern Sie nur vorhandene Elemente in den
PostFilter
MethodenRufen Sie die Basisimplementierung immer zuerst in den Methoden auf.
PreFilter
Rufen Sie die Basisimplementierung immer zuletzt in den Methoden auf.
PostFilter
Durch die Einhaltung dieser Regeln wird sichergestellt, dass alle Designer in der Entwurfszeitumgebung eine einheitliche Ansicht aller zu entwerfenden Komponenten haben.
Die ComponentDesigner Klasse stellt ein Wörterbuch zum Verwalten der Werte von schattierten Eigenschaften bereit, wodurch Sie von der Notwendigkeit befreit werden, bestimmte Instanzvariablen zu erstellen.
So erstellen Sie einen benutzerdefinierten Designer zum Schatten und Filtern von Eigenschaften
Klicken Sie mit der rechten Maustaste auf den Entwurfsordner , und fügen Sie eine neue Klasse hinzu. Geben Sie der Quelldatei einen Basisnamen von MarqueeBorderDesigner.
Öffnen Sie die Quelldatei "MarqueeBorderDesigner" im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces:
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
Ändern Sie die Deklaration von
MarqueeBorderDesigner
, sodass sie von ParentControlDesigner erbt.Da das
MarqueeBorder
Steuerelement untergeordnete Steuerelemente enthalten kann, erbtMarqueeBorderDesigner
von ParentControlDesigner, der die Interaktion zwischen übergeordneten und untergeordneten Elementen behandelt.namespace MarqueeControlLibrary.Design { public class MarqueeBorderDesigner : ParentControlDesigner {
Namespace MarqueeControlLibrary.Design Public Class MarqueeBorderDesigner Inherits ParentControlDesigner
Überschreiben Sie die Basisimplementierung von 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
Implementieren Sie die Enabled- und Visible-Eigenschaften. Diese Implementierungen schatten die Eigenschaften des Steuerelements.
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
Behandeln von Komponentenänderungen
Die MarqueeControlRootDesigner
Klasse bietet die maßgeschneiderte Entwurfserfahrung für Ihre MarqueeControl
Instanzen. Die meisten Entwurfszeitfunktionen werden von der DocumentDesigner Klasse geerbt. Ihr Code implementiert zwei spezifische Anpassungen: Behandeln von Komponentenänderungen und Hinzufügen von Designerverben.
Wenn Benutzer ihre MarqueeControl
Instanzen entwerfen, verfolgt Ihr Stamm-Designer Änderungen an MarqueeControl
und seinen untergeordneten Steuerelementen. Die Entwurfszeitumgebung bietet einen bequemen Dienst, IComponentChangeServiceum Änderungen am Komponentenzustand nachzuverfolgen.
Sie erhalten einen Verweis auf diesen Dienst, indem Sie die Umgebung mit der GetService Methode abfragen. Wenn die Abfrage erfolgreich ist, kann ihr Designer einen Handler für das ComponentChanged Ereignis anfügen und alle Aufgaben ausführen, die erforderlich sind, um einen konsistenten Zustand zur Entwurfszeit beizubehalten.
Im Fall der MarqueeControlRootDesigner
Klasse rufen Sie die Refresh Methode für jedes IMarqueeWidget
Objekt auf, das in der MarqueeControl
Klasse enthalten ist. Dadurch wird das IMarqueeWidget
-Objekt entsprechend neu gezeichnet, wenn die Eigenschaften des übergeordneten Size-Objekts geändert werden.
So behandeln Sie Komponentenänderungen
Öffnen Sie die
MarqueeControlRootDesigner
Quelldatei im Code-Editor , und überschreiben Sie die Initialize Methode. Rufen Sie die Basisimplementierung von Initialize auf und fragen Sie nach der 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
Implementieren Sie den OnComponentChanged Ereignishandler. Testen Sie den Typ der sendenden Komponente, und wenn es sich um eine
IMarqueeWidget
handelt, rufen Sie deren Refresh-Methode auf.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
Hinzufügen von Designerverben zu Ihrem benutzerdefinierten Designer
Ein Designerverb ist ein Menübefehl, der mit einem Ereignishandler verknüpft ist. Designerverben werden dem Kontextmenü einer Komponente während der Gestaltung hinzugefügt. Weitere Informationen finden Sie unter DesignerVerb.
Sie fügen Ihren Designern zwei Designer-Verben hinzu: Test ausführen und Test beenden. Mit diesen Verben können Sie das Laufzeitverhalten der MarqueeControl
zur Entwurfszeit anzeigen. Diese Verben werden zu MarqueeControlRootDesigner
hinzugefügt.
Wenn "Test ausführen" aufgerufen wird, ruft der Verb-Ereignishandler die StartMarquee
Methode für die MarqueeControl
. Wenn "Stop Test" aufgerufen wird, ruft der Verb-Ereignishandler die StopMarquee
Methode für die MarqueeControl
. Die Implementierung der StartMarquee
und StopMarquee
-Methoden ruft diese auf jenen enthaltenen Steuerelementen auf, die IMarqueeWidget
implementieren, sodass alle enthaltenen IMarqueeWidget
-Steuerelemente ebenfalls am Test teilnehmen.
So fügen Sie Ihren benutzerdefinierten Designern Designerverben hinzu
Fügen Sie in der
MarqueeControlRootDesigner
Klasse Ereignishandler namensOnVerbRunTest
undOnVerbStopTest
.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
Verbinden Sie diese Ereignishandler mit ihren entsprechenden Designerverben.
MarqueeControlRootDesigner
erbt DesignerVerbCollection von der Basisklasse. Sie erstellen zwei neue DesignerVerb Objekte und fügen sie dieser Auflistung in der Initialize Methode hinzu.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)))
Benutzerdefinierten UITypeEditor erstellen
Beim Erstellen einer benutzerdefinierten Entwurfszeiterfahrung für Benutzer ist es häufig wünschenswert, eine benutzerdefinierte Interaktion mit dem Eigenschaftenfenster zu schaffen. Sie können dies erreichen, indem Sie ein Element UITypeEditor erstellen.
Das MarqueeBorder
Steuerelement macht mehrere Eigenschaften im Eigenschaftenfenster verfügbar. Zwei dieser Eigenschaften MarqueeSpinDirection
und MarqueeLightShape
werden durch Enumerationen dargestellt. Zur Veranschaulichung der Verwendung eines UI-Typ-Editors verfügt die MarqueeLightShape
Eigenschaft über eine zugeordnete UITypeEditor Klasse.
So erstellen Sie einen benutzerdefinierten Benutzeroberflächentyp-Editor
Öffnen Sie die
MarqueeBorder
Quelldatei im Code-Editor.Deklarieren Sie in der Definition der
MarqueeBorder
-Klasse eine Klasse namensLightShapeEditor
, die von UITypeEditor abgeleitet ist.// 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
Deklarieren Sie eine Instanzvariable IWindowsFormsEditorService namens
editorService
.private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
Überschreiben Sie die GetEditStyle-Methode. Diese Implementierung gibt DropDown zurück, was der Entwurfsumgebung angibt, wie die
LightShapeEditor
angezeigt wird.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
Überschreiben Sie die EditValue-Methode. Diese Implementierung fragt die Entwurfsumgebung für ein IWindowsFormsEditorService Objekt ab. Falls es gelingt, erzeugt es ein
LightShapeSelectionControl
. Die DropDownControl Methode wird aufgerufen, um dieLightShapeEditor
zu starten. Der Rückgabewert aus diesem Aufruf wird an die Entwurfsumgebung zurückgegeben.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
Erstellen eines Ansichtssteuerelements für den benutzerdefinierten UITypeEditor
Die MarqueeLightShape
Eigenschaft unterstützt zwei Arten von Lichtformen: Square
und Circle
. Sie erstellen ein benutzerdefiniertes Steuerelement, das ausschließlich zum Zweck der grafischen Anzeige dieser Werte im Eigenschaftenfenster verwendet wird. Dieses benutzerdefinierte Steuerelement wird von Ihrem UITypeEditor verwendet, um mit dem Eigenschaftenfenster zu interagieren.
So erstellen Sie ein Ansichtssteuerelement für den benutzerdefinierten Benutzeroberflächentyp-Editor
Fügen Sie dem UserControl Projekt ein neues
MarqueeControlLibrary
Element hinzu. Geben Sie der neuen Quelldatei einen Basisnamen von LightShapeSelectionControl.Ziehen Sie zwei Panel Steuerelemente aus der Toolbox auf die
LightShapeSelectionControl
. Benennen Sie siesquarePanel
undcirclePanel
. Ordnen Sie sie nebeneinander an. Legen Sie die Size Eigenschaft beider Panel Steuerelemente auf (60, 60) fest. Legen Sie die Location Eigenschaft dessquarePanel
Steuerelements auf (8, 10) fest. Legen Sie die Location Eigenschaft descirclePanel
Steuerelements auf (80, 10) fest. Legen Sie schließlich die Size Eigenschaft des WertsLightShapeSelectionControl
auf (150, 80) fest.Öffnen Sie die
LightShapeSelectionControl
Quelldatei im Code-Editor. Importieren Sie den System.Windows.Forms.Design Namespace zu Beginn der Datei.Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
Implementieren Sie Click Ereignishandler für die
squarePanel
undcirclePanel
Steuerelemente. Diese Methoden werden CloseDropDown aufgerufen, um die benutzerdefinierte UITypeEditor Bearbeitungssitzung zu beenden.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
Deklarieren Sie eine Instanzvariable IWindowsFormsEditorService namens
editorService
.Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
Deklarieren sie eine
MarqueeLightShape
Instanzvariable namenslightShapeValue
.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
Fügen Sie im
LightShapeSelectionControl
Konstruktor die Click Ereignishandler an diesquarePanel
undcirclePanel
Steuerelemente für ihre Click Ereignisse an. Definieren Sie außerdem eine Konstruktorüberladung, die den Wert aus der Entwurfsumgebung demMarqueeLightShape
-Feld zuweist.// 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
Trennen Sie in der Dispose Methode die Click Ereignishandler.
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
Klicken Sie im Projektmappen-Explorer auf die Schaltfläche "Alle Dateien anzeigen ". Öffnen Sie die datei LightShapeSelectionControl.Designer.cs oder LightShapeSelectionControl.Designer.vb, und entfernen Sie die Standarddefinition der Dispose Methode.
Implementieren Sie die
LightShape
Eigenschaft.// 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
Überschreiben Sie die OnPaint-Methode. Diese Implementierung zeichnet ein gefülltes Quadrat und einen Kreis. Außerdem wird der ausgewählte Wert hervorgehoben, indem ein Rahmen um die eine oder andere Form gezeichnet wird.
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
Testen Sie Ihr benutzerdefiniertes Steuerelement im Designer
Zu diesem Zeitpunkt können Sie das MarqueeControlLibrary
Projekt erstellen. Testen Sie Ihre Implementierung, indem Sie ein Steuerelement erstellen, das von der MarqueeControl
Klasse erbt und es in einem Formular verwendet.
So erstellen Sie eine benutzerdefinierte MarqueeControl-Implementierung
Öffnen Sie
DemoMarqueeControl
im Windows Forms-Designer. Dadurch wird eine Instanz desDemoMarqueeControl
Typs erstellt und in einer Instanz desMarqueeControlRootDesigner
Typs angezeigt.Öffnen Sie in der Toolbox die Registerkarte "MarqueeControlLibrary Components". Sie sehen die Steuerelemente
MarqueeBorder
undMarqueeText
, die zur Auswahl zur Verfügung stehen.Ziehen Sie eine Instanz des
MarqueeBorder
Steuerelements auf dieDemoMarqueeControl
Entwurfsoberfläche. Docken Sie diesesMarqueeBorder
Steuerelement an das übergeordnete Steuerelement an.Ziehen Sie eine Instanz des
MarqueeText
Steuerelements auf dieDemoMarqueeControl
Entwurfsoberfläche.Erstellen Sie die Lösung.
Klicken Sie mit der rechten Maustaste auf
DemoMarqueeControl
, und wählen Sie im angezeigten Kontextmenü die Option "Test ausführen" aus, um die Animation zu starten. Klicken Sie auf "Test beenden ", um die Animation zu beenden.Öffnen Sie Formular1 in der Entwurfsansicht.
Platzieren Sie zwei Button Steuerelemente im Formular. Benennen Sie sie
startButton
undstopButton
, und ändern Sie die Text-Eigenschaftswerte in 'Start' bzw. 'Stop'.Implementieren Sie Click Ereignishandler für beide Button Steuerelemente.
Öffnen Sie in der Toolbox die Registerkarte MarqueeControlTest-Komponenten. Das
DemoMarqueeControl
ist zur Auswahl verfügbar.Ziehen Sie eine Instanz von
DemoMarqueeControl
auf die Entwurfsoberfläche von Form1.In den Click Ereignishandlern rufen Sie die
Start
undStop
Methoden auf demDemoMarqueeControl
auf.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(); }
Legen Sie das
MarqueeControlTest
Projekt als Startprojekt fest, und führen Sie es aus. Das Formular wird angezeigt, in dem IhrDemoMarqueeControl
Formular angezeigt wird. Wählen Sie die Schaltfläche "Start " aus, um die Animation zu starten. Sie sollten den Text blinken sehen und die Lichter, die sich um den Rand bewegen.
Nächste Schritte
Dies MarqueeControlLibrary
veranschaulicht eine einfache Implementierung von benutzerdefinierten Steuerelementen und zugeordneten Designern. Sie können dieses Beispiel auf verschiedene Arten komplexer gestalten:
Ändern Sie die Eigenschaften des
DemoMarqueeControl
im Designer. Fügen Sie weitereMarqueBorder
Steuerelemente hinzu und docken Sie sie in ihre übergeordneten Instanzen an, um einen geschachtelten Effekt zu erstellen. Experimentieren Sie mit unterschiedlichen Einstellungen für dieUpdatePeriod
und die lichtbezogenen Eigenschaften.Erstellen Sie Ihre eigenen Implementierungen von
IMarqueeWidget
. Sie könnten z. B. ein blinkendes "Neonzeichen" oder ein animiertes Zeichen mit mehreren Bildern erstellen.Passen Sie die Entwurfszeit weiter an. Sie könnten versuchen, mehr Eigenschaften zu schattieren als Enabled und Visible, und Sie könnten neue Eigenschaften hinzufügen. Fügen Sie neue Designerverben hinzu, um allgemeine Aufgaben wie das Andocken untergeordneter Steuerelemente zu vereinfachen.
Lizenz für .
MarqueeControl
Steuern Sie, wie Ihre Steuerelemente serialisiert werden und wie Code für sie generiert wird. Weitere Informationen finden Sie unter Dynamische Quellcodegenerierung und Kompilierung.
Siehe auch
.NET Desktop feedback