Exemplarische Vorgehensweise: Erstellen eines Steuerelements, das Entwurfszeitfeatures nutzt
Die Entwurfszeitfunktionalität für ein benutzerdefiniertes Steuerelement kann durch die Erstellung eines zugehörigen benutzerdefinierten Designers erweitert werden.
Achtung
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 Designeränderungen seit .NET Framework -Artikel überprüfen.
Dieser Artikel 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 einer Lichtwerbung an der Außenfassade eines Kinos, mit animierten Lichtern und blinkendem Text.
Der Designer für dieses Steuerelement interagiert mit der Entwurfsumgebung, um eine benutzerdefinierte Entwurfszeitfunktionalität bereitzustellen. Mit dem benutzerdefinierten Designer können Sie eine benutzerdefinierte MarqueeControl
-Implementierung mit animierten Lichtern und blinkendem Text in vielen Kombinationen zusammenstellen. Sie können das erstellte Steuerelement anschließend wie jedes andere Windows Forms-Steuerelement in einem Formular verwenden.
Nach Abschluss dieser exemplarischen Vorgehensweise wird Ihr benutzerdefiniertes Steuerelement in etwa wie das folgende aussehen:
Den vollständigen Code finden Sie unter Vorgehensweise: Erstellen eines Windows Forms-Steuerelements, das Entwurfszeitfeatures nutzt.
Voraussetzungen
Sie benötigen Visual Studio, um diese exemplarische Vorgehensweise bearbeiten zu können.
Erstellen eines Projekts
Im ersten Schritt erstellen Sie das Anwendungsprojekt. Sie verwenden dieses Projekt, um die Anwendung zu erstellen, die das benutzerdefinierte Steuerelement enthält.
Erstellen Sie in Visual Studio ein neues Windows Forms-Anwendungsprojekt, und geben Sie ihm den Namen MarqueeControlTest.
Erstellen des Steuerelementbibliotheksprojekts
Fügen Sie der Projektmappe ein Projekt des Typs „Windows Forms-Steuerelementbibliothek“ hinzu. Geben Sie dem Projekt den Namen MarqueeControlLibrary.
Löschen Sie mit dem Projektmappen-Explorer das Standardsteuerelement des Projekts, indem Sie die Quelldatei mit dem Namen „UserControl1.cs“ oder „UserControl1.vb“ löschen, je nach der von Ihnen gewählten Sprache.
Fügen Sie dem
MarqueeControlLibrary
-Projekt ein neues UserControl-Element hinzu. Geben Sie der neuen Quelldatei den Basisnamen MarqueeControl.Erstellen Sie über den Projektmappen-Explorer einen neuen Ordner im
MarqueeControlLibrary
-Projekt.Klicken Sie mit der rechten Maustaste auf den Ordner Design, und fügen Sie eine neue Klasse hinzu. Nennen Sie sie MarqueeControlRootDesigner.
Sie müssen Typen aus der Assembly „System.Design“ verwenden, also fügen Sie diesen Verweis zum Projekt
MarqueeControlLibrary
hinzu.
Verweisen auf das benutzerdefinierte Steuerelement
Sie werden das benutzerdefinierte Steuerelement mithilfe des Projekts MarqueeControlTest
testen. Das Testprojekt erhält Kenntnis über das benutzerdefinierte Steuerelement, wenn Sie einen Projektverweis auf die Assembly MarqueeControlLibrary
hinzufügen.
Fügen Sie dem Projekt MarqueeControlTest
einen Projektverweis auf die Assembly MarqueeControlLibrary
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 zugehörigen benutzerdefinierten Designers
Ihr benutzerdefiniertes Steuerelement wird von der Klasse UserControl abgeleitet. Auf diese Weise kann Ihr Steuerelement andere Steuerelemente enthalten, und Ihr Steuerelement erhält zahlreiche Standardfunktionen.
Ihr benutzerdefiniertes Steuerelement verfügt über einen zugeordneten benutzerdefinierten Designer. So können Sie eine individuelle Designfunktionalität erstellen, die speziell auf Ihr benutzerdefiniertes Steuerelement zugeschnitten ist.
Sie ordnen das Steuerelement mithilfe der DesignerAttribute-Klasse dem zugehörigen Designer zu. Da Sie das gesamte Entwurfszeitverhalten Ihres benutzerdefinierten Steuerelements entwickeln, implementiert der benutzerdefinierte Designer die Schnittstelle IRootDesigner.
So definieren Sie ein benutzerdefiniertes Steuerelement und den zugehörigen benutzerdefinierten Designer
Öffnen Sie die Quelldatei
MarqueeControl
im Code-Editor. Importieren Sie am Anfang 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 das DesignerAttribute der
MarqueeControl
-Klassendeklaration hinzu. Dadurch wird das benutzerdefinierte Steuerelement dem entsprechenden 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 Quelldatei
MarqueeControlRootDesigner
im Code-Editor. Importieren Sie am Anfang 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
, um von der Klasse DocumentDesigner zu erben. Wenden Sie das ToolboxItemFilterAttribute an, um die Designerinteraktion mit der Toolbox anzugeben.Hinweis
Die Definition für die
MarqueeControlRootDesigner
-Klasse wurde in einen Namespace namens „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 in den 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
MarqueeControlTest
-Projekt ein neues UserControl-Element hinzu. Geben Sie der neuen Quelldatei den Basisnamen DemoMarqueeControl.Öffnen Sie die Datei
DemoMarqueeControl
im Code-Editor. Importieren Sie am Anfang der Datei denMarqueeControlLibrary
-Namespace:Imports MarqueeControlLibrary
using MarqueeControlLibrary;
Ändern Sie die Deklaration von
DemoMarqueeControl
, um von der KlasseMarqueeControl
zu erben.Erstellen Sie das Projekt.
Öffnen Sie „Form1“ im Windows Forms-Designer.
Suchen Sie in der Toolbox nach der Registerkarte MarqueeControlTest-Komponenten, und öffnen Sie sie. Ziehen Sie ein
DemoMarqueeControl
aus der Toolbox auf Ihr Formular.Erstellen Sie das Projekt.
Einrichten des Projekts für das Entwurfszeitdebuggen
Bei der Entwicklung einer benutzerdefinierten Entwurfszeitfunktionalität ist es notwendig, Ihre Steuerelemente und Komponenten zu debuggen. Es gibt eine einfache Möglichkeit, Ihr Projekt einzurichten, um das Debuggen zur Entwurfszeit 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. Die ausführbare Datei heißt „devenv.exe“, und wenn Sie die Datei am Standardspeicherort installiert haben, lautet der zugehörige 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.
Prüfpunkt
Sie können nun das Entwurfszeitverhalten Ihres benutzerdefinierten Steuerelements debuggen. Nachdem Sie sich davon überzeugt haben, dass die Debugumgebung korrekt eingerichtet ist, testen Sie die Verbindung zwischen dem benutzerdefinierten Steuerelement und dem benutzerdefinierten Designer.
So testen Sie die Debugumgebung und die Designerzuordnung
Öffnen Sie die Quelldatei „MarqueeControlRootDesigner“ im Code-Editor, und platzieren Sie einen Haltepunkt in der WriteLine-Anweisung.
Drücken Sie F5, um die Debugsitzung zu starten.
Eine neue Instanz von Visual Studio wird erstellt.
Öffnen Sie die Projektmappe „MarqueeControlTest“ in der neuen Visual Studio-Instanz. Sie können die Projektmappe ganz einfach finden, indem Sie im Menü Datei die Option Zuletzt verwendete Projekte auswählen. Die Projektmappendatei „MarqueeControlTest.sln“ wird als zuletzt verwendete Datei aufgeführt.
Öffnen Sie
DemoMarqueeControl
im 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 sind alle Voraussetzungen für die Entwicklung und das Debuggen Ihres benutzerdefinierten Steuerelements und des zugehörigen benutzerdefinierten Designers gegeben. Im verbleibenden Teil dieses Artikels geht es um die Einzelheiten der Implementierung von Features für das Steuerelement und den Designer.
Implementieren des benutzerdefinierten Steuerelements
Das MarqueeControl
ist ein UserControl mit einigen Anpassungen. Es macht zwei Methoden verfügbar: Start
, das die Marquee-Animation startet, und Stop
, das die Animation beendet. Da MarqueeControl
untergeordnete Steuerelemente enthält, die die Schnittstelle IMarqueeWidget
implementieren, zählen Start
und Stop
jedes untergeordnete Steuerelement auf und rufen die Methoden StartMarquee
und StopMarquee
für jedes untergeordnete Steuerelement auf, das IMarqueeWidget
implementiert.
Das Aussehen der Steuerelemente MarqueeBorder
und MarqueeText
hängt vom Layout ab, daher überschreibt MarqueeControl
die Methode OnLayout und ruft PerformLayout für untergeordnete Steuerelemente dieses Typs auf.
Dies ist der Umfang der MarqueeControl
-Anpassungen. Die Laufzeitfeatures werden durch die Steuerelemente MarqueeBorder
und MarqueeText
implementiert, die Entwurfszeitfeatures werden durch die Klassen MarqueeBorderDesigner
und MarqueeControlRootDesigner
implementiert.
So implementieren Sie Ihr benutzerdefiniertes Steuerelement
Öffnen Sie die Quelldatei
MarqueeControl
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
MarqueeControl
hostet zwei Arten von untergeordneten Steuerelementen: das Steuerelement MarqueeBorder
und das Steuerelement MarqueeText
.
MarqueeBorder
: Dieses Steuerelement zeichnet einen Rahmen aus „Lichtern“ um seine Ränder. Sie leuchten nacheinander auf, sodass der Eindruck entsteht, sie würden sich um den Rand herum bewegen. Die Geschwindigkeit, mit der die Lichter blinken, wird durch eine Eigenschaft namensUpdatePeriod
gesteuert. Verschiedene weitere benutzerdefinierte Eigenschaften bestimmen andere Aspekte des Aussehens des Steuerelements. Die beiden MethodenStartMarquee
undStopMarquee
steuern, wann die Animation beginnt und endet.MarqueeText
: Dieses Steuerelement erzeugt eine blinkende Zeichenfolge. Wie beim SteuerelementMarqueeBorder
wird die Geschwindigkeit, mit der der Text blinkt, durch die EigenschaftUpdatePeriod
gesteuert. Das SteuerelementMarqueeText
verfügt ebenso wie das SteuerelementMarqueeBorder
über die MethodenStartMarquee
undStopMarquee
.
Zur Entwurfszeit können diese beiden Steuerelementtypen mit MarqueeControlRootDesigner
in beliebiger Kombination zu einem MarqueeControl
hinzugefügt werden.
Die gemeinsamen Merkmale der beiden Steuerelemente werden in der Schnittstelle IMarqueeWidget
zusammengefasst. Dadurch kann MarqueeControl
alle Marquee-bezogenen untergeordneten Steuerelemente erkennen und entsprechend verarbeiten.
Um die intermittierende Animation zu implementieren, verwenden Sie BackgroundWorker-Objekte aus dem System.ComponentModel-Namespace. Sie könnten zwar Timer-Objekte verwenden, aber wenn viele IMarqueeWidget
-Objekte vorhanden sind, kann der einzelne UI-Thread möglicherweise nicht mit der Animation Schritt halten.
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 Quelldatei
IMarqueeWidget
im Code-Editor, und ändern Sie die Deklaration vonclass
ininterface
:// 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 den folgenden Code zur Schnittstelle
IMarqueeWidget
hinzu, um zwei Methoden und eine Eigenschaft bereitzustellen, die die Marquee-Animation beeinflussen:// 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
MarqueeControlLibrary
ein neues Element Benutzerdefiniertes Steuerelement 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 selbst asynchron zu aktualisieren.Legen Sie im Fenster Eigenschaften die Eigenschaften
WorkerReportsProgress
und WorkerSupportsCancellation der Komponente BackgroundWorker auf true fest. Diese Einstellungen ermöglichen es der Komponente BackgroundWorker, das Ereignis ProgressChanged periodisch auszulösen und asynchrone Aktualisierungen abzubrechen.Weitere Informationen finden Sie unter BackgroundWorker-Komponente.
Öffnen Sie die Quelldatei
MarqueeText
im Code-Editor. Importieren Sie am Anfang 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 die SchnittstelleIMarqueeWidget
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 Feld
isLit
bestimmt, ob der Text in der durch die EigenschaftLightColor
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 Methoden
StartMarquee
undStopMarquee
rufen die Methoden RunWorkerAsync und CancelAsync der Komponente BackgroundWorker auf, um die Animation zu starten und zu stoppen.Die Attribute Category und Browsable werden auf die Eigenschaft
UpdatePeriod
angewendet, sodass sie in einem benutzerdefinierten Abschnitt des Eigenschaftsfensters namens „Marquee“ erscheint.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 Eigenschaftenaccessoren. Sie machen zwei Eigenschaften für Clients verfügbar:
LightColor
undDarkColor
. Die Attribute Category und Browsable werden auf diese Eigenschaften angewendet, sodass sie in einem benutzerdefinierten Abschnitt des Eigenschaftsfensters namens „Marquee“ erscheinen.[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 Ereignisse DoWork und ProgressChanged der Komponente BackgroundWorker.
Der Ereignishandler DoWork wartet die durch
UpdatePeriod
festgelegte Anzahl von Millisekunden und löst dann das Ereignis ProgressChanged aus, bis Ihr Code die Animation durch den Aufruf von CancelAsync beendet.Der Ereignishandler ProgressChanged schaltet zwischen dem hellen und dem dunklen Textzustand um, sodass der Eindruck entsteht, dass der Text blinkt.
// 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 Methode OnPaint, 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 Projektmappe zu erstellen.
Erstellen des untergeordneten MarqueeBorder-Steuerelements
Das Steuerelement MarqueeBorder
ist etwas ausgefeilter als das Steuerelement MarqueeText
. Es umfasst mehr Eigenschaften und die Animation in der OnPaint-Methode ist aufwändiger. Im Prinzip ist es dem Steuerelement MarqueeText
recht ähnlich.
Da das Steuerelement MarqueeBorder
untergeordnete Steuerelemente enthalten kann, muss es über Layout-Ereignisse informiert sein.
So erstellen Sie das MarqueeBorder-Steuerelement
Fügen Sie dem Projekt
MarqueeControlLibrary
ein neues Element Benutzerdefiniertes Steuerelement 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 selbst asynchron zu aktualisieren.Legen Sie im Fenster Eigenschaften die Eigenschaften
WorkerReportsProgress
und WorkerSupportsCancellation der Komponente BackgroundWorker auf true fest. Diese Einstellungen ermöglichen es der Komponente BackgroundWorker, das Ereignis ProgressChanged periodisch auszulösen und asynchrone Aktualisierungen abzubrechen. Weitere Informationen finden Sie unter BackgroundWorker-Komponente.Klicken Sie im Fenster Eigenschaften auf die Schaltfläche Ereignisse. Fügen Sie Handler für die Ereignisse DoWork und ProgressChanged hinzu.
Öffnen Sie die Quelldatei
MarqueeBorder
im Code-Editor. Importieren Sie am Anfang 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
, um von Panel zu erben und die SchnittstelleIMarqueeWidget
zu implementieren.[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 zur Verwaltung des Zustands des Steuerelements
MarqueeBorder
:MarqueeSpinDirection
bestimmt die Richtung, in der sich die Lichter um den Rand „bewegen“, undMarqueeLightShape
bestimmt die Form der Lichter (quadratisch oder kreisförmig). 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 Methoden
StartMarquee
undStopMarquee
rufen die Methoden RunWorkerAsync und CancelAsync der Komponente BackgroundWorker auf, um die Animation zu starten und zu stoppen.Da das Steuerelement
MarqueeBorder
untergeordnete Steuerelemente enthalten kann, zählt die MethodeStartMarquee
alle untergeordneten Steuerelemente auf und ruftStartMarquee
für diejenigen auf, dieIMarqueeWidget
implementieren. DieStopMarquee
-Methode verfügt über 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 Eigenschaftenaccessoren. Das Steuerelement
MarqueeBorder
verfügt über mehrere Eigenschaften zur Steuerung seines Aussehens.[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 Ereignisse DoWork und ProgressChanged der Komponente BackgroundWorker.
Der Ereignishandler DoWork wartet die durch
UpdatePeriod
festgelegte Anzahl von Millisekunden und löst dann das Ereignis ProgressChanged aus, bis Ihr Code die Animation durch den Aufruf von CancelAsync beendet.Der Ereignishandler ProgressChanged erhöht die Position des „Basislichts“, von dem aus der Hell-/ Dunkelzustand der anderen Lichter bestimmt wird, und ruft die Methode Refresh auf, damit das Steuerelement neu gezeichnet wird.
// 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 Methode
IsLit
bestimmt die Farbe eines Lichts an einer bestimmten Position. „Eingeschaltete“ Lichter werden in der Farbe gezeichnet, die durch die EigenschaftLightColor
angegeben ist, und „dunkle“ Lichter werden in der Farbe gezeichnet, die durch die EigenschaftDarkColor
angegeben ist.Die Methode
DrawLight
zeichnet ein Licht mit der entsprechenden 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 Methode OnPaint zeichnet die Lichter entlang der Ränder des Steuerelements
MarqueeBorder
.Da die Methode OnPaint von den Abmessungen des Steuerelements
MarqueeBorder
abhängt, müssen Sie sie bei jeder Änderung des Layouts aufrufen. Um dies zu erreichen, überschreiben OnLayout sie und rufen 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 Klasse MarqueeControlRootDesigner
stellt die Implementierung für den RootDesigner bereit. Zusätzlich zu diesem Designer, der mit dem MarqueeControl
arbeitet, benötigen Sie einen benutzerdefinierten Designer, der ausdrücklich dem Steuerelement MarqueeBorder
zugeordnet ist. Dieser Designer bietet ein benutzerdefiniertes Verhalten, das im Kontext des benutzerdefinierten Stamm-Designers angemessen ist.
Insbesondere sorgt das Steuerelement MarqueeBorderDesigner
für ein „Shadowing“ und die Filterung bestimmter Eigenschaften des Steuerelements MarqueeBorder
, wodurch dessen Interaktion mit der Entwurfsumgebung verändert wird.
Das Abfangen von Aufrufen des Eigenschaftsaccessors einer Komponente wird als „Shadowing“ bezeichnet. Dadurch kann ein Designer den vom Benutzer festgelegten Wert nachverfolgen und diesen Wert optional an die entworfene Komponente weitergeben.
In diesem Beispiel erfolgt ein Shadowing der Eigenschaften Visible und Enabled durch die Eigenschaft MarqueeBorderDesigner
, was den Benutzer daran hindert, das Steuerelement MarqueeBorder
während der Entwurfszeit als unsichtbar oder deaktiviert festzulegen.
Designer können außerdem Eigenschaften hinzufügen und entfernen. In diesem Beispiel wird die Eigenschaft Padding zur Entwurfszeit entfernt, da das Steuerelement MarqueeBorder
die Auffüllung basierend auf der Größe der durch die Eigenschaft LightSize
angegebenen Lichter programmgesteuert festlegt.
Die Basisklasse für MarqueeBorderDesigner
ist ComponentDesigner, die über Methoden verfügt, mit denen die Attribute, Eigenschaften und Ereignisse eines Steuerelements zur Entwurfszeit geändert werden können:
Wenn Sie die öffentliche Schnittstelle einer Komponente mithilfe dieser Methoden ändern, befolgen Sie diese Regeln:
Stellen Sie sicher, dass Elemente nur in den
PreFilter
-Methoden hinzugefügt oder entfernt werden.Ändern vorhandener Elemente nur in den
PostFilter
-Methoden.Rufen Sie die Basisimplementierung immer zuerst in den
PreFilter
-Methoden auf.Rufen Sie die Basisimplementierung immer zuletzt in den
PostFilter
-Methoden auf.
Durch die Einhaltung dieser Regeln wird sichergestellt, dass alle Designer in der Entwurfszeitumgebung eine einheitliche Sicht auf alle zu entwerfenden Komponenten haben.
Die Klasse ComponentDesigner bietet ein Wörterbuch zum Verwalten der Werte von Eigenschaften mit Shadowing, sodass Sie keine spezifischen Instanzvariablen erstellen müssen.
So erstellen Sie einen benutzerdefinierten Designer für das Shadowing und Filterung von Eigenschaften
Klicken Sie mit der rechten Maustaste auf den Ordner Design, und fügen Sie eine neue Klasse hinzu. Geben Sie der Quelldatei den Basisnamen MarqueeBorderDesigner.
Öffnen Sie die Quelldatei „MarqueeBorderDesigner“ im Code-Editor. Importieren Sie am Anfang 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
, um von ParentControlDesigner zu erben.Da das Steuerelement
MarqueeBorder
untergeordnete Steuerelemente enthalten kann, erbtMarqueeBorderDesigner
von ParentControlDesigner, das die Interaktion zwischen übergeordneten und untergeordneten Steuerelementen verwaltet.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-Eigenschaft und die Visible-Eigenschaft. Diese Implementierungen sorgen für ein Shadowing der 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
Verarbeiten von Komponentenänderungen
Die Klasse MarqueeControlRootDesigner
stellt die benutzerdefinierte Entwurfszeitfunktionalität für Ihre MarqueeControl
-Instanzen bereit. Der größte Teil der Entwurfszeitfunktionalität wird von der Klasse DocumentDesigner geerbt. Ihr Code implementiert zwei spezifische Anpassungen: die Verarbeitung von Komponentenänderungen und das Hinzufügen von Designerverben.
Wenn Benutzer ihre MarqueeControl
-Instanzen entwerfen, verfolgt Ihr Stamm-Designer Änderungen an MarqueeControl
und dessen untergeordneten Steuerelementen. Die Entwurfszeitumgebung stellt den nützlichen Dienst IComponentChangeService bereit, um Änderungen am Komponentenzustand nachzuverfolgen.
Sie rufen einen Verweis auf diesen Dienst ab, indem Sie die Umgebung mit der Methode GetService abfragen. Wenn die Abfrage erfolgreich ist, kann Ihr Designer einen Handler für das ComponentChanged-Ereignis anfügen und alle erforderlichen Aufgaben ausführen, 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 Klasse MarqueeControl
enthalten ist. Dies führt dazu, dass das Objekt IMarqueeWidget
bei Eigenschaftenänderungen ordnungsgemäß neu gezeichnet wird, beispielsweise bei Änderung der Eigenschaften des übergeordneten Objekts Size.
So verarbeiten Sie Komponentenänderungen
Öffnen Sie die Quelldatei
MarqueeControlRootDesigner
im Code-Editor, und überschreiben Sie die Methode Initialize. Rufen Sie die Basisimplementierung von Initialize auf, und fragen Sie IComponentChangeService ab.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 Ereignishandler OnComponentChanged. Testen Sie den Typ der sendenden Komponente. Wenn es sich um
IMarqueeWidget
handelt, rufen Sie die zugehörige Methode Refresh 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 mit einem Ereignishandler verknüpfter Menübefehl. Designerverben werden zur Entwurfszeit dem Kontextmenü einer Komponente hinzugefügt. Weitere Informationen finden Sie unter DesignerVerb.
Sie werden Ihren Designern zwei Verben hinzufügen: Test ausführen und Test beenden. Mit diesen Verben können Sie das Laufzeitverhalten von MarqueeControl
zur Entwurfszeit anzeigen. Diese Verben werden MarqueeControlRootDesigner
hinzugefügt.
Beim Aufruf von Test ausführen ruft der Ereignishandler des Verbs die Methode StartMarquee
für MarqueeControl
auf. Beim Aufruf von Test beenden ruft der Ereignishandler des Verbs die Methode StopMarquee
für MarqueeControl
auf. Die Implementierung der Methoden StartMarquee
und StopMarquee
ruft diese Methoden für enthaltene Steuerelemente auf, die IMarqueeWidget
implementieren, sodass alle enthaltenen Steuerelemente IMarqueeWidget
ebenfalls in den Test einbezogen werden.
So fügen Sie Ihren benutzerdefinierten Designern Designerverben hinzu
Fügen Sie in der Klasse
MarqueeControlRootDesigner
Ereignishandler mit den NamenOnVerbRunTest
undOnVerbStopTest
hinzu.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 den entsprechenden Designerverben.
MarqueeControlRootDesigner
erbt eine DesignerVerbCollection von seiner Basisklasse. Sie erstellen zwei neue DesignerVerb-Objekte und fügen sie dieser Auflistung in der Methode Initialize 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)))
Erstellen eines benutzerdefinierten UITypeEditors
Beim Erstellen einer benutzerdefinierten Entwurfszeitfunktionalität für Benutzer ist es oft wünschenswert, eine benutzerdefinierte Interaktion mit dem Eigenschaftenfenster zu ermöglichen. Sie können dies erreichen, indem Sie einen UITypeEditor erstellen.
Das MarqueeBorder
-Steuerelement macht mehrere Eigenschaften im Eigenschaftenfenster verfügbar. Zwei dieser Eigenschaften, MarqueeSpinDirection
und MarqueeLightShape
, werden durch Enumerationen dargestellt. Um die Verwendung eines UITypEditors zu veranschaulichen, wird der Eigenschaft MarqueeLightShape
eine Klasse UITypeEditor zugeordnet.
So erstellen Sie einen benutzerdefinierten UITypEditor
Öffnen Sie die Quelldatei
MarqueeBorder
im Code-Editor.Deklarieren Sie in der Definition der Klasse
MarqueeBorder
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 IWindowsFormsEditorService-Instanzvariable mit dem Namen
editorService
.private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
Überschreiben Sie die GetEditStyle -Methode. Diese Implementierung gibt DropDown zurück, wodurch der Entwurfsumgebung mitgeteilt wird, wie der
LightShapeEditor
angezeigt werden soll.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 nach einem IWindowsFormsEditorService-Objekt ab. Bei einer erfolgreichen Abfrage wird ein
LightShapeSelectionControl
erstellt. Die Methode DropDownControl wird aufgerufen, um denLightShapeEditor
zu starten. Der Rückgabewert dieses Aufrufs 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 Ihren benutzerdefinierten UITypeEditor
Die Eigenschaft MarqueeLightShape
unterstützt zwei Arten von Lichtformen: Square
und Circle
. Sie erstellen ein benutzerdefiniertes Steuerelement, das ausschließlich dazu dient, diese Werte im Eigenschaftenfenster grafisch darzustellen. Dieses benutzerdefinierte Steuerelement wird von Ihrem UITypeEditor zur Interaktion mit dem Eigenschaftenfenster verwendet.
So erstellen Sie ein Ansichtssteuerelement für Ihren benutzerdefinierten UITypeEditor
Fügen Sie dem
MarqueeControlLibrary
-Projekt ein neues UserControl-Element hinzu. Geben Sie der neuen Quelldatei den Basisnamen LightShapeSelectionControl.Ziehen Sie zwei Panel-Steuerelemente aus der Toolbox auf das
LightShapeSelectionControl
. Nennen Sie siesquarePanel
undcirclePanel
. Ordnen Sie sie nebeneinander an. Legen Sie die Eigenschaft Size der beiden Panel-Steuerelemente auf (60, 60) fest. Legen Sie die Eigenschaft Location dessquarePanel
-Steuerelemente auf (8, 10) fest. Legen Sie die Eigenschaft Location descirclePanel
-Steuerelemente auf (80, 10) fest. Und legen Sie schließlich die Eigenschaft Size vonLightShapeSelectionControl
auf (150, 80) fest.Öffnen Sie die Quelldatei
LightShapeSelectionControl
im Code-Editor. Importieren Sie am Anfang der Datei den System.Windows.Forms.Design-Namespace:Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
Implementieren Sie Click-Ereignishandler für die Steuerelemente
squarePanel
undcirclePanel
. Diese Methoden rufen CloseDropDown auf, um die Bearbeitungssitzung für den benutzerdefinierten UITypeEditor 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 IWindowsFormsEditorService-Instanzvariable mit dem Namen
editorService
.Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
Deklarieren Sie eine
MarqueeLightShape
-Instanzvariable mit dem NamenlightShapeValue
.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
Fügen Sie im Konstruktor
LightShapeSelectionControl
die Click-Ereignishandler an die Ereignisse Click der SteuerelementesquarePanel
undcirclePanel
an. Definieren Sie außerdem eine Konstruktorüberladung, die denMarqueeLightShape
-Wert aus der Entwurfsumgebung dem FeldlightShapeValue
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 Methode Dispose.
Implementiert 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. Durch diese Implementierung werden ein ausgefülltes Quadrat und ein Kreis gezeichnet. Außerdem wird der ausgewählte Wert hervorgehoben, indem ein Rahmen um eine der beiden Formen 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 des benutzerdefinierten Steuerelements im Designer
An diesem Punkt können Sie das MarqueeControlLibrary
-Projekt kompilieren. Testen Sie Ihre Implementierung, indem Sie ein Steuerelement erstellen, das von der Klasse MarqueeControl
erbt, und dieses in einem Formular verwenden.
So erstellen Sie eine benutzerdefinierte MarqueeControl-Implementierung
Öffnen Sie
DemoMarqueeControl
im Windows Forms-Designer. Dies erzeugt eine Instanz des TypsDemoMarqueeControl
und zeigt sie in einer Instanz des TypsMarqueeControlRootDesigner
an.Öffnen Sie in der Toolbox die Registerkarte MarqueeControlLibrary-Komponenten. Sie sehen, dass hier die Steuerelemente
MarqueeBorder
undMarqueeText
zur Auswahl stehen.Ziehen Sie eine Instanz des Steuerelements
MarqueeBorder
auf dieDemoMarqueeControl
-Entwurfsoberfläche. Docken Sie diesesMarqueeBorder
-Steuerelement an das übergeordnete Steuerelement an.Ziehen Sie eine Instanz des Steuerelements
MarqueeText
auf dieDemoMarqueeControl
-Entwurfsoberfläche.Erstellen Sie die Projektmappe.
Klicken Sie mit der rechten Maustaste auf
DemoMarqueeControl
, und wählen Sie im Kontextmenü die Option Test ausführen aus, um die Animation zu starten. Klicken Sie auf Test beenden, um die Animation zu beenden.Öffnen Sie Form1 in der Designansicht.
Platzieren Sie zwei Button-Steuerelemente im Formular. Geben Sie ihnen die Namen
startButton
undstopButton
, und ändern Sie die den Wert der Text-Eigenschaft in Starten bzw. Beenden.Implementieren Sie Click-Ereignishandler für beide Button-Steuerelemente.
Öffnen Sie in der Toolbox die Registerkarte MarqueeControlTest-Komponenten. Sie sehen, dass
DemoMarqueeControl
zur Auswahl steht.Ziehen Sie eine Instanz von
DemoMarqueeControl
auf die Form1-Entwurfsoberfläche.Rufen Sie in den Click-Ereignishandlern die Methoden
Start
undStop
für dasDemoMarqueeControl
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 Projekt
MarqueeControlTest
als Startprojekt fest, und führen Sie es aus. Sie sehen das Formular, in dem IhrDemoMarqueeControl
angezeigt wird. Klicken Sie auf die Schaltfläche Starten, um die Animation zu starten. Sie sollten sehen, dass der Text blinkt und die Lichter sich um den Rand bewegen.
Nächste Schritte
Das MarqueeControlLibrary
-Beispiel veranschaulicht eine einfach Implementierung von benutzerdefinierten Steuerelementen und zugehörigen Designern. Sie können dieses Beispiel auf verschiedene Weise verfeinern:
Ändern Sie die Eigenschaftswerte für das
DemoMarqueeControl
im Designer. Fügen Sie weitereMarqueBorder
-Steuerelemente hinzu, und docken Sie sie innerhalb ihrer übergeordneten Instanzen an, um einen Schachtelungseffekt zu erzielen. Experimentieren Sie mit verschiedenen Einstellungen fürUpdatePeriod
und die lichtbezogenen Eigenschaften.Erstellen Sie eigenen Implementierungen von
IMarqueeWidget
. Sie könnten zum Beispiel ein blinkendes „Neonschild“ oder ein animiertes Schild mit verschiedenen Bildern erstellen.Passen Sie die Entwurfszeitfunktionalität weiter an. Sie könnten versuchen, ein Shadowing auf mehr als nur die Eigenschaften Enabled and Visible anzuwenden, und Sie könnten neue Eigenschaften hinzufügen. Fügen Sie neue Designerverben hinzu, um häufige Aufgaben wie das Andocken von untergeordneten Steuerelementen zu vereinfachen.
Lizenzieren Sie das
MarqueeControl
.Steuern Sie, wie Ihre Steuerelemente serialisiert werden und wie der Code für sie generiert wird. Weitere Informationen finden Sie unter Kompilieren und Generieren von dynamischem Quellcode.
Siehe auch
.NET Desktop feedback