Exemplarische Vorgehensweise: Erstellen eines Windows Forms-Steuerelements, das Visual Studio-Entwurfszeitfeatures nutzt

Die Entwurfszeiterfahrung für ein benutzerdefiniertes Steuerelement kann verbessert werden, indem ein zugeordneter benutzerdefinierter Designer erstellt wird.

Diese exemplarische Vorgehensweise veranschaulicht, wie ein benutzerdefinierter Designer für ein benutzerdefiniertes Steuerelement erstellt wird. Sie implementieren einen MarqueeControl-Typ und eine zugeordnete Designerklasse mit dem Namen MarqueeControlRootDesigner.

Der MarqueeControl-Typ implementiert eine Anzeige, die vergleichbar ist mit einem Theatervordach mit animierten Lichtern und blinkendem Text.

Der Designer für dieses Steuerelement interagiert mit der Entwurfsumgebung, um eine benutzerdefinierte Entwurfszeiterfahrung bereitzustellen. Mit dem benutzerdefinierten Designer können Sie eine benutzerdefinierte MarqueeControl-Implementierung mit animierten Lichtern und blinkendem Text in vielen Kombinationen erstellen. Sie können das erstellte Steuerelement ebenso wie andere Windows Forms-Steuerelemente auf einem Formular verwenden.

Zu den Aufgaben in dieser exemplarischen Vorgehensweise gehören:

  • Erstellen des Projekts

  • Erstellen eines Steuerelementbibliothek-Projekts

  • Verweisen auf das benutzerdefinierte Steuerelementprojekt

  • Definieren eines benutzerdefinierten Steuerelements und seines benutzerdefinierten Designers

  • Erstellen einer Instanz des benutzerdefinierten Steuerelements

  • Einrichten des Projekts zum Entwurfszeitdebuggen

  • Implementieren des benutzerdefinierten Steuerelements

  • Erstellen eines untergeordneten Steuerelements für das benutzerdefinierte Steuerelement

  • Erstellen des untergeordneten MarqueeBorder-Steuerelements

  • Erstellen eines benutzerdefinierten Designers, um Eigenschaften zu filtern und Shadowing auszuführen

  • Behandeln von Komponentenänderungen

  • Hinzufügen von Designerverben zum benutzerdefinierten Designer

  • Erstellen eines benutzerdefinierten UI-Typ-Editors

  • Testen des benutzerdefinierten Steuerelements im Designer

Zum Schluss sieht das benutzerdefinierte Steuerelement etwa folgendermaßen aus:

Mögliche Anordnung eines MarqueeControl

Die vollständige Codeliste finden Sie unter Gewusst wie: Erstellen eines Windows Forms-Steuerelements, das Entwurfszeitfeatures nutzt.

Hinweis

Je nach den aktiven Einstellungen oder der verwendeten Version können die angezeigten Dialogfelder und Menübefehle von den in der Hilfe beschriebenen abweichen. Klicken Sie im Menü Extras auf Einstellungen importieren und exportieren, um die Einstellungen zu ändern. Weitere Informationen finden Sie unter Visual Studio-Einstellungen.

Vorbereitungsmaßnahmen

Für die Durchführung dieser exemplarischen Vorgehensweise benötigen Sie Folgendes:

  • Ausreichende Berechtigungen zum Erstellen und Ausführen von Windows Forms-Anwendungsprojekten auf dem Computer, auf dem Visual Studio installiert ist.

Erstellen des Projekts

Zunächst muss das Anwendungsprojekt erstellt werden. Mit diesem Projekt erstellen Sie die Anwendung, die das benutzerdefinierte Steuerelement hostet.

So erstellen Sie das Projekt

Erstellen eines Steuerelementbibliothek-Projekts

Der nächste Schritt besteht darin, das Steuerelementbibliothek-Projekt zu erstellen. Sie erstellen ein neues benutzerdefiniertes Steuerelement und den entsprechenden benutzerdefinierten Designer.

So erstellen Sie das Steuerelementbibliothek-Projekt

  1. Fügen Sie der Projektmappe ein Windows-Steuerelementbibliothek-Projekt hinzu. Weitere Informationen finden Sie unter Dialogfeld "Neues Projekt hinzufügen". Nennen Sie das Projekt "MarqueeControlLibrary".

  2. Löschen Sie im Projektmappen-Explorer das standardmäßige Steuerelement des Projekts, indem Sie die Quelldatei "UserControl1.cs" oder "UserControl1.vb" löschen, je nach ausgewählter Sprache. Weitere Informationen finden Sie unter Gewusst wie: Entfernen, Löschen und Ausschließen von Elementen.

  3. Fügen Sie dem MarqueeControlLibrary-Projekt ein neues UserControl-Element hinzu. Weisen Sie der neuen Quelldatei den Basisnamen "MarqueeControl" zu.

  4. Erstellen Sie im Projektmappen-Explorer einen neuen Ordner im MarqueeControlLibrary-Projekt. Weitere Informationen finden Sie unter Gewusst wie: Hinzufügen neuer Projektelemente. Nennen Sie den neuen Ordner "Design".

  5. Klicken Sie mit der rechten Maustaste auf den Ordner Design, und fügen Sie eine neue Klasse hinzu. Geben Sie der Quelldatei den Basisnamen "MarqueeControlRootDesigner".

  6. Sie müssen Typen aus der System.Design-Assembly verwenden; fügen Sie also diesen Verweis zum MarqueeControlTest-Projekt hinzu. Weitere Informationen finden Sie unter Gewusst wie: Hinzufügen oder Entfernen von Verweisen in Visual Studio (C#, J#).

Verweisen auf das benutzerdefinierte Steuerelementprojekt

Sie verwenden das MarqueeControlTest-Projekt, um das benutzerdefinierte Steuerelement zu testen. Das Testprojekt erkennt das benutzerdefinierte Steuerelement, wenn Sie der MarqueeControlLibrary-Assembly einen Projektverweis hinzufügen.

So verweisen Sie auf das benutzerdefinierte Steuerelementprojekt

  • Fügen Sie im MarqueeControlTest-Projekt der MarqueeControlLibrary-Assembly einen Projektverweis hinzu. Verwenden Sie die Registerkarte Projekte im Dialogfeld Verweis hinzufügen, anstatt direkt auf die MarqueeControlLibrary-Assembly zu verweisen.

Definieren eines benutzerdefinierten Steuerelements und seines benutzerdefinierten Designers

Das benutzerdefinierte Steuerelement leitet sich von der UserControl-Klasse ab. Dadurch kann es andere Steuerelemente enthalten und erhält einen Großteil der Standardfunktionen.

Das benutzerdefinierte Steuerelement verfügt über einen zugeordneten benutzerdefinierten Designer. Dies ermöglicht es Ihnen, einen eindeutigen Entwurfsvorgang zu entwickeln, der speziell an das benutzerdefinierte Steuerelement angepasst ist.

Sie ordnen das Steuerelement seinem Designer zu, indem Sie die DesignerAttribute-Klasse verwenden. Da Sie das gesamte Entwurfszeitverhalten des benutzerdefinierten Steuerelements entwickeln, implementiert der benutzerdefinierte Designer die IRootDesigner-Schnittstelle.

So definieren Sie ein benutzerdefiniertes Steuerelement und seinen benutzerdefinierten Designer

  1. Öffnen Sie die MarqueeControl-Quelldatei im Code-Editor. Importieren Sie am Anfang der Datei folgende Namespaces:

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  2. Fügen Sie das DesignerAttribute der MarqueeControl-Klassendeklaration hinzu. Dadurch wird das benutzerdefinierte Steuerelement seinem Designer zugeordnet.

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
    [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
    public class MarqueeControl : UserControl
    {
    
  3. Öffnen Sie die MarqueeControlRootDesigner-Quelldatei im Code-Editor. Importieren Sie am Anfang der Datei folgende Namespaces:

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing.Design
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing.Design;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  4. Ändern Sie die Deklaration von MarqueeControlRootDesigner, um von der DocumentDesigner-Klasse zu erben. Übernehmen Sie ToolboxItemFilterAttribute, um die Designerinteraktion mit der Toolbox anzugeben.

    Hinweis   Die Definition für die MarqueeControlRootDesigner-Klasse wurde in einem Namespace mit der Bezeichnung "MarqueeControlLibrary.Design" eingeschlossen. Durch diese Deklaration wird der Designer in einem speziellen Namespace platziert, der für entwurfsbezogene Typen reserviert ist.

    Namespace MarqueeControlLibrary.Design
    
        <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
        ToolboxItemFilterType.Require), _
        ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
        ToolboxItemFilterType.Require)> _
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeControlRootDesigner
            Inherits DocumentDesigner
    
    namespace MarqueeControlLibrary.Design
    {
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeControlRootDesigner : DocumentDesigner
        {
    
  5. Definieren Sie den Konstruktor für die MarqueeControlRootDesigner-Klasse. Fügen Sie eine WriteLine-Anweisung in den Konstruktortext ein. Diese Vorgehensweise wird zum Debuggen empfohlen.

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

Erstellen einer Instanz des benutzerdefinierten Steuerelements

Um das Entwurfszeitverhalten des benutzerdefinierten Steuerelements zu überprüfen, fügen Sie dem Formular im MarqueeControlTest-Projekt eine Instanz des benutzerdefinierten Steuerelements hinzu.

So erstellen Sie eine Instanz des benutzerdefinierten Steuerelements

  1. Fügen Sie dem MarqueeControlTest-Projekt ein neues UserControl-Element hinzu. Weisen Sie der neuen Quelldatei den Basisnamen "DemoMarqueeControl" zu.

  2. Öffnen Sie die DemoMarqueeControl-Datei im Code-Editor. Importieren Sie am Anfang der Datei den MarqueeControlLibrary-Namespace:

Imports MarqueeControlLibrary
using MarqueeControlLibrary;

Einrichten des Projekts zum Entwurfszeitdebuggen

Wenn Sie eine benutzerdefinierte Entwurfszeiterfahrung entwickeln, müssen Sie die Steuerelemente und Komponenten debuggen. Es gibt eine einfache Möglichkeit, das Projekt zum Entwurfszeitdebuggen einzurichten. Weitere Informationen finden Sie unter Exemplarische Vorgehensweise: Debuggen von benutzerdefinierten Windows Forms-Steuerelementen zur Entwurfszeit.

So richten Sie das Projekt zum Entwurfszeitdebuggen ein

  1. Klicken Sie mit der rechten Maustaste auf das MarqueeControlLibrary-Projekt, und wählen Sie Eigenschaften.

  2. Wählen Sie im Dialogfeld "MarqueeControlLibrary Property Pages" die Seite Konfigurationseigenschaften aus.

  3. Wählen Sie im Abschnitt Startaktion die Option Externes Programm starten aus. Da Sie eine separate Instanz von Visual Studio debuggen, klicken Sie auf die Schaltfläche mit den Auslassungszeichen (VisualStudioEllipsesButton-Bildschirmabbild), um nach der Visual Studio IDE zu suchen. Der Name der ausführbaren Datei lautet devenv.exe. Wenn am Standardspeicherort installiert wurde, lautet der Pfad "C:\\Programme\\Microsoft Visual Studio 8\Common7\IDE\devenv.exe".

  4. Klicken Sie auf OK, um das Dialogfeld zu schließen.

  5. Klicken Sie mit der rechten Maustaste auf das MarqueeControlLibrary-Projekt, und wählen Sie "Als Startprojekt festlegen" aus, um diese Debugkonfiguration zu aktivieren.

Checkpoint

Sie sind nun bereit, das Entwurfszeitverhalten des benutzerdefinierten Steuerelements zu debuggen. Nachdem Sie sichergestellt haben, dass die Debugumgebung ordnungsgemäß eingerichtet wurde, testen Sie die Zuordnung zwischen dem benutzerdefinierten Steuerelement und dem benutzerdefinierten Designer.

So testen Sie die Debugumgebung und die Designerzuordnung

  1. Öffnen Sie die Quelldatei MarqueeControlRootDesigner im Code-Editor, und platzieren Sie auf der WriteLine-Anweisung einen Haltepunkt.

  2. Drücken Sie F5, um die Debugsitzung zu starten. Beachten Sie, dass eine neue Instanz von Visual Studio erstellt wird.

  3. Öffnen Sie in der neuen Instanz von Visual Studio die Projektmappe "MarqueeControlTest". Sie können die Projektmappe leicht finden, indem Sie im Menü Datei die Option Zuletzt geöffnete Projekte auswählen. Die Projektmappendatei "MarqueeControlTest.sln" wird als zuletzt verwendete Datei aufgelistet.

  4. Öffnen Sie das DemoMarqueeControl im Designer. Beachten Sie, dass die Debuginstanz von Visual Studio aktiviert und die Ausführung am Haltepunkt beendet wird. Drücken Sie F5, um die Debugsitzung fortzusetzen.

An diesem Punkt können Sie damit beginnen, das benutzerdefinierte Steuerelement und seinen zugeordneten Designer zu entwickeln und zu debuggen. Im restlichen Teil dieser exemplarischen Vorgehensweise werden in erster Linie die Details der Implementierung von Features für das Steuerelement und den Designer erläutert.

Implementieren des benutzerdefinierten Steuerelements

Das MarqueeControl ist ein UserControl, für das geringfügige Anpassungen erforderlich sind. Es macht zwei Methoden verfügbar: Start startet die Animation, und Stop beendet die Animation. Da das MarqueeControl untergeordnete Steuerelemente enthält, die die IMarqueeWidget-Schnittstelle implementieren, listen Start und Stop jedes untergeordnete Steuerelement auf und rufen die StartMarquee-Methode bzw. die StopMarquee-Methode zu jedem untergeordneten Element auf, das IMarqueeWidget implementiert.

Das Aussehen des MarqueeBorder-Steuerelements und des MarqueeText-Steuerelements hängt vom Layout ab, d. h., MarqueeControl überschreibt die OnLayout-Methode und ruft zu untergeordneten Steuerelementen dieses Typs PerformLayout auf.

Dies sind alle für das MarqueeControl erforderlichen Anpassungen. Die Laufzeitfeatures werden von dem MarqueeBorder-Steuerelement und dem MarqueeText-Steuerelement implementiert, und die Entwurfszeitfeatures werden von der MarqueeBorderDesigner-Klasse und der MarqueeControlRootDesigner-Klasse implementiert.

So implementieren Sie das benutzerdefinierte Steuerelement

  1. Öffnen Sie die MarqueeControl-Quelldatei im Code-Editor. Implementieren Sie die Start-Methode und die Stop-Methode.

    Public Sub Start()
        ' The MarqueeControl may contain any number of 
        ' controls that implement IMarqueeWidget, so 
        ' find each IMarqueeWidget child and call its
        ' StartMarquee method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StartMarquee()
            End If
        Next cntrl
    End Sub
    
    
    Public Sub [Stop]()
        ' The MarqueeControl may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StopMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StopMarquee()
            End If
        Next cntrl
    End Sub
    
    public void Start()
    {
        // The MarqueeControl may contain any number of 
        // controls that implement IMarqueeWidget, so 
        // find each IMarqueeWidget child and call its
        // StartMarquee method.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StartMarquee();
            }
        }
    }
    
    public void Stop()
    {
        // The MarqueeControl may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StopMarquee
        // method.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StopMarquee();
            }
        }
    }
    
  2. Überschreiben der OnLayout-Methode.

    Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs)
        MyBase.OnLayout(levent)
    
        ' Repaint all IMarqueeWidget children if the layout 
        ' has changed.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                cntrl.PerformLayout()
            End If
        Next cntrl
    End Sub
    
    protected override void OnLayout(LayoutEventArgs levent)
    {
        base.OnLayout (levent);
    
        // Repaint all IMarqueeWidget children if the layout 
        // has changed.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                Control control = cntrl as Control; 
    
                control.PerformLayout();
            }
        }
    }
    

Erstellen eines untergeordneten Steuerelements für das benutzerdefinierte Steuerelement

Das MarqueeControl hostet zwei Arten von untergeordnetem Steuerelementen: das MarqueeBorder-Steuerelement und das MarqueeText-Steuerelement.

  • MarqueeBorder: Dieses Steuerelement zeichnet einen Rahmen mit "Lichtern" um seine Kanten. Die Lichter blinken nacheinander auf, als würden Sie um den Rahmen herum wandern. Die Geschwindigkeit, mit der die Lichter aufblinken, wird von einer Eigenschaft mit dem Namen UpdatePeriod gesteuert. Mehrere andere benutzerdefinierte Eigenschaften bestimmen weitere Aspekte der Darstellung des Steuerelements. Zwei Methoden, nämlich StartMarquee und StopMarquee, steuern, wann die Animation startet und anhält.

  • MarqueeText: Dieses Steuerelement zeichnet eine aufblinkende Zeichenfolge. Wie beim MarqueeBorder-Steuerelement wird die Geschwindigkeit, mit der der Text aufblinkt, von der UpdatePeriod-Eigenschaft gesteuert. Außerdem verfügt das MarqueeText-Steuerelement wie das MarqueeBorder-Steuerelement über die StartMarquee-Methode und die StopMarquee-Methode.

Zur Entwurfszeit ermöglicht der MarqueeControlRootDesigner, dass diese beiden Steuerelementtypen in jeder beliebigen Kombination einem MarqueeControl hinzugefügt werden.

Allgemeine Features der beiden Steuerelemente werden auf eine Schnittstelle mit dem Namen IMarqueeWidget aufgeteilt. Dies ermöglicht es dem MarqueeControl, Marquee-verwandte untergeordnete Steuerelemente zu ermitteln und sie besonders zu behandeln.

Um das periodische Animationsfeature zu implementieren, verwenden Sie BackgroundWorker-Objekte vom System.ComponentModel-Namespace. Sie könnten Timer-Objekte verwenden, aber bei mehreren IMarqueeWidget-Objekten ist der einzelne UI-Thread möglicherweise nicht in der Lage, die Animation zu verarbeiten.

So erstellen Sie ein untergeordnetes Steuerelement für das benutzerdefinierte Steuerelement

  1. Fügen Sie dem MarqueeControlLibrary-Projekt ein neues Klassenelement hinzu. Weisen Sie der neuen Quelldatei den Basisnamen "IMarqueeWidget" zu.

  2. Öffnen Sie die Quelldatei IMarqueeWidget im Code-Editor, und ändern Sie die Deklaration von class in interface:

    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
    
  3. Fügen Sie folgenden Code zur IMarqueeWidget-Schnittstelle hinzu, um zwei Methoden und eine Eigenschaft verfügbar zu machen, mit denen die Animation bearbeitet wird:

    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
       ' This method starts the animation. If the control can 
       ' contain other classes that implement IMarqueeWidget as
       ' children, the control should call StartMarquee on all
       ' its IMarqueeWidget child controls.
       Sub StartMarquee()
    
       ' This method stops the animation. If the control can 
       ' contain other classes that implement IMarqueeWidget as
       ' children, the control should call StopMarquee on all
       ' its IMarqueeWidget child controls.
       Sub StopMarquee()
    
       ' This method specifies the refresh rate for the animation,
       ' in milliseconds.
       Property UpdatePeriod() As Integer
    
    End Interface
    
    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
        // This method starts the animation. If the control can 
        // contain other classes that implement IMarqueeWidget as
        // children, the control should call StartMarquee on all
        // its IMarqueeWidget child controls.
        void StartMarquee();
    
        // This method stops the animation. If the control can 
        // contain other classes that implement IMarqueeWidget as
        // children, the control should call StopMarquee on all
        // its IMarqueeWidget child controls.
        void StopMarquee();
    
        // This method specifies the refresh rate for the animation,
        // in milliseconds.
        int UpdatePeriod
        {
            get;
            set;
        }
    }
    
  4. Fügen Sie dem MarqueeControlLibrary-Projekt ein neues Benutzerdefiniertes Steuerelement-Element hinzu. Weisen Sie der neuen Quelldatei den Basisnamen "MarqueeText" zu.

  5. Ziehen Sie eine BackgroundWorker-Komponente aus der Toolbox auf das MarqueeText-Steuerelement. Diese Komponente ermöglicht es dem MarqueeText-Steuerelement, sich asynchron zu aktualisieren.

  6. Legen Sie im Eigenschaftenfenster die WorkerReportsProgess-Eigenschaft und die WorkerSupportsCancellation-Eigenschaft der BackgroundWorker-Komponente auf true fest. Diese Einstellungen ermöglichen es der BackgroundWorker-Komponente, das ProgressChanged-Ereignis periodisch auszulösen und asynchrone Aktualisierungen abzubrechen. Weitere Informationen finden Sie unter BackgroundWorker-Komponente.

  7. Öffnen Sie die MarqueeText-Quelldatei im Code-Editor. Importieren Sie am Anfang der Datei folgende Namespaces:

    Imports System
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  8. Ändern Sie die Deklaration von MarqueeText, um von Label zu erben und die IMarqueeWidget-Schnittstelle zu implementieren:

    <ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeText
        Inherits Label
        Implements IMarqueeWidget
    
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    public partial class MarqueeText : Label, IMarqueeWidget
    {
    
  9. Deklarieren Sie die Instanzvariablen, die den verfügbar gemachten Eigenschaften entsprechen, und initialisieren Sie sie im Konstruktor. Das isLit-Feld legt fest, ob der Text in der von der LightColor-Eigenschaft vorgegebenen Farbe gezeichnet wird.

    ' When isLit is true, the text is painted in the light color;
    ' When isLit is false, the text is painted in the dark color.
    ' This value changes whenever the BackgroundWorker component
    ' raises the ProgressChanged event.
    Private isLit As Boolean = True
    
    ' These fields back the public properties.
    Private updatePeriodValue As Integer = 50
    Private lightColorValue As Color
    Private darkColorValue As Color
    
    ' These brushes are used to paint the light and dark
    ' colors of the text.
    Private lightBrush As Brush
    Private darkBrush As Brush
    
    ' This component updates the control asynchronously.
    Private WithEvents backgroundWorker1 As BackgroundWorker
    
    
    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()
    
        ' Initialize light and dark colors 
        ' to the control's default values.
        Me.lightColorValue = Me.ForeColor
        Me.darkColorValue = Me.BackColor
        Me.lightBrush = New SolidBrush(Me.lightColorValue)
        Me.darkBrush = New SolidBrush(Me.darkColorValue)
    End Sub 'New
    
    // When isLit is true, the text is painted in the light color;
    // When isLit is false, the text is painted in the dark color.
    // This value changes whenever the BackgroundWorker component
    // raises the ProgressChanged event.
    private bool isLit = true;
    
    // These fields back the public properties.
    private int updatePeriodValue = 50;
    private Color lightColorValue;
    private Color darkColorValue;
    
    // These brushes are used to paint the light and dark
    // colors of the text.
    private Brush lightBrush;
    private Brush darkBrush;
    
    // This component updates the control asynchronously.
    private BackgroundWorker backgroundWorker1;
    
    public MarqueeText()
    {
        // This call is required by the Windows.Forms Form Designer.
        InitializeComponent();
    
        // Initialize light and dark colors 
        // to the control's default values.
        this.lightColorValue = this.ForeColor;
        this.darkColorValue = this.BackColor;
        this.lightBrush = new SolidBrush(this.lightColorValue);
        this.darkBrush = new SolidBrush(this.darkColorValue);
    }
    
  10. Implementieren Sie die IMarqueeWidget-Schnittstelle.

    Die StartMarquee-Methode und die StopMarquee-Methode rufen die RunWorkerAsync-Methode und die CancelAsync-Methode der BackgroundWorker-Komponente auf, um die Animation zu starten und zu stoppen.

    Das Category-Attribut und das Browsable-Attribut werden auf die UpdatePeriod-Eigenschaft angewendet, sodass diese in einem benutzerdefinierten Abschnitt des Eigenschaftenfensters mit dem Namen "Marquee" angezeigt wird.

    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' Start the updating thread and pass it the UpdatePeriod.
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub
    
    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' Stop the updating thread.
        Me.backgroundWorker1.CancelAsync()
    End Sub
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod
    
        Get
            Return Me.updatePeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.updatePeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0")
            End If
        End Set
    
    End Property
    
    public virtual void StartMarquee()
    {
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
  11. Implementieren Sie die Eigenschaftenaccessoren. Sie machen Clients zwei Eigenschaften verfügbar: LightColor und DarkColor. Das Category-Attribut und das Browsable-Attribut werden auf diese Eigenschaften angewendet, sodass die Eigenschaften in einem benutzerdefinierten Abschnitt des Eigenschaftenfensters mit dem Namen "Marquee" angezeigt werden.

    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color
    
        Get
            Return Me.lightColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If
        End Set
    
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color
    
        Get
            Return Me.darkColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If
        End Set
    
    End Property
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color LightColor
    {
        get
        {
            return this.lightColorValue;
        }
        set
        {
            // The LightColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.lightColorValue.ToArgb() != value.ToArgb())
            {
                this.lightColorValue = value;
                this.lightBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color DarkColor
    {
        get
        {
            return this.darkColorValue;
        }
        set
        {
            // The DarkColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.darkColorValue.ToArgb() != value.ToArgb())
            {
                this.darkColorValue = value;
                this.darkBrush = new SolidBrush(value);
            }
        }
    }
    
  12. Implementieren Sie die Handler für das DoWork-Ereignis und das ProgressChanged-Ereignis der BackgroundWorker-Komponente.

    Der DoWork-Ereignishandler ruht für die durch UpdatePeriod festgelegte Anzahl an Millisekunden und löst anschließend das ProgressChanged-Ereignis aus, bis der Code die Animation durch Aufrufen von CancelAsync beendet.

    Der ProgressChanged-Ereignishandler schaltet den Zustand des Textes von hell in dunkel und umgekehrt um, sodass der Anschein entsteht, dass dieser aufblinkt.

    ' This method is called in the worker thread's context, 
    ' so it must not make any calls into the MarqueeText control.
    ' Instead, it communicates to the control using the 
    ' ProgressChanged event.
    '
    ' The only work done in this event handler is
    ' to sleep for the number of milliseconds specified 
    ' by UpdatePeriod, then raise the ProgressChanged event.
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
        ' This event handler will run until the client cancels
        ' the background task by calling CancelAsync.
        While Not worker.CancellationPending
            ' The Argument property of the DoWorkEventArgs
            ' object holds the value of UpdatePeriod, which 
            ' was passed as the argument to the RunWorkerAsync
            ' method. 
            Thread.Sleep(Fix(e.Argument))
    
            ' The DoWork eventhandler does not actually report
            ' progress; the ReportProgress event is used to 
            ' periodically alert the control to update its state.
            worker.ReportProgress(0)
        End While
    End Sub
    
    
    ' The ProgressChanged event is raised by the DoWork method.
    ' This event handler does work that is internal to the
    ' control. In this case, the text is toggled between its
    ' light and dark state, and the control is told to 
    ' repaint itself.
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
        Me.isLit = Not Me.isLit
        Me.Refresh()
    End Sub
    
    // This method is called in the worker thread's context, 
    // so it must not make any calls into the MarqueeText control.
    // Instead, it communicates to the control using the 
    // ProgressChanged event.
    //
    // The only work done in this event handler is
    // to sleep for the number of milliseconds specified 
    // by UpdatePeriod, then raise the ProgressChanged event.
    private void backgroundWorker1_DoWork(
        object sender,
        System.ComponentModel.DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
    
        // This event handler will run until the client cancels
        // the background task by calling CancelAsync.
        while (!worker.CancellationPending)
        {
            // The Argument property of the DoWorkEventArgs
            // object holds the value of UpdatePeriod, which 
            // was passed as the argument to the RunWorkerAsync
            // method. 
            Thread.Sleep((int)e.Argument);
    
            // The DoWork eventhandler does not actually report
            // progress; the ReportProgress event is used to 
            // periodically alert the control to update its state.
            worker.ReportProgress(0);
        }
    }
    
    // The ProgressChanged event is raised by the DoWork method.
    // This event handler does work that is internal to the
    // control. In this case, the text is toggled between its
    // light and dark state, and the control is told to 
    // repaint itself.
    private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        this.isLit = !this.isLit;
        this.Refresh();
    }
    
  13. Überschreiben Sie die OnPaint-Methode, um die Animation zu aktivieren.

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        ' The text is painted in the light or dark color,
        ' depending on the current value of isLit.
        Me.ForeColor = IIf(Me.isLit, Me.lightColorValue, Me.darkColorValue)
    
        MyBase.OnPaint(e)
    End Sub
    
    protected override void OnPaint(PaintEventArgs e)
    {
        // The text is painted in the light or dark color,
        // depending on the current value of isLit.
        this.ForeColor =
            this.isLit ? this.lightColorValue : this.darkColorValue;
    
        base.OnPaint(e);
    }
    
  14. Drücken Sie F6, um die Projektmappe zu erstellen.

Erstellen des untergeordneten MarqueeBorder-Steuerelements

Das MarqueeBorder-Steuerelement ist etwas raffinierter als das MarqueeText-Steuerelement. Es verfügt über mehr Eigenschaften, und die Animation in der OnPaint-Methode ist komplizierter. Im Prinzip ist es mit dem MarqueeText-Steuerelement vergleichbar.

Da das MarqueeBorder-Steuerelement über untergeordnete Steuerelemente verfügen kann, muss es Layout-Ereignisse berücksichtigen.

So erstellen Sie das MarqueeBorder-Steuerelement

  1. Fügen Sie dem MarqueeControlLibrary-Projekt ein neues Benutzerdefiniertes Steuerelement-Element hinzu. Weisen Sie der neuen Quelldatei den Basisnamen "MarqueeBorder" zu.

  2. Ziehen Sie eine BackgroundWorker-Komponente aus der Toolbox auf das MarqueeBorder-Steuerelement. Diese Komponente ermöglicht es dem MarqueeBorder-Steuerelement, sich asynchron zu aktualisieren.

  3. Legen Sie im Eigenschaftenfenster die WorkerReportsProgess-Eigenschaft und die WorkerSupportsCancellation-Eigenschaft der BackgroundWorker-Komponente auf true fest. Diese Einstellungen ermöglichen es der BackgroundWorker-Komponente, das ProgressChanged-Ereignis periodisch auszulösen und asynchrone Aktualisierungen abzubrechen. Weitere Informationen finden Sie unter BackgroundWorker-Komponente.

  4. Klicken Sie im Fenster Eigenschaften auf die Schaltfläche Ereignisse. Fügen Sie Handler für die DoWork-Ereignisse und die ProgressChanged-Ereignisse an.

  5. Öffnen Sie die MarqueeBorder-Quelldatei im Code-Editor. Importieren Sie am Anfang der Datei folgende Namespaces:

    Imports System
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Drawing.Design
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  6. Ändern Sie die Deklaration von MarqueeBorder, um von Panel zu erben und die IMarqueeWidget-Schnittstelle zu implementieren.

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _
    ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeBorder
        Inherits Panel
        Implements IMarqueeWidget
    
    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {
    
  7. Deklarieren Sie zwei Enumerationen, um den Zustand des MarqueeBorder-Steuerelements zu verwalten: MarqueeSpinDirection bestimmt die Richtung, in die die Lichter um den Rand herum wandern, und MarqueeLightShape bestimmt die Form der Lichter (quadratisch oder rund). Platzieren Sie diese Deklarationen vor der MarqueeBorder-Klassendeklaration.

    ' This defines the possible values for the MarqueeBorder
    ' control's SpinDirection property.
    Public Enum MarqueeSpinDirection
       CW
       CCW
    End Enum
    
    ' This defines the possible values for the MarqueeBorder
    ' control's LightShape property.
    Public Enum MarqueeLightShape
        Square
        Circle
    End Enum
    
    // This defines the possible values for the MarqueeBorder
    // control's SpinDirection property.
    public enum MarqueeSpinDirection
    {
        CW,
        CCW
    }
    
    // This defines the possible values for the MarqueeBorder
    // control's LightShape property.
    public enum MarqueeLightShape
    {
        Square,
        Circle
    }
    
  8. Deklarieren Sie die Instanzvariablen, die den verfügbar gemachten Eigenschaften entsprechen, und initialisieren Sie sie im Konstruktor.

    Public Shared MaxLightSize As Integer = 10
    
    ' These fields back the public properties.
    Private updatePeriodValue As Integer = 50
    Private lightSizeValue As Integer = 5
    Private lightPeriodValue As Integer = 3
    Private lightSpacingValue As Integer = 1
    Private lightColorValue As Color
    Private darkColorValue As Color
    Private spinDirectionValue As MarqueeSpinDirection = MarqueeSpinDirection.CW
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
    ' These brushes are used to paint the light and dark
    ' colors of the marquee lights.
    Private lightBrush As Brush
    Private darkBrush As Brush
    
    ' This field tracks the progress of the "first" light as it
    ' "travels" around the marquee border.
    Private currentOffset As Integer = 0
    
    ' This component updates the control asynchronously.
    Private WithEvents backgroundWorker1 As System.ComponentModel.BackgroundWorker
    
    
    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()
    
        ' Initialize light and dark colors 
        ' to the control's default values.
        Me.lightColorValue = Me.ForeColor
        Me.darkColorValue = Me.BackColor
        Me.lightBrush = New SolidBrush(Me.lightColorValue)
        Me.darkBrush = New SolidBrush(Me.darkColorValue)
    
        ' The MarqueeBorder control manages its own padding,
        ' because it requires that any contained controls do
        ' not overlap any of the marquee lights.
        Dim pad As Integer = 2 * (Me.lightSizeValue + Me.lightSpacingValue)
        Me.Padding = New Padding(pad, pad, pad, pad)
    
        SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
    End Sub
    
    public static int MaxLightSize = 10;
    
    // These fields back the public properties.
    private int updatePeriodValue = 50;
    private int lightSizeValue = 5;
    private int lightPeriodValue = 3;
    private int lightSpacingValue = 1;
    private Color lightColorValue;
    private Color darkColorValue;
    private MarqueeSpinDirection spinDirectionValue = MarqueeSpinDirection.CW;
    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    // These brushes are used to paint the light and dark
    // colors of the marquee lights.
    private Brush lightBrush;
    private Brush darkBrush;
    
    // This field tracks the progress of the "first" light as it
    // "travels" around the marquee border.
    private int currentOffset = 0;
    
    // This component updates the control asynchronously.
    private System.ComponentModel.BackgroundWorker backgroundWorker1;
    
    public MarqueeBorder()
    {
        // This call is required by the Windows.Forms Form Designer.
        InitializeComponent();
    
        // Initialize light and dark colors 
        // to the control's default values.
        this.lightColorValue = this.ForeColor;
        this.darkColorValue = this.BackColor;
        this.lightBrush = new SolidBrush(this.lightColorValue);
        this.darkBrush = new SolidBrush(this.darkColorValue);
    
        // The MarqueeBorder control manages its own padding,
        // because it requires that any contained controls do
        // not overlap any of the marquee lights.
        int pad = 2 * (this.lightSizeValue + this.lightSpacingValue);
        this.Padding = new Padding(pad, pad, pad, pad);
    
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    }
    
  9. Implementieren Sie die IMarqueeWidget-Schnittstelle.

    Die StartMarquee-Methode und die StopMarquee-Methode rufen die RunWorkerAsync-Methode und die CancelAsync-Methode der BackgroundWorker-Komponente auf, um die Animation zu starten und zu stoppen.

    Da das MarqueeBorder-Steuerelement untergeordnete Steuerelemente enthalten kann, listet die StartMarquee-Methode alle untergeordneten Steuerelemente auf und ruft StartMarquee für diejenigen auf, die IMarqueeWidget implementieren. Die StopMarquee-Methode verfügt über eine ähnliche Implementierung.

    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' The MarqueeBorder control may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StartMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StartMarquee()
            End If
        Next cntrl
    
        ' Start the updating thread and pass it the UpdatePeriod.
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub
    
    
    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' The MarqueeBorder control may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StopMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StopMarquee()
            End If
        Next cntrl
    
        ' Stop the updating thread.
        Me.backgroundWorker1.CancelAsync()
    End Sub
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Overridable Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod
    
        Get
            Return Me.updatePeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.updatePeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("UpdatePeriod", _
                "must be > 0")
            End If
        End Set
    
    End Property
    
    public virtual void StartMarquee()
    {
        // The MarqueeBorder control may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StartMarquee
        // method.
        foreach (Control cntrl in this.Controls)
        {
            if (cntrl is IMarqueeWidget)
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StartMarquee();
            }
        }
    
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // The MarqueeBorder control may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StopMarquee
        // method.
        foreach (Control cntrl in this.Controls)
        {
            if (cntrl is IMarqueeWidget)
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StopMarquee();
            }
        }
    
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public virtual int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
  10. Implementieren Sie die Eigenschaftenaccessoren. Das MarqueeBorder-Steuerelement verfügt über mehrere Eigenschaften, die sein Aussehen steuern.

    <Category("Marquee"), Browsable(True)> _
    Public Property LightSize() As Integer
        Get
            Return Me.lightSizeValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 AndAlso Value <= MaxLightSize Then
                Me.lightSizeValue = Value
                Me.DockPadding.All = 2 * Value
            Else
                Throw New ArgumentOutOfRangeException("LightSize", _
                "must be > 0 and < MaxLightSize")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightPeriod() As Integer
        Get
            Return Me.lightPeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.lightPeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("LightPeriod", _
                "must be > 0 ")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color
        Get
            Return Me.lightColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color
        Get
            Return Me.darkColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightSpacing() As Integer
        Get
            Return Me.lightSpacingValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value >= 0 Then
                Me.lightSpacingValue = Value
            Else
                Throw New ArgumentOutOfRangeException("LightSpacing", _
                "must be >= 0")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True), _
    EditorAttribute(GetType(LightShapeEditor), _
    GetType(System.Drawing.Design.UITypeEditor))> _
    Public Property LightShape() As MarqueeLightShape
    
        Get
            Return Me.lightShapeValue
        End Get
    
        Set(ByVal Value As MarqueeLightShape)
            Me.lightShapeValue = Value
        End Set
    
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property SpinDirection() As MarqueeSpinDirection
    
        Get
            Return Me.spinDirectionValue
        End Get
    
        Set(ByVal Value As MarqueeSpinDirection)
            Me.spinDirectionValue = Value
        End Set
    
    End Property
    
    [Category("Marquee")]
    [Browsable(true)]
    public int LightSize
    {
        get
        {
            return this.lightSizeValue;
        }
    
        set
        {
            if (value > 0 && value <= MaxLightSize)
            {
                this.lightSizeValue = value;
                this.DockPadding.All = 2 * value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightSize", "must be > 0 and < MaxLightSize");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int LightPeriod
    {
        get
        {
            return this.lightPeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.lightPeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightPeriod", "must be > 0 ");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color LightColor
    {
        get
        {
            return this.lightColorValue;
        }
    
        set
        {
            // The LightColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.lightColorValue.ToArgb() != value.ToArgb())
            {
                this.lightColorValue = value;
                this.lightBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color DarkColor
    {
        get
        {
            return this.darkColorValue;
        }
    
        set
        {
            // The DarkColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.darkColorValue.ToArgb() != value.ToArgb())
            {
                this.darkColorValue = value;
                this.darkBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int LightSpacing
    {
        get
        {
            return this.lightSpacingValue;
        }
    
        set
        {
            if (value >= 0)
            {
                this.lightSpacingValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightSpacing", "must be >= 0");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    [EditorAttribute(typeof(LightShapeEditor), 
         typeof(System.Drawing.Design.UITypeEditor))]
    public MarqueeLightShape LightShape
    {
        get
        {
            return this.lightShapeValue;
        }
    
        set
        {
            this.lightShapeValue = value;
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public MarqueeSpinDirection SpinDirection
    {
        get
        {
            return this.spinDirectionValue;
        }
    
        set
        {
            this.spinDirectionValue = value;
        }
    }
    
  11. Implementieren Sie die Handler für das DoWork-Ereignis und das ProgressChanged-Ereignis der BackgroundWorker-Komponente.

    Der DoWork-Ereignishandler ruht für die durch UpdatePeriod festgelegte Anzahl an Millisekunden und löst anschließend das ProgressChanged-Ereignis aus, bis der Code die Animation durch Aufrufen von CancelAsync beendet.

    Der ProgressChanged-Ereignishandler erhöht die Position des "Basislichts", durch die der Hell-/Dunkelzustand der anderen Lichter bestimmt wird, und ruft die Refresh-Methode auf, damit 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 Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
        ' This event handler will run until the client cancels
        ' the background task by calling CancelAsync.
        While Not worker.CancellationPending
            ' The Argument property of the DoWorkEventArgs
            ' object holds the value of UpdatePeriod, which 
            ' was passed as the argument to the RunWorkerAsync
            ' method. 
            Thread.Sleep(Fix(e.Argument))
    
            ' The DoWork eventhandler does not actually report
            ' progress; the ReportProgress event is used to 
            ' periodically alert the control to update its state.
            worker.ReportProgress(0)
        End While
    End Sub
    
    
    ' The ProgressChanged event is raised by the DoWork method.
    ' This event handler does work that is internal to the
    ' control. In this case, the currentOffset is incremented,
    ' and the control is told to repaint itself.
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
        Me.currentOffset += 1
        Me.Refresh()
    End Sub
    
    // This method is called in the worker thread's context, 
    // so it must not make any calls into the MarqueeBorder
    // control. Instead, it communicates to the control using 
    // the ProgressChanged event.
    //
    // The only work done in this event handler is
    // to sleep for the number of milliseconds specified 
    // by UpdatePeriod, then raise the ProgressChanged event.
    private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
    
        // This event handler will run until the client cancels
        // the background task by calling CancelAsync.
        while (!worker.CancellationPending)
        {
            // The Argument property of the DoWorkEventArgs
            // object holds the value of UpdatePeriod, which 
            // was passed as the argument to the RunWorkerAsync
            // method. 
            Thread.Sleep((int)e.Argument);
    
            // The DoWork eventhandler does not actually report
            // progress; the ReportProgress event is used to 
            // periodically alert the control to update its state.
            worker.ReportProgress(0);
        }
    }
    
    // The ProgressChanged event is raised by the DoWork method.
    // This event handler does work that is internal to the
    // control. In this case, the currentOffset is incremented,
    // and the control is told to repaint itself.
    private void backgroundWorker1_ProgressChanged(
        object sender,
        System.ComponentModel.ProgressChangedEventArgs e)
    {
        this.currentOffset++;
        this.Refresh();
    }
    
  12. Implementieren Sie die Hilfemethoden IsLit und DrawLight.

    Die IsLit-Methode bestimmt die Farbe eines Lichts an einer bestimmten Position. Helle Lichter werden in der durch die LightColor-Eigenschaft vorgegebenen Farbe gezeichnet, während dunkle Farben in der durch die DarkColor-Eigenschaft vorgegebenen Farbe gezeichnet werden.

    Die DrawLight-Methode 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 Overridable Function IsLit(ByVal lightIndex As Integer) As Boolean
        Dim directionFactor As Integer = _
        IIf(Me.spinDirectionValue = MarqueeSpinDirection.CW, -1, 1)
    
        Return (lightIndex + directionFactor * Me.currentOffset) Mod Me.lightPeriodValue = 0
    End Function
    
    
    Protected Overridable Sub DrawLight( _
    ByVal g As Graphics, _
    ByVal brush As Brush, _
    ByVal xPos As Integer, _
    ByVal yPos As Integer)
    
        Select Case Me.lightShapeValue
            Case MarqueeLightShape.Square
                g.FillRectangle( _
                brush, _
                xPos, _
                yPos, _
                Me.lightSizeValue, _
                Me.lightSizeValue)
                Exit Select
            Case MarqueeLightShape.Circle
                g.FillEllipse( _
                brush, _
                xPos, _
                yPos, _
                Me.lightSizeValue, _
                Me.lightSizeValue)
                Exit Select
            Case Else
                Trace.Assert(False, "Unknown value for light shape.")
                Exit Select
        End Select
    
    End Sub
    
    // This method determines if the marquee light at lightIndex
    // should be lit. The currentOffset field specifies where
    // the "first" light is located, and the "position" of the
    // light given by lightIndex is computed relative to this 
    // offset. If this position modulo lightPeriodValue is zero,
    // the light is considered to be on, and it will be painted
    // with the control's lightBrush. 
    protected virtual bool IsLit(int lightIndex)
    {
        int directionFactor =
            (this.spinDirectionValue == MarqueeSpinDirection.CW ? -1 : 1);
    
        return (
            (lightIndex + directionFactor * this.currentOffset) % this.lightPeriodValue == 0
            );
    }
    
    protected virtual void DrawLight(
        Graphics g,
        Brush brush,
        int xPos,
        int yPos)
    {
        switch (this.lightShapeValue)
        {
            case MarqueeLightShape.Square:
                {
                    g.FillRectangle(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue);
                    break;
                }
            case MarqueeLightShape.Circle:
                {
                    g.FillEllipse(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue);
                    break;
                }
            default:
                {
                    Trace.Assert(false, "Unknown value for light shape.");
                    break;
                }
        }
    }
    
  13. Überschreiben Sie die OnLayout-Methode und die OnPaint-Methode.

    Die OnPaint-Methode zeichnet die Lichter an den Rändern des MarqueeBorder-Steuerelements.

    Da die OnPaint-Methode von den Maßen des MarqueeBorder-Steuerelements abhängt, müssen Sie sie aufrufen, sobald sich das Layout ändert. Hierfür überschreiben Sie OnLayout und rufen Refresh auf.

    Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs)
        MyBase.OnLayout(levent)
    
        ' Repaint when the layout has changed.
        Me.Refresh()
    End Sub
    
    
    ' This method paints the lights around the border of the 
    ' control. It paints the top row first, followed by the
    ' right side, the bottom row, and the left side. The color
    ' of each light is determined by the IsLit method and
    ' depends on the light's position relative to the value
    ' of currentOffset.
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim g As Graphics = e.Graphics
        g.Clear(Me.BackColor)
    
        MyBase.OnPaint(e)
    
        ' If the control is large enough, draw some lights.
        If Me.Width > MaxLightSize AndAlso Me.Height > MaxLightSize Then
            ' The position of the next light will be incremented 
            ' by this value, which is equal to the sum of the
            ' light size and the space between two lights.
            Dim increment As Integer = _
            Me.lightSizeValue + Me.lightSpacingValue
    
            ' Compute the number of lights to be drawn along the
            ' horizontal edges of the control.
            Dim horizontalLights As Integer = _
            (Me.Width - increment) / increment
    
            ' Compute the number of lights to be drawn along the
            ' vertical edges of the control.
            Dim verticalLights As Integer = _
            (Me.Height - increment) / increment
    
            ' These local variables will be used to position and
            ' paint each light.
            Dim xPos As Integer = 0
            Dim yPos As Integer = 0
            Dim lightCounter As Integer = 0
            Dim brush As Brush
    
            ' Draw the top row of lights.
            Dim i As Integer
            For i = 0 To horizontalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                xPos += increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the right edge of the control.
            xPos = Me.Width - Me.lightSizeValue
    
            ' Draw the right column of lights.
            'Dim i As Integer
            For i = 0 To verticalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                yPos += increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the bottom edge of the control.
            yPos = Me.Height - Me.lightSizeValue
    
            ' Draw the bottom row of lights.
            'Dim i As Integer
            For i = 0 To horizontalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                xPos -= increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the left edge of the control.
            xPos = 0
    
            ' Draw the left column of lights.
            'Dim i As Integer
            For i = 0 To verticalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                yPos -= increment
                lightCounter += 1
            Next i
        End If
    End Sub
    
    protected override void OnLayout(LayoutEventArgs levent)
    {
        base.OnLayout(levent);
    
        // Repaint when the layout has changed.
        this.Refresh();
    }
    
    // This method paints the lights around the border of the 
    // control. It paints the top row first, followed by the
    // right side, the bottom row, and the left side. The color
    // of each light is determined by the IsLit method and
    // depends on the light's position relative to the value
    // of currentOffset.
    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        g.Clear(this.BackColor);
    
        base.OnPaint(e);
    
        // If the control is large enough, draw some lights.
        if (this.Width > MaxLightSize &&
            this.Height > MaxLightSize)
        {
            // The position of the next light will be incremented 
            // by this value, which is equal to the sum of the
            // light size and the space between two lights.
            int increment =
                this.lightSizeValue + this.lightSpacingValue;
    
            // Compute the number of lights to be drawn along the
            // horizontal edges of the control.
            int horizontalLights =
                (this.Width - increment) / increment;
    
            // Compute the number of lights to be drawn along the
            // vertical edges of the control.
            int verticalLights =
                (this.Height - increment) / increment;
    
            // These local variables will be used to position and
            // paint each light.
            int xPos = 0;
            int yPos = 0;
            int lightCounter = 0;
            Brush brush;
    
            // Draw the top row of lights.
            for (int i = 0; i < horizontalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                xPos += increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the right edge of the control.
            xPos = this.Width - this.lightSizeValue;
    
            // Draw the right column of lights.
            for (int i = 0; i < verticalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                yPos += increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the bottom edge of the control.
            yPos = this.Height - this.lightSizeValue;
    
            // Draw the bottom row of lights.
            for (int i = 0; i < horizontalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                xPos -= increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the left edge of the control.
            xPos = 0;
    
            // Draw the left column of lights.
            for (int i = 0; i < verticalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                yPos -= increment;
                lightCounter++;
            }
        }
    }
    

Erstellen eines benutzerdefinierten Designers, um Eigenschaften zu filtern und Shadowing auszuführen

Die MarqueeControlRootDesigner-Klasse stellt die Implementierung für den Stamm-Designer bereit. Neben diesem Designer, der auf das MarqueeControl angewendet wird, benötigen Sie einen benutzerdefinierten Designer, der speziell dem MarqueeBorder-Steuerelement zugeordnet ist. Dieser Designer stellt benutzerdefiniertes Verhalten bereit, das im Hinblick auf den benutzerdefinierten Stamm-Designer angemessen ist.

Insbesondere der MarqueeBorderDesigner filtert bestimmte Eigenschaften des MarqueeBorder-Steuerelements und führt für diese Shadowing aus, wodurch sich die Interaktion mit der Entwurfsumgebung ändert.

Unter Shadowing versteht man das Abfangen von Aufrufen an den Eigenschaftenaccessor einer Komponente. Es ermöglicht einem Designer, den vom Benutzer festgelegten Wert zu verfolgen und optional diesen Wert an die Komponente zu übergeben, die entworfen wird.

In diesem Beispiel wird für die Visible-Eigenschaft und die Enabled-Eigenschaft vom MarqueeBorderDesigner ein Shadowing ausgeführt. Dadurch wird der Benutzer daran gehindert, das MarqueeBorder-Steuerelement zur Entwurfszeit unsichtbar zu machen oder zu deaktivieren.

Designer können zudem Eigenschaften hinzufügen und entfernen. In diesem Beispiel wird die Padding-Eigenschaft zur Entwurfszeit entfernt, da das MarqueeBorder-Steuerelement den Abstand programmgesteuert anhand der Größe der Lichter festlegt, die von der LightSize-Eigenschaft vorgegeben ist.

Die Basisklasse für MarqueeBorderDesigner ist ComponentDesigner. Diese verfügt über Methoden, die die Attribute, Eigenschaften und Ereignisse ändern können, die zur Entwurfszeit von einem Steuerelement verfügbar gemacht werden.

Wenn Sie die öffentliche Schnittstelle einer Komponente mithilfe dieser Methoden ändern, müssen Sie die folgenden Regeln beachten:

  • Verwenden Sie ausschließlich die PreFilter-Methoden, um Elemente hinzuzufügen oder zu entfernen.

  • Ändern Sie vorhandene Elemente ausschließlich in den PostFilter-Methoden.

  • Rufen Sie immer zuerst die Basisimplementierung in den PreFilter-Methoden auf.

  • Rufen Sie immer zuletzt die Basisimplementierung in den PostFilter-Methoden auf.

Wenn Sie diese Regeln beachten, wird sichergestellt, dass alle Designer in der Entwurfszeitumgebung über eine konsistente Ansicht der zu entwerfenden Komponenten verfügen.

Die ComponentDesigner-Klasse stellt ein Wörterbuch zur Verwaltung der Werte von Eigenschaften zur Verfügung, für die ein Shadowing ausgeführt wurde, sodass Sie keine bestimmten Instanzenvariablen erstellen müssen.

So erstellen Sie einen benutzerdefinierten Designer, um Eigenschaften zu filtern und Shadowing durchzuführen

  1. 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".

  2. Öffnen Sie die MarqueeBorderDesigner-Quelldatei im Code-Editor. Importieren Sie am Anfang der Datei folgende Namespaces:

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  3. Ändern Sie die Deklaration von MarqueeBorderDesigner, um von ParentControlDesigner zu erben.

    Da das MarqueeBorder-Steuerelement untergeordnete Steuerelemente enthalten kann, erbt MarqueeBorderDesigner von ParentControlDesigner, der die Interaktion zwischen übergeordneten und untergeordneten Elementen behandelt.

    Namespace MarqueeControlLibrary.Design
    
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
    namespace MarqueeControlLibrary.Design
    {
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
  4. Überschreiben Sie die Basisimplementierung von PreFilterProperties.

    Protected Overrides Sub PreFilterProperties( _
    ByVal properties As IDictionary)
    
        MyBase.PreFilterProperties(properties)
    
        If properties.Contains("Padding") Then
            properties.Remove("Padding")
        End If
    
        properties("Visible") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Visible"), PropertyDescriptor), _
        New Attribute(-1) {})
    
        properties("Enabled") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Enabled"), _
        PropertyDescriptor), _
        New Attribute(-1) {})
    
    End Sub
    
    protected override void PreFilterProperties(IDictionary properties)
    {
        base.PreFilterProperties(properties);
    
        if (properties.Contains("Padding"))
        {
            properties.Remove("Padding");
        }
    
        properties["Visible"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Visible"],
            new Attribute[0]);
    
        properties["Enabled"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Enabled"],
            new Attribute[0]);
    }
    
  5. Implementieren Sie die Enabled-Eigenschaft und die Visible-Eigenschaft. Diese Implementierungen führen für die Eigenschaften des Steuerelements ein Shadowing aus.

    Public Property Visible() As Boolean
        Get
            Return CBool(ShadowProperties("Visible"))
        End Get
        Set(ByVal Value As Boolean)
            Me.ShadowProperties("Visible") = Value
        End Set
    End Property
    
    
    Public Property Enabled() As Boolean
        Get
            Return CBool(ShadowProperties("Enabled"))
        End Get
        Set(ByVal Value As Boolean)
            Me.ShadowProperties("Enabled") = Value
        End Set
    End Property
    
    public bool Visible
    {
        get
        {
            return (bool)ShadowProperties["Visible"];
        }
        set
        {
            this.ShadowProperties["Visible"] = value;
        }
    }
    
    public bool Enabled
    {
        get
        {
            return (bool)ShadowProperties["Enabled"];
        }
        set
        {
            this.ShadowProperties["Enabled"] = value;
        }
    }
    

Behandeln von Komponentenänderungen

Die MarqueeControlRootDesigner-Klasse stellt die benutzerdefinierte Entwurfszeiterfahrung für die MarqueeControl-Instanzen bereit. Die meisten Entwurfszeitfunktionen werden von der DocumentDesigner-Klasse geerbt. Der Code implementiert zwei spezielle Anpassungen: das Behandeln von Komponentenänderungen und das Hinzufügen von Designerverben.

Da Benutzer die MarqueeControl-Instanzen entwerfen, verfolgt der Stamm-Designer Änderungen am MarqueeControl und an dessen untergeordneten Steuerelementen nach. Die Entwurfszeitumgebung bietet einen praktischen Dienst, den IComponentChangeService, um Änderungen am Komponentenzustand zu verfolgen.

Sie erhalten einen Verweis auf diesen Dienst, indem Sie die Umgebung mit der GetService-Methode abfragen. Wenn die Abfrage erfolgreich ist, kann der Designer einen Handler für das ComponentChanged-Ereignis zuordnen und alle Aufgaben ausführen, die zur Erhaltung eines konsistenten Zustands zur Entwurfszeit erforderlich sind.

Bei einer MarqueeControlRootDesigner-Klasse rufen Sie die Refresh-Methode für jedes IMarqueeWidget-Objekt auf, das im MarqueeControl enthalten ist. Dies bewirkt, dass das IMarqueeWidget-Objekt sich entsprechend neu zeichnet, wenn sich Eigenschaften wie die Size des übergeordneten Elements ändern.

So behandeln Sie Komponentenänderungen

  1. Öffnen Sie die Quelldatei MarqueeControlRootDesigner im Code-Editor, und überschreiben Sie die Initialize-Methode. Rufen Sie die Basisimplementierung von Initialize auf, und fragen Sie den IComponentChangeService ab.

    MyBase.Initialize(component)
    
    Dim cs As IComponentChangeService = _
    CType(GetService(GetType(IComponentChangeService)), _
    IComponentChangeService)
    
    If (cs IsNot Nothing) Then
        AddHandler cs.ComponentChanged, AddressOf OnComponentChanged
    End If
    
    base.Initialize(component);
    
    IComponentChangeService cs =
        GetService(typeof(IComponentChangeService)) 
        as IComponentChangeService;
    
    if (cs != null)
    {
        cs.ComponentChanged +=
            new ComponentChangedEventHandler(OnComponentChanged);
    }
    
  2. Implementieren Sie den OnComponentChanged-Ereignishandler. Testen Sie den Typ der sendenden Komponente. Wenn es sich um IMarqueeWidget handelt, rufen Sie die entsprechende Refresh-Methode auf.

    Private Sub OnComponentChanged( _
    ByVal sender As Object, _
    ByVal e As ComponentChangedEventArgs)
        If TypeOf e.Component Is IMarqueeWidget Then
            Me.Control.Refresh()
        End If
    End Sub
    
    private void OnComponentChanged(
        object sender,
        ComponentChangedEventArgs e)
    {
        if (e.Component is IMarqueeWidget)
        {
            this.Control.Refresh();
        }
    }
    

Hinzufügen von Designerverben zum 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 fügen den Designern zwei Designerverben hinzu: Test ausführen und Test beenden. Diese Verben ermöglichen es Ihnen, das Laufzeitverhalten des MarqueeControl zur Entwurfszeit anzuzeigen. Diese Verben werden dem MarqueeControlRootDesigner hinzugefügt.

Wenn Test ausführen aufgerufen wird, ruft der Verbereignishandler die StartMarquee-Methode für das MarqueeControl auf. Wenn Test beenden aufgerufen wird, ruft der Verbereignishandler die StopMarquee-Methode für das MarqueeControl auf. Die Implementierung der StartMarquee-Methode und der StopMarquee-Methode bewirkt, dass diese Methoden für enthaltene Steuerelemente aufgerufen werden, die IMarqueeWidget implementieren, sodass jedes enthaltene IMarqueeWidget-Steuerelement im Test berücksichtigt wird.

So fügen Sie Designerverben den benutzerdefinierten Designern hinzu

  1. Fügen Sie in der MarqueeControlRootDesigner-Klasse Ereignishandler mit den Namen OnVerbRunTest und OnVerbStopTest hinzu.

    Private Sub OnVerbRunTest( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
        c.Start()
    
    End Sub
    
    Private Sub OnVerbStopTest( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
        c.Stop()
    
    End Sub
    
    private void OnVerbRunTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Start();
    }
    
    private void OnVerbStopTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Stop();
    }
    
  2. Verbinden Sie diese Ereignishandler mit ihren entsprechenden Designerverben. MarqueeControlRootDesigner erbt eine DesignerVerbCollection von seiner Basisklasse. Sie erstellen zwei neue DesignerVerb-Objekte und fügen sie dieser Auflistung in der Initialize-Methode hinzu.

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

Erstellen eines benutzerdefinierten UI-Typ-Editors

Wenn Sie eine benutzerdefinierte Entwurfszeiterfahrung für Benutzer erstellen, ist es häufig angebracht, eine benutzerdefinierte Interaktion mit dem Eigenschaftenfenster zu erstellen. Hierzu können Sie einen UITypeEditor erstellen. Weitere Informationen finden Sie unter Gewusst wie: Erstellen eines UI-Typ-Editors.

Das MarqueeBorder-Steuerelement macht mehrere Eigenschaften im Eigenschaftenfenster verfügbar. Zwei von diesen Eigenschaften, MarqueeSpinDirection und MarqueeLightShape, werden durch Enumerationen dargestellt. Um die Verwendung eines UI-Typ-Editors zu veranschaulichen, verfügt die MarqueeLightShape-Eigenschaft über eine zugehörige UITypeEditor-Klasse.

So erstellen Sie einen benutzerdefinierten UI-Typ-Editor

  1. Öffnen Sie die MarqueeBorder-Quelldatei im Code-Editor.

  2. Deklarieren Sie in der Definition der MarqueeBorder-Klasse eine Klasse mit dem Namen LightShapeEditor, die sich von UITypeEditor ableitet.

    ' This class demonstrates the use of a custom UITypeEditor. 
    ' It allows the MarqueeBorder control's LightShape property
    ' to be changed at design time using a customized UI element
    ' that is invoked by the Properties window. The UI is provided
    ' by the LightShapeSelectionControl class.
    Friend Class LightShapeEditor
        Inherits UITypeEditor
    
    // This class demonstrates the use of a custom UITypeEditor. 
    // It allows the MarqueeBorder control's LightShape property
    // to be changed at design time using a customized UI element
    // that is invoked by the Properties window. The UI is provided
    // by the LightShapeSelectionControl class.
    internal class LightShapeEditor : UITypeEditor
    {
    
  3. Deklarieren Sie eine IWindowsFormsEditorService-Instanzvariable mit der Bezeichnung editorService.

    Private editorService As IWindowsFormsEditorService = Nothing
    
    private IWindowsFormsEditorService editorService = null;
    
  4. Überschreiben der GetEditStyle-Methode. Diese Implementierung gibt DropDown zurück und weist die Entwurfsumgebung an, wie der LightShapeEditor angezeigt werden soll.

    Public Overrides Function GetEditStyle( _
    ByVal context As System.ComponentModel.ITypeDescriptorContext) _
    As UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function
    
    public override UITypeEditorEditStyle GetEditStyle(
    System.ComponentModel.ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    
  5. Überschreiben der EditValue-Methode. Diese Implementierung fragt ein IWindowsFormsEditorService-Objekt aus der Entwurfsumgebung ab. Wenn die Abfrage erfolgreich ist, wird ein LightShapeSelectionControl erstellt. Die DropDownControl-Methode wird aufgerufen, um den LightShapeEditor zu starten. Der Rückgabewert von diesem Aufruf wird an die Entwurfsumgebung zurückgegeben.

    Public Overrides Function EditValue( _
    ByVal context As ITypeDescriptorContext, _
    ByVal provider As IServiceProvider, _
    ByVal value As Object) As Object
        If (provider IsNot Nothing) Then
            editorService = _
            CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
            IWindowsFormsEditorService)
        End If
    
        If (editorService IsNot Nothing) Then
            Dim selectionControl As _
            New LightShapeSelectionControl( _
            CType(value, MarqueeLightShape), _
            editorService)
    
            editorService.DropDownControl(selectionControl)
    
            value = selectionControl.LightShape
        End If
    
        Return value
    End Function
    
    public override object EditValue(
        ITypeDescriptorContext context,
        IServiceProvider provider,
        object value)
    {
        if (provider != null)
        {
            editorService =
                provider.GetService(
                typeof(IWindowsFormsEditorService))
                as IWindowsFormsEditorService;
        }
    
        if (editorService != null)
        {
            LightShapeSelectionControl selectionControl =
                new LightShapeSelectionControl(
                (MarqueeLightShape)value,
                editorService);
    
            editorService.DropDownControl(selectionControl);
    
            value = selectionControl.LightShape;
        }
    
        return value;
    }
    

Erstellen eines Ansichtssteuerelements für den benutzerdefinierten UI-Typ-Editor

  1. Die MarqueeLightShape-Eigenschaft unterstützt zwei Typen von Lichterformen: Square und Circle. Sie erstellen ein benutzerdefiniertes Steuerelement, das ausschließlich zur Anzeige dieser Werte im Eigenschaftenfenster dient. Dieses benutzerdefinierte Steuerelement wird vom UITypeEditor zur Interaktion mit dem Eigenschaftenfenster verwendet.

So erstellen Sie ein Ansichtssteuerelement für den benutzerdefinierten UI-Typ-Editor

  1. Fügen Sie dem MarqueeControlLibrary-Projekt ein neues UserControl-Element hinzu. Weisen Sie der neuen Quelldatei den Basisnamen "LightShapeSelectionControl" zu.

  2. Ziehen Sie zwei Panel-Steuerelemente aus der Toolbox auf das LightShapeSelectionControl. Nennen Sie sie squarePanel und circlePanel. Ordnen Sie sie nebeneinander an. Legen Sie die Size-Eigenschaft beider Panel-Steuerelemente auf (60, 60) fest. Legen Sie die Location-Eigenschaft des squarePanel-Steuerelements auf (8, 10) fest. Legen Sie die Location-Eigenschaft des circlePanel-Steuerelements auf (80, 10) fest. Legen Sie zuletzt die Size-Eigenschaft des LightShapeSelectionControl auf (150, 80) fest.

  3. Öffnen Sie die LightShapeSelectionControl-Quelldatei 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;
Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;

Testen des benutzerdefinierten Steuerelements im Designer

Sie können nun das MarqueeControlLibrary-Projekt erstellen. Testen Sie die Implementierung, indem Sie ein Steuerelement erstellen, das von der MarqueeControl-Klasse erbt, und dieses auf einem Formular verwenden.

So erstellen Sie eine benutzerdefinierte MarqueeControl-Implementierung

  1. Öffnen Sie DemoMarqueeControl im Windows Forms-Designer. Hierdurch wird eine Instanz des DemoMarqueeControl-Typs erstellt und in einer Instanz des MarqueeControlRootDesigner-Typs angezeigt.

  2. Öffnen Sie in der Toolbox die Registerkarte MarqueeControlLibrary-Komponenten. Das MarqueeBorder-Steuerelement und das MarqueeText-Steuerelement werden angezeigt und können ausgewählt werden.

  3. Ziehen Sie eine Instanz des MarqueeBorder-Steuerelements auf die DemoMarqueeControl-Entwurfsoberfläche. Docken Sie dieses MarqueeBorder-Steuerelement am übergeordneten Steuerelement an.

  4. Ziehen Sie eine Instanz des MarqueeText-Steuerelements auf die DemoMarqueeControl-Entwurfsoberfläche.

  5. Erstellen Sie die Projektmappe.

  6. Klicken Sie mit der rechten Maustaste auf DemoMarqueeControl. 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.

  7. Öffnen Sie Form1 in der Entwurfsansicht.

  8. Platzieren Sie zwei Button-Steuerelemente auf dem Formular. Nennen Sie die Steuerelemente startButton und stopButton, und ändern Sie die Text-Eigenschaftenwerte in Start bzw. Stop.

  9. Implementieren Sie Click-Ereignishandler für beide Button-Steuerelemente.

  10. Öffnen Sie in der Toolbox die Registerkarte MarqueeControlTest-Komponenten. Das DemoMarqueeControl wird angezeigt und kann ausgewählt werden.

  11. Ziehen Sie eine Instanz des DemoMarqueeControl auf die Form1-Entwurfsoberfläche.

  12. Rufen Sie in den Click-Ereignishandlern die Start-Methode und die Stop-Methode für das DemoMarqueeControl 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();
}

Nächste Schritte

Die MarqueeControlLibrary veranschaulicht eine einfache Implementierung von benutzerdefinierten Steuerelementen und zugehörigen Designern. Sie können dieses Beispiel auf verschiedene Weise anspruchsvoller gestalten:

  • Ändern Sie die Eigenschaftenwerte für das DemoMarqueeControl im Designer. Fügen Sie weitere MarqueBorder-Steuerelemente hinzu, und docken Sie sie innerhalb ihrer übergeordneten Instanzen an, um einen verschachtelten Effekt zu erstellen. Probieren Sie andere Einstellungen für die UpdatePeriod und die Lichteigenschaften aus.

  • Erstellen Sie Ihre eigenen Implementierungen von IMarqueeWidget. Sie könnten beispielsweise eine aufblinkende "Leuchtreklame" oder ein animiertes Symbol mit mehreren Bildern erstellen.

  • Passen Sie die Entwurfszeiterfahrung weiter an. Sie könnten versuchen, für mehr Eigenschaften als Enabled und Visible ein Shadowing auszuführen, und Sie könnten neue Eigenschaften hinzufügen. Fügen Sie neue Designerverben hinzu, um häufige Aufgaben zu vereinfachen, z. B. das Andocken von untergeordneten Steuerelementen.

  • Lizenzieren Sie das MarqueeControl. Weitere Informationen finden Sie unter Gewusst wie: Lizenzieren von Komponenten und Steuerelementen.

  • Steuern Sie, wie die Steuerelemente serialisiert werden und wie Code für sie generiert wird. Weitere Informationen finden Sie unter Generieren und Kompilieren von dynamischem Quellcode.

Siehe auch

Aufgaben

Gewusst wie: Erstellen eines Windows Forms-Steuerelements, das Entwurfszeitfeatures nutzt

Referenz

UserControl
ParentControlDesigner
DocumentDesigner
IRootDesigner
DesignerVerb
UITypeEditor
BackgroundWorker

Weitere Ressourcen

Erweitern der Entwurfszeitunterstützung
Benutzerdefinierte Designer
.NET Shape Library: A Sample Designer