Freigeben über


Einführung: Erstellen Sie ein Steuerungselement, das Vorteile aus den Entwurfszeitfunktionen zieht.

Die Entwurfszeit für ein benutzerdefiniertes Steuerelement kann durch erstellen eines zugeordneten benutzerdefinierten Designers verbessert werden.

Vorsicht

Dieser Inhalt wurde für .NET Framework geschrieben. Wenn Sie .NET 6 oder eine höhere Version verwenden, verwenden Sie diesen Inhalt mit Vorsicht. Das Designersystem hat sich für Windows Forms geändert, und es ist wichtig, dass Sie die Änderungen im Designer seit dem .NET Framework Artikel überprüfen.

In diesem Artikel wird veranschaulicht, wie Sie einen benutzerdefinierten Designer für ein benutzerdefiniertes Steuerelement erstellen. Sie implementieren einen MarqueeControl Typ und eine zugeordnete Designerklasse namens MarqueeControlRootDesigner.

Der MarqueeControl Typ implementiert eine Anzeige ähnlich wie ein Theaterzelt mit animierten Lichtern und blinkendem Text.

Der Designer für dieses Steuerelement interagiert mit der Entwurfsumgebung, um eine benutzerdefinierte Entwurfszeit zu ermöglichen. Mit dem benutzerdefinierten Designer können Sie eine benutzerdefinierte MarqueeControl Implementierung mit animierten Lichtern und blinkenden Text in vielen Kombinationen zusammenstellen. Sie können das zusammengesetzte Steuerelement auf einem Formular wie jedes andere Windows Forms-Steuerelement verwenden.

Wenn Sie mit dieser exemplarischen Vorgehensweise fertig sind, sieht Ihr benutzerdefiniertes Steuerelement ungefähr wie folgt aus:

Die App mit einem Rahmen, der

Die vollständige Codeauflistung finden Sie unter Wie erstellt man ein Windows Forms Steuerelement, das die Design-Time Features nutzt.

Voraussetzungen

Um diese Anleitung abzuschließen, benötigen Sie Visual Studio.

Erstelle das Projekt

Der erste Schritt besteht darin, das Anwendungsprojekt zu erstellen. Sie verwenden dieses Projekt, um die Anwendung zu erstellen, die das benutzerdefinierte Steuerelement hosten soll.

Erstellen Sie in Visual Studio ein neues Windows Forms-Anwendungsprojekt, und nennen Sie es MarqueeControlTest.

Erstellen des Projekts für die Steuerelementbibliothek

  1. Fügen Sie der Lösungsdatei ein Windows Forms-Steuerelementbibliothek-Projekt hinzu. Nennen Sie das Projekt MarqueeControlLibrary.

  2. Löschen Sie mithilfe des Projektmappen-Explorers das Standardsteuerelement des Projekts, indem Sie die Quelldatei mit dem Namen "UserControl1.cs" oder "UserControl1.vb" je nach Ihrer Wahl löschen.

  3. Fügen Sie dem UserControl Projekt ein neues MarqueeControlLibrary Element hinzu. Geben Sie der neuen Quelldatei einen Basisnamen von MarqueeControl.

  4. Erstellen Sie mit dem Projektmappen-Explorer einen neuen Ordner im MarqueeControlLibrary Projekt.

  5. Klicken Sie mit der rechten Maustaste auf den Entwurfsordner , und fügen Sie eine neue Klasse hinzu. Nennen Sie ihn MarqueeControlRootDesigner.

  6. Sie müssen Typen aus der System.Design-Assembly verwenden. Fügen Sie also diesen Verweis auf das MarqueeControlLibrary Projekt hinzu.

Verweisen auf das Projekt "Benutzerdefiniertes Steuerelement"

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

Fügen Sie im MarqueeControlTest Projekt einen Projektverweis zur MarqueeControlLibrary Assembly hinzu. Achten Sie darauf, die Registerkarte "Projekte " im Dialogfeld " Verweis hinzufügen " zu verwenden, anstatt direkt auf die MarqueeControlLibrary Assembly zu verweisen.

Definieren eines benutzerdefinierten Steuerelements und des benutzerdefinierten Designers

Ihr benutzerdefiniertes Steuerelement wird von der UserControl Klasse abgeleitet. Auf diese Weise kann Ihr Steuerelement andere Steuerelemente enthalten und bietet Ihrem Steuerelement eine Vielzahl von Standardfunktionen.

Ihr benutzerdefiniertes Steuerelement verfügt über einen zugeordneten benutzerdefinierten Designer. Auf diese Weise können Sie eine einzigartige Designoberfläche erstellen, die speziell auf Ihr benutzerdefiniertes Steuerelement zugeschnitten ist.

Sie ordnen das Steuerelement dem Designer mithilfe der DesignerAttribute Klasse zu. Da Sie das gesamte Entwurfszeitverhalten Ihres benutzerdefinierten Steuerelements entwickeln, implementiert der benutzerdefinierte Designer die IRootDesigner Schnittstelle.

So definieren Sie ein benutzerdefiniertes Steuerelement und seinen benutzerdefinierten Designer

  1. Öffnen Sie die MarqueeControl Quelldatei im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces:

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

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

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing.Design;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing.Design
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  4. Ändern Sie die Deklaration von MarqueeControlRootDesigner, sodass sie von der DocumentDesigner Klasse erbt. Wenden Sie die ToolboxItemFilterAttribute an, um die Designerinteraktion mit der Toolbox anzugeben.

    Hinweis

    Die Definition für die MarqueeControlRootDesigner Klasse wurde in einen Namespace mit dem Namen MarqueeControlLibrary.Design eingeschlossen. Diese Deklaration platziert den Designer in einem speziellen Namespace, der für entwurfsbezogene Typen reserviert ist.

    namespace MarqueeControlLibrary.Design
    {
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
        public class MarqueeControlRootDesigner : DocumentDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
        ToolboxItemFilterType.Require), _
        ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
        ToolboxItemFilterType.Require)> _
        Public Class MarqueeControlRootDesigner
            Inherits DocumentDesigner
    
  5. Definieren Sie den Konstruktor für die MarqueeControlRootDesigner Klasse. Fügen Sie eine WriteLine Anweisung im Konstruktortext ein. Dies ist nützlich für das Debuggen.

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

Erstellen einer Instanz Ihres benutzerdefinierten Steuerelements

  1. Fügen Sie dem UserControl Projekt ein neues MarqueeControlTest Element hinzu. Geben Sie der neuen Quelldatei einen Basisnamen von DemoMarqueeControl.

  2. Öffnen Sie die DemoMarqueeControl Datei im Code-Editor. Importieren Sie den MarqueeControlLibrary Namespace zu Beginn der Datei.

    Imports MarqueeControlLibrary
    
    using MarqueeControlLibrary;
    
  3. Ändern Sie die Deklaration von DemoMarqueeControl, sodass sie von der MarqueeControl Klasse erbt.

  4. Projekt erstellen.

  5. Öffnen Sie Formular1 im Windows Forms-Designer.

  6. Suchen Sie die Registerkarte "MarqueeControlTest-Komponenten " in der Toolbox , und öffnen Sie sie. Ziehen Sie DemoMarqueeControl aus der Toolbox auf Ihr Formular.

  7. Projekt erstellen.

Einrichten des Projekts für Design-Time Debuggen

Wenn Sie ein benutzerdefiniertes Entwurfserlebnis entwickeln, müssen Sie Ihre Steuerelemente und Komponenten debuggen. Es gibt eine einfache Möglichkeit, Ihr Projekt einzurichten, um das Debuggen während der Entwurfsphase zu ermöglichen. Weitere Informationen finden Sie unter Exemplarische Vorgehensweise: Debuggen von benutzerdefinierten Windows Forms-Steuerelementen zur Entwurfszeit.

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

  2. Wählen Sie im Dialogfeld "MarqueeControlLibrary-Eigenschaftenseiten " die Seite "Debuggen " aus.

  3. Wählen Sie im Abschnitt "Startaktion " die Option "Externes Programm starten" aus. Sie debuggen eine separate Instanz von Visual Studio, also klicken Sie auf die Schaltfläche mit Auslassungspunkten (Die Schaltfläche mit Auslassungspunkten (...) im Fenster „Eigenschaften“ von Visual Studio), um nach der Visual Studio-IDE zu suchen. Der Name der ausführbaren Datei ist devenv.exe, und wenn Sie den Standardspeicherort installiert haben, ist der Pfad %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.

  4. Wählen Sie OK aus, 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.

Kontrollpunkt

Jetzt können Sie das Entwurfszeitverhalten Ihres benutzerdefinierten Steuerelements debuggen. Nachdem Sie festgestellt haben, dass die Debugumgebung ordnungsgemäß eingerichtet ist, testen Sie die Zuordnung zwischen dem benutzerdefinierten Steuerelement und dem benutzerdefinierten Designer.

Um die Debugumgebung und die Zuordnung des Designers zu testen

  1. Öffnen Sie die MarqueeControlRootDesigner-Quelldatei im Code-Editor , und platzieren Sie einen Haltepunkt für die WriteLine Anweisung.

  2. Drücken Sie F5-, um die Debugsitzung zu starten.

    Es wird eine neue Instanz von Visual Studio erstellt.

  3. Öffnen Sie in der neuen Instanz von Visual Studio die MarqueeControlTest-Lösung. Sie können die Projektmappe ganz einfach finden, indem Sie im Menü Datei die Option Zuletzt verwendete Projekte auswählen. Die MarqueeControlTest.sln Lösungsdatei wird als zuletzt verwendete Datei aufgeführt.

  4. Öffnen Sie den DemoMarqueeControl Designer.

    Die Debuginstanz von Visual Studio erhält den Fokus, und die Ausführung stoppt an Ihrem Haltepunkt. Drücken Sie F5 , um die Debugsitzung fortzusetzen.

An diesem Punkt ist alles für Sie vorhanden, um Ihr benutzerdefiniertes Steuerelement und den zugehörigen benutzerdefinierten Designer zu entwickeln und zu debuggen. Der rest dieses Artikels konzentriert sich auf die Details der Implementierungsfeatures des Steuerelements und des Designers.

Implementieren des benutzerdefinierten Steuerelements

Das MarqueeControl ist ein UserControl mit ein wenig Anpassung. Sie macht zwei Methoden verfügbar: Start, die die Animation des Laufrahmens startet und Stopdie Animation anhält. Da die MarqueeControl untergeordnete Steuerelemente enthalten, die die IMarqueeWidget-Schnittstelle implementieren, zählen Start und Stop jedes untergeordnete Steuerelement auf und rufen die StartMarquee- und StopMarquee-Methoden für jedes untergeordnete Steuerelement auf, das IMarqueeWidget implementiert.

Das Erscheinungsbild der Steuerelemente MarqueeBorder und MarqueeText hängt vom Layout ab. Daher überschreibt MarqueeControl die OnLayout-Methode und ruft PerformLayout bei untergeordneten Steuerelementen dieses Typs auf.

Dies ist der Umfang der MarqueeControl Anpassungen. Die Laufzeitfeatures werden durch die Steuerelemente MarqueeBorder und MarqueeText implementiert, und die Entwurfszeitfeatures werden durch die Klassen MarqueeBorderDesigner und MarqueeControlRootDesigner implementiert.

So implementieren Sie Ihr benutzerdefiniertes Steuerelement

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

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

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

Erstellen eines untergeordneten Steuerelements für Ihr benutzerdefiniertes Steuerelement

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

  • MarqueeBorder: Dieses Steuerelement zeichnet einen Rahmen von "Lichtern" an seinen Rändern. Die Lichter blinken nacheinander, sodass sie sich um den Rand bewegen. Die Geschwindigkeit, mit der die Lichter blinken, wird durch eine Eigenschaft gesteuert, die genannt wird UpdatePeriod. Mehrere andere benutzerdefinierte Eigenschaften bestimmen andere Aspekte der Darstellung des Steuerelements. Zwei Methoden, namens StartMarquee und StopMarquee, steuern, wann die Animation beginnt und endet.

  • MarqueeText: Dieses Steuerelement zeichnet eine blinkende Zeichenfolge. Wie das MarqueeBorder Steuerelement wird die Geschwindigkeit, mit der der Text blinkt, durch die UpdatePeriod Eigenschaft gesteuert. Das MarqueeText Steuerelement hat auch die StartMarquee Methoden und StopMarquee Methoden, die mit dem MarqueeBorder Steuerelement gemeinsam sind.

Zur Entwurfszeit können diese beiden Steuerelementtypen in beliebigen Kombinationen einem MarqueeControlRootDesigner hinzugefügt werden.

Gemeinsame Funktionen der beiden Steuerelemente sind in eine Schnittstelle namens IMarqueeWidget einbezogen. Dies ermöglicht es, MarqueeControl alle Marquee-bezogenen Kind-Steuerelemente zu entdecken und ihnen eine besondere Behandlung zu geben.

Um die periodische Animationsfunktion zu implementieren, verwenden Sie BackgroundWorker Objekte aus dem System.ComponentModel Namespace. Sie können Timer Objekte verwenden, aber wenn viele IMarqueeWidget Objekte vorhanden sind, kann der einzelne UI-Thread möglicherweise nicht mit der Animation schritthalten.

So erstellen Sie ein untergeordnetes Steuerelement für Ihr benutzerdefiniertes Steuerelement

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

  2. Öffnen Sie die IMarqueeWidget Quelldatei im Code-Editor und ändern Sie die Deklaration von class zu 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 der IMarqueeWidget Schnittstelle den folgenden Code hinzu, um zwei Methoden und eine Eigenschaft bereitzustellen, die die Marquee-Animation manipulieren.

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

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

  6. Legen Sie im Eigenschaftenfenster die Eigenschaften und BackgroundWorker Eigenschaften der WorkerReportsProgress Komponente WorkerSupportsCancellation auf "true" fest. Diese Einstellungen ermöglichen es der BackgroundWorker Komponente, das ProgressChanged Ereignis regelmäßig auszuheben und asynchrone Updates abzubrechen.

    Weitere Informationen finden Sie unter BackgroundWorker Component.

  7. Öffnen Sie die MarqueeText Quelldatei im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces:

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

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

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

    Die StartMarquee Methoden und StopMarquee Methoden rufen die BackgroundWorker Komponenten RunWorkerAsync und CancelAsync Methoden auf, um die Animation zu starten und zu beenden.

    Die Category Eigenschaften und Browsable Attribute werden auf die UpdatePeriod Eigenschaft angewendet, sodass sie in einem benutzerdefinierten Abschnitt des Eigenschaftenfensters mit dem Namen "Marquee" angezeigt wird.

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

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

    Der DoWork-Ereignishandler wird für die Anzahl von Millisekunden in den Schlaf versetzt, die durch UpdatePeriod angegeben wird, und löst dann das ProgressChanged-Ereignis aus, bis Ihr Code die Animation stoppt, indem er CancelAsync aufruft.

    Der ProgressChanged Ereignishandler schaltet den Text zwischen seinem hellen und dunklen Zustand um, um den Eindruck von Blinken zu erzeugen.

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

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

Erstellen des untergeordneten Steuerelements "MarqueeBorder"

Das MarqueeBorder Steuerelement ist etwas komplexer als das MarqueeText Steuerelement. Es verfügt über mehr Eigenschaften, und die Animation in der OnPaint Methode ist stärker beteiligt. Grundsätzlich ist es dem Steuerelement ziemlich ähnlich MarqueeText .

Da das MarqueeBorder Steuerelement über untergeordnete Steuerelemente verfügen kann, muss es sich um Layout Ereignisse bewusst sein.

So erstellen Sie das MarqueeBorder-Steuerelement

  1. Fügen Sie dem Projekt ein neues MarqueeControlLibrary hinzu. Geben Sie der neuen Quelldatei den Basisnamen "MarqueeBorder".

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

  3. Legen Sie im Eigenschaftenfenster die Eigenschaften und BackgroundWorker Eigenschaften der WorkerReportsProgress Komponente WorkerSupportsCancellation auf "true" fest. Diese Einstellungen ermöglichen es der BackgroundWorker Komponente, das ProgressChanged Ereignis regelmäßig auszuheben und asynchrone Updates abzubrechen. Weitere Informationen finden Sie unter BackgroundWorker Component.

  4. Wählen Sie im Eigenschaftenfenster die Schaltfläche "Ereignisse " aus. Fügen Sie Handler für das DoWork- und das ProgressChanged-Ereignis an.

  5. Öffnen Sie die MarqueeBorder Quelldatei im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces:

    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Drawing.Design
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  6. Ändern Sie die Deklaration von MarqueeBorder, sodass sie von Panel erbt und die IMarqueeWidget Schnittstelle implementiert.

    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {
    
    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _
    ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeBorder
        Inherits Panel
        Implements IMarqueeWidget
    
  7. Deklarieren Sie zwei Enumerationen zum Verwalten des Zustands des MarqueeBorder Steuerelements: MarqueeSpinDirection, wodurch die Richtung bestimmt wird, in der die Lichter um den Rahmen "drehen" und MarqueeLightShapewelche die Form der Lichter (quadratisch oder kreisförmig) bestimmt. 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
    }
    
    // This defines the possible values for the MarqueeBorder
    // control's LightShape property.
    public enum MarqueeLightShape
    {
        Square,
        Circle
    }
    
    ' This defines the possible values for the MarqueeBorder
    ' control's SpinDirection property.
    Public Enum MarqueeSpinDirection
       CW
       CCW
    End Enum
    
    ' This defines the possible values for the MarqueeBorder
    ' control's LightShape property.
    Public Enum MarqueeLightShape
        Square
        Circle
    End Enum
    
  8. Deklarieren Sie die Instanzvariablen, die den verfügbar gemachten Eigenschaften entsprechen, und initialisieren Sie sie im Konstruktor.

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

    Die StartMarquee Methoden und StopMarquee Methoden rufen die BackgroundWorker Komponenten RunWorkerAsync und CancelAsync Methoden auf, um die Animation zu starten und zu beenden.

    Da das MarqueeBorder Steuerelement untergeordnete Steuerelemente enthalten kann, enumeriert die StartMarquee Methode alle untergeordneten Steuerelemente und ruft StartMarquee auf diejenigen auf, die IMarqueeWidget implementieren. Die StopMarquee Methode hat eine ähnliche Implementierung.

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

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

    Der DoWork-Ereignishandler wird für die Anzahl von Millisekunden in den Schlaf versetzt, die durch UpdatePeriod angegeben wird, und löst dann das ProgressChanged-Ereignis aus, bis Ihr Code die Animation stoppt, indem er CancelAsync aufruft.

    Der ProgressChanged Ereignishandler erhöht die Position des "Basislichts", aus dem der Hell-/Dunkelzustand der anderen Lichter bestimmt wird, und ruft die Refresh Methode auf, die dazu führt, dass das Steuerelement sich selbst neu zeichnet.

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

    Die IsLit Methode bestimmt die Farbe eines Lichts an einer bestimmten Position. Lichter, die "beleuchtet" sind, werden in der von der LightColor Eigenschaft angegebenen Farbe gezeichnet, und diejenigen, die "dunkel" sind, werden in der Farbe gezeichnet, die von der DarkColor Eigenschaft angegeben wird.

    Die DrawLight Methode rendert ein Licht mit der passenden Farbe, Form und Position.

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

    Die OnPaint Methode zeichnet die Lichter entlang der Ränder des MarqueeBorder Steuerelements.

    Da die OnPaint Methode von den Abmessungen des MarqueeBorder Steuerelements abhängt, müssen Sie sie immer dann aufrufen, wenn sich das Layout ändert. Um dies zu erreichen, überschreiben Sie OnLayout und rufen Sie Refresh auf.

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

Erstellen eines benutzerdefinierten Designers für Schatten- und Filtereigenschaften

Die MarqueeControlRootDesigner Klasse stellt die Implementierung für den Stamm-Designer bereit. Zusätzlich zu diesem Designer, der auf dem MarqueeControlSteuerelement arbeitet, benötigen Sie einen benutzerdefinierten Designer, der speziell dem MarqueeBorder Steuerelement zugeordnet ist. Dieser Designer bietet benutzerdefiniertes Verhalten, das im Kontext des benutzerdefinierten Stamm-Designers geeignet ist.

Insbesondere werden bestimmte Eigenschaften des MarqueeBorderDesigner Steuerelements "geschattet" und gefiltert, wodurch ihre Interaktion mit der Entwurfsumgebung verändert wird.

Das Abfangen von Aufrufen des Eigenschaftsaccessors einer Komponente wird als "Überschattung" bezeichnet. Es ermöglicht einem Entwickler, den vom Benutzer festgelegten Wert nachzuverfolgen und gegebenenfalls an die zu entwerfende Komponente weiterzugeben.

In diesem Beispiel werden die Visible- und Enabled-Eigenschaften durch das MarqueeBorderDesigner-Element abgeschattet, wodurch verhindert wird, dass das MarqueeBorder-Steuerelement während der Entwurfsphase unsichtbar oder deaktiviert wird.

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

Die Basisklasse für MarqueeBorderDesigner ist ComponentDesigner, die über Methoden verfügt, mit denen die Attribute, Eigenschaften und Ereignisse geändert werden können, die von einem Steuerelement zur Entwurfszeit bereitgestellt werden:

Wenn Sie die öffentliche Schnittstelle einer Komponente mithilfe dieser Methoden ändern, befolgen Sie die folgenden Regeln:

  • Hinzufügen oder Entfernen von Elementen nur in den PreFilter Methoden

  • Ändern Sie nur vorhandene Elemente in den PostFilter Methoden

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

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

Durch die Einhaltung dieser Regeln wird sichergestellt, dass alle Designer in der Entwurfszeitumgebung eine einheitliche Ansicht aller zu entwerfenden Komponenten haben.

Die ComponentDesigner Klasse stellt ein Wörterbuch zum Verwalten der Werte von schattierten Eigenschaften bereit, wodurch Sie von der Notwendigkeit befreit werden, bestimmte Instanzvariablen zu erstellen.

So erstellen Sie einen benutzerdefinierten Designer zum Schatten und Filtern von Eigenschaften

  1. Klicken Sie mit der rechten Maustaste auf den Entwurfsordner , und fügen Sie eine neue Klasse hinzu. Geben Sie der Quelldatei einen Basisnamen von MarqueeBorderDesigner.

  2. Öffnen Sie die Quelldatei "MarqueeBorderDesigner" im Code-Editor. Importieren Sie oben in der Datei die folgenden Namespaces:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  3. Ändern Sie die Deklaration von MarqueeBorderDesigner, sodass sie von ParentControlDesigner erbt.

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

    namespace MarqueeControlLibrary.Design
    {
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
  4. Überschreiben Sie die Basisimplementierung von PreFilterProperties.

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

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

Behandeln von Komponentenänderungen

Die MarqueeControlRootDesigner Klasse bietet die maßgeschneiderte Entwurfserfahrung für Ihre MarqueeControl Instanzen. Die meisten Entwurfszeitfunktionen werden von der DocumentDesigner Klasse geerbt. Ihr Code implementiert zwei spezifische Anpassungen: Behandeln von Komponentenänderungen und Hinzufügen von Designerverben.

Wenn Benutzer ihre MarqueeControl Instanzen entwerfen, verfolgt Ihr Stamm-Designer Änderungen an MarqueeControl und seinen untergeordneten Steuerelementen. Die Entwurfszeitumgebung bietet einen bequemen Dienst, IComponentChangeServiceum Änderungen am Komponentenzustand nachzuverfolgen.

Sie erhalten einen Verweis auf diesen Dienst, indem Sie die Umgebung mit der GetService Methode abfragen. Wenn die Abfrage erfolgreich ist, kann ihr Designer einen Handler für das ComponentChanged Ereignis anfügen und alle Aufgaben ausführen, die erforderlich sind, um einen konsistenten Zustand zur Entwurfszeit beizubehalten.

Im Fall der MarqueeControlRootDesigner Klasse rufen Sie die Refresh Methode für jedes IMarqueeWidget Objekt auf, das in der MarqueeControlKlasse enthalten ist. Dadurch wird das IMarqueeWidget-Objekt entsprechend neu gezeichnet, wenn die Eigenschaften des übergeordneten Size-Objekts geändert werden.

So behandeln Sie Komponentenänderungen

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

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

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

Hinzufügen von Designerverben zu Ihrem benutzerdefinierten Designer

Ein Designerverb ist ein Menübefehl, der mit einem Ereignishandler verknüpft ist. Designerverben werden dem Kontextmenü einer Komponente während der Gestaltung hinzugefügt. Weitere Informationen finden Sie unter DesignerVerb.

Sie fügen Ihren Designern zwei Designer-Verben hinzu: Test ausführen und Test beenden. Mit diesen Verben können Sie das Laufzeitverhalten der MarqueeControl zur Entwurfszeit anzeigen. Diese Verben werden zu MarqueeControlRootDesigner hinzugefügt.

Wenn "Test ausführen" aufgerufen wird, ruft der Verb-Ereignishandler die StartMarquee Methode für die MarqueeControl. Wenn "Stop Test" aufgerufen wird, ruft der Verb-Ereignishandler die StopMarquee Methode für die MarqueeControl. Die Implementierung der StartMarquee und StopMarquee-Methoden ruft diese auf jenen enthaltenen Steuerelementen auf, die IMarqueeWidget implementieren, sodass alle enthaltenen IMarqueeWidget-Steuerelemente ebenfalls am Test teilnehmen.

So fügen Sie Ihren benutzerdefinierten Designern Designerverben hinzu

  1. Fügen Sie in der MarqueeControlRootDesigner Klasse Ereignishandler namens OnVerbRunTest und OnVerbStopTest.

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

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

Benutzerdefinierten UITypeEditor erstellen

Beim Erstellen einer benutzerdefinierten Entwurfszeiterfahrung für Benutzer ist es häufig wünschenswert, eine benutzerdefinierte Interaktion mit dem Eigenschaftenfenster zu schaffen. Sie können dies erreichen, indem Sie ein Element UITypeEditor erstellen.

Das MarqueeBorder Steuerelement macht mehrere Eigenschaften im Eigenschaftenfenster verfügbar. Zwei dieser Eigenschaften MarqueeSpinDirection und MarqueeLightShape werden durch Enumerationen dargestellt. Zur Veranschaulichung der Verwendung eines UI-Typ-Editors verfügt die MarqueeLightShape Eigenschaft über eine zugeordnete UITypeEditor Klasse.

So erstellen Sie einen benutzerdefinierten Benutzeroberflächentyp-Editor

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

  2. Deklarieren Sie in der Definition der MarqueeBorder-Klasse eine Klasse namens LightShapeEditor, die von UITypeEditor abgeleitet ist.

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

    private IWindowsFormsEditorService editorService = null;
    
    Private editorService As IWindowsFormsEditorService = Nothing
    
  4. Überschreiben Sie die GetEditStyle-Methode. Diese Implementierung gibt DropDown zurück, was der Entwurfsumgebung angibt, wie die LightShapeEditor angezeigt wird.

    public override UITypeEditorEditStyle GetEditStyle(
    System.ComponentModel.ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    
    Public Overrides Function GetEditStyle( _
    ByVal context As System.ComponentModel.ITypeDescriptorContext) _
    As UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function
    
    
  5. Überschreiben Sie die EditValue-Methode. Diese Implementierung fragt die Entwurfsumgebung für ein IWindowsFormsEditorService Objekt ab. Falls es gelingt, erzeugt es ein LightShapeSelectionControl. Die DropDownControl Methode wird aufgerufen, um die LightShapeEditor zu starten. Der Rückgabewert aus diesem Aufruf wird an die Entwurfsumgebung zurückgegeben.

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

Erstellen eines Ansichtssteuerelements für den benutzerdefinierten UITypeEditor

Die MarqueeLightShape Eigenschaft unterstützt zwei Arten von Lichtformen: Square und Circle. Sie erstellen ein benutzerdefiniertes Steuerelement, das ausschließlich zum Zweck der grafischen Anzeige dieser Werte im Eigenschaftenfenster verwendet wird. Dieses benutzerdefinierte Steuerelement wird von Ihrem UITypeEditor verwendet, um mit dem Eigenschaftenfenster zu interagieren.

So erstellen Sie ein Ansichtssteuerelement für den benutzerdefinierten Benutzeroberflächentyp-Editor

  1. Fügen Sie dem UserControl Projekt ein neues MarqueeControlLibrary Element hinzu. Geben Sie der neuen Quelldatei einen Basisnamen von LightShapeSelectionControl.

  2. Ziehen Sie zwei Panel Steuerelemente aus der Toolbox auf die LightShapeSelectionControl. Benennen 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 schließlich die Size Eigenschaft des Werts LightShapeSelectionControl auf (150, 80) fest.

  3. Öffnen Sie die LightShapeSelectionControl Quelldatei im Code-Editor. Importieren Sie den System.Windows.Forms.Design Namespace zu Beginn der Datei.

    Imports System.Windows.Forms.Design
    
    using System.Windows.Forms.Design;
    
  4. Implementieren Sie Click Ereignishandler für die squarePanel und circlePanel Steuerelemente. Diese Methoden werden CloseDropDown aufgerufen, um die benutzerdefinierte UITypeEditor Bearbeitungssitzung zu beenden.

    private void squarePanel_Click(object sender, EventArgs e)
    {
        this.lightShapeValue = MarqueeLightShape.Square;
        
        this.Invalidate( false );
    
        this.editorService.CloseDropDown();
    }
    
    private void circlePanel_Click(object sender, EventArgs e)
    {
        this.lightShapeValue = MarqueeLightShape.Circle;
    
        this.Invalidate( false );
    
        this.editorService.CloseDropDown();
    }
    
    Private Sub squarePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Me.lightShapeValue = MarqueeLightShape.Square
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()
    
    End Sub
    
    
    Private Sub circlePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Me.lightShapeValue = MarqueeLightShape.Circle
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()
    
    End Sub
    
  5. Deklarieren Sie eine Instanzvariable IWindowsFormsEditorService namens editorService.

    Private editorService As IWindowsFormsEditorService
    
    private IWindowsFormsEditorService editorService;
    
  6. Deklarieren sie eine MarqueeLightShape Instanzvariable namens lightShapeValue.

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
  7. Fügen Sie im LightShapeSelectionControl Konstruktor die Click Ereignishandler an die squarePanel und circlePanel Steuerelemente für ihre Click Ereignisse an. Definieren Sie außerdem eine Konstruktorüberladung, die den Wert aus der Entwurfsumgebung dem MarqueeLightShape-Feld zuweist.

    // This constructor takes a MarqueeLightShape value from the
    // design-time environment, which will be used to display
    // the initial state.
    public LightShapeSelectionControl(
        MarqueeLightShape lightShape,
        IWindowsFormsEditorService editorService )
    {
        // This call is required by the designer.
        InitializeComponent();
    
        // Cache the light shape value provided by the
        // design-time environment.
        this.lightShapeValue = lightShape;
    
        // Cache the reference to the editor service.
        this.editorService = editorService;
    
        // Handle the Click event for the two panels.
        this.squarePanel.Click += new EventHandler(squarePanel_Click);
        this.circlePanel.Click += new EventHandler(circlePanel_Click);
    }
    
    ' This constructor takes a MarqueeLightShape value from the
    ' design-time environment, which will be used to display
    ' the initial state.
     Public Sub New( _
     ByVal lightShape As MarqueeLightShape, _
     ByVal editorService As IWindowsFormsEditorService)
         ' This call is required by the Windows.Forms Form Designer.
         InitializeComponent()
    
         ' Cache the light shape value provided by the 
         ' design-time environment.
         Me.lightShapeValue = lightShape
    
         ' Cache the reference to the editor service.
         Me.editorService = editorService
    
         ' Handle the Click event for the two panels. 
         AddHandler Me.squarePanel.Click, AddressOf squarePanel_Click
         AddHandler Me.circlePanel.Click, AddressOf circlePanel_Click
     End Sub
    
  8. Trennen Sie in der Dispose Methode die Click Ereignishandler.

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            // Be sure to unhook event handlers
            // to prevent "lapsed listener" leaks.
            this.squarePanel.Click -=
                new EventHandler(squarePanel_Click);
            this.circlePanel.Click -=
                new EventHandler(circlePanel_Click);
    
            if(components != null)
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }
    
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
    
            ' Be sure to unhook event handlers
            ' to prevent "lapsed listener" leaks.
            RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click
            RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click
    
            If (components IsNot Nothing) Then
                components.Dispose()
            End If
    
        End If
        MyBase.Dispose(disposing)
    End Sub
    
  9. Klicken Sie im Projektmappen-Explorer auf die Schaltfläche "Alle Dateien anzeigen ". Öffnen Sie die datei LightShapeSelectionControl.Designer.cs oder LightShapeSelectionControl.Designer.vb, und entfernen Sie die Standarddefinition der Dispose Methode.

  10. Implementieren Sie die LightShape Eigenschaft.

    // LightShape is the property for which this control provides
    // a custom user interface in the Properties window.
    public MarqueeLightShape LightShape
    {
        get
        {
            return this.lightShapeValue;
        }
        
        set
        {
            if( this.lightShapeValue != value )
            {
                this.lightShapeValue = value;
            }
        }
    }
    
    ' LightShape is the property for which this control provides
    ' a custom user interface in the Properties window.
    Public Property LightShape() As MarqueeLightShape
    
        Get
            Return Me.lightShapeValue
        End Get
    
        Set(ByVal Value As MarqueeLightShape)
            If Me.lightShapeValue <> Value Then
                Me.lightShapeValue = Value
            End If
        End Set
    
    End Property
    
  11. Überschreiben Sie die OnPaint-Methode. Diese Implementierung zeichnet ein gefülltes Quadrat und einen Kreis. Außerdem wird der ausgewählte Wert hervorgehoben, indem ein Rahmen um die eine oder andere Form gezeichnet wird.

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint (e);
    
        using(
            Graphics gSquare = this.squarePanel.CreateGraphics(),
            gCircle = this.circlePanel.CreateGraphics() )
        {	
            // Draw a filled square in the client area of
            // the squarePanel control.
            gSquare.FillRectangle(
                Brushes.Red,
                0,
                0,
                this.squarePanel.Width,
                this.squarePanel.Height
                );
    
            // If the Square option has been selected, draw a
            // border inside the squarePanel.
            if( this.lightShapeValue == MarqueeLightShape.Square )
            {
                gSquare.DrawRectangle(
                    Pens.Black,
                    0,
                    0,
                    this.squarePanel.Width-1,
                    this.squarePanel.Height-1);
            }
    
            // Draw a filled circle in the client area of
            // the circlePanel control.
            gCircle.Clear( this.circlePanel.BackColor );
            gCircle.FillEllipse(
                Brushes.Blue,
                0,
                0,
                this.circlePanel.Width,
                this.circlePanel.Height
                );
    
            // If the Circle option has been selected, draw a
            // border inside the circlePanel.
            if( this.lightShapeValue == MarqueeLightShape.Circle )
            {
                gCircle.DrawRectangle(
                    Pens.Black,
                    0,
                    0,
                    this.circlePanel.Width-1,
                    this.circlePanel.Height-1);
            }
        }	
    }
    
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        MyBase.OnPaint(e)
    
        Dim gCircle As Graphics = Me.circlePanel.CreateGraphics()
        Try
            Dim gSquare As Graphics = Me.squarePanel.CreateGraphics()
            Try
                ' Draw a filled square in the client area of
                ' the squarePanel control.
                gSquare.FillRectangle( _
                Brushes.Red, _
                0, _
                0, _
                Me.squarePanel.Width, _
                Me.squarePanel.Height)
    
                ' If the Square option has been selected, draw a 
                ' border inside the squarePanel.
                If Me.lightShapeValue = MarqueeLightShape.Square Then
                    gSquare.DrawRectangle( _
                    Pens.Black, _
                    0, _
                    0, _
                    Me.squarePanel.Width - 1, _
                    Me.squarePanel.Height - 1)
                End If
    
                ' Draw a filled circle in the client area of
                ' the circlePanel control.
                gCircle.Clear(Me.circlePanel.BackColor)
                gCircle.FillEllipse( _
                Brushes.Blue, _
                0, _
                0, _
                Me.circlePanel.Width, _
                Me.circlePanel.Height)
    
                ' If the Circle option has been selected, draw a 
                ' border inside the circlePanel.
                If Me.lightShapeValue = MarqueeLightShape.Circle Then
                    gCircle.DrawRectangle( _
                    Pens.Black, _
                    0, _
                    0, _
                    Me.circlePanel.Width - 1, _
                    Me.circlePanel.Height - 1)
                End If
            Finally
                gSquare.Dispose()
            End Try
        Finally
            gCircle.Dispose()
        End Try
    End Sub
    

Testen Sie Ihr benutzerdefiniertes Steuerelement im Designer

Zu diesem Zeitpunkt können Sie das MarqueeControlLibrary Projekt erstellen. Testen Sie Ihre Implementierung, indem Sie ein Steuerelement erstellen, das von der MarqueeControl Klasse erbt und es in einem Formular verwendet.

So erstellen Sie eine benutzerdefinierte MarqueeControl-Implementierung

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

  2. Öffnen Sie in der Toolbox die Registerkarte "MarqueeControlLibrary Components". Sie sehen die Steuerelemente MarqueeBorder und MarqueeText, die zur Auswahl zur Verfügung stehen.

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

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

  5. Erstellen Sie die Lösung.

  6. Klicken Sie mit der rechten Maustaste auf DemoMarqueeControl, und wählen Sie im angezeigten Kontextmenü die Option "Test ausführen" aus, um die Animation zu starten. Klicken Sie auf "Test beenden ", um die Animation zu beenden.

  7. Öffnen Sie Formular1 in der Entwurfsansicht.

  8. Platzieren Sie zwei Button Steuerelemente im Formular. Benennen Sie sie startButton und stopButton, und ändern Sie die Text-Eigenschaftswerte 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 ist zur Auswahl verfügbar.

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

  12. In den Click Ereignishandlern rufen Sie die Start und Stop Methoden auf dem 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();
    }
    
  13. Legen Sie das MarqueeControlTest Projekt als Startprojekt fest, und führen Sie es aus. Das Formular wird angezeigt, in dem Ihr DemoMarqueeControlFormular angezeigt wird. Wählen Sie die Schaltfläche "Start " aus, um die Animation zu starten. Sie sollten den Text blinken sehen und die Lichter, die sich um den Rand bewegen.

Nächste Schritte

Dies MarqueeControlLibrary veranschaulicht eine einfache Implementierung von benutzerdefinierten Steuerelementen und zugeordneten Designern. Sie können dieses Beispiel auf verschiedene Arten komplexer gestalten:

  • Ändern Sie die Eigenschaften des DemoMarqueeControl im Designer. Fügen Sie weitere MarqueBorder Steuerelemente hinzu und docken Sie sie in ihre übergeordneten Instanzen an, um einen geschachtelten Effekt zu erstellen. Experimentieren Sie mit unterschiedlichen Einstellungen für die UpdatePeriod und die lichtbezogenen Eigenschaften.

  • Erstellen Sie Ihre eigenen Implementierungen von IMarqueeWidget. Sie könnten z. B. ein blinkendes "Neonzeichen" oder ein animiertes Zeichen mit mehreren Bildern erstellen.

  • Passen Sie die Entwurfszeit weiter an. Sie könnten versuchen, mehr Eigenschaften zu schattieren als Enabled und Visible, und Sie könnten neue Eigenschaften hinzufügen. Fügen Sie neue Designerverben hinzu, um allgemeine Aufgaben wie das Andocken untergeordneter Steuerelemente zu vereinfachen.

  • Lizenz für .MarqueeControl

  • Steuern Sie, wie Ihre Steuerelemente serialisiert werden und wie Code für sie generiert wird. Weitere Informationen finden Sie unter Dynamische Quellcodegenerierung und Kompilierung.

Siehe auch