Markieren Sie das Kontrollkästchen Englisch, um die englische Version dieses Artikels anzuzeigen. Sie können den englischen Text auch in einem Popup-Fenster einblenden, indem Sie den Mauszeiger über den Text bewegen.
Übersetzung
Englisch

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

 

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

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

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

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

In dieser exemplarischen Vorgehensweise werden u. a. folgende Aufgaben veranschaulicht:

  • Erstellen des Projekts

  • Erstellen eines Steuerelementbibliothek-Projekts

  • Verweisen auf das benutzerdefinierte Steuerelementprojekt

  • Definieren eines benutzerdefinierten Steuerelements und seines benutzerdefinierten Designers

  • Erstellen einer Instanz des benutzerdefinierten Steuerelements

  • Einrichten des Projekts zum Entwurfszeitdebuggen

  • Implementieren des benutzerdefinierten Steuerelements

  • Erstellen eines untergeordneten Steuerelements für das benutzerdefinierte Steuerelement

  • Erstellen des untergeordneten MarqueeBorder-Steuerelements

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

  • Behandeln von Komponentenänderungen

  • Hinzufügen von Designerverben zum benutzerdefinierten Designer

  • Erstellen eines benutzerdefinierten UI-Typ-Editors

  • Testen des benutzerdefinierten Steuerelements im Designer

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

Mögliche Anordnung eines MarqueeControl

Die vollständige Codeliste finden Sie unter How to: Create a Windows Forms Control That Takes Advantage of Design-Time Features.

System_CAPS_noteHinweis

Je nach den aktiven Einstellungen oder der Version unterscheiden sich die Dialogfelder und Menübefehle auf Ihrem Bildschirm möglicherweise von den in der Hilfe beschriebenen. Wählen Sie im Menü Extras die Option Einstellungen importieren und exportieren aus, um die Einstellungen zu ändern. Weitere Informationen finden Sie unter Customizing Development Settings in Visual Studio.

Erforderliche Komponenten

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

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

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

So erstellen Sie das Projekt

  • Erstellen Sie ein Windows Forms-Anwendungsprojekt mit dem Namen "MarqueeControlTest". Weitere Informationen finden Sie unter How to: Create a Windows Application Project.

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

So erstellen Sie das Steuerelementbibliothek-Projekt

  1. Fügen Sie der Projektmappe ein Windows Forms-Steuerelementbibliothek-Projekt hinzu. Nennen Sie das Projekt "MarqueeControlLibrary".

  2. Löschen Sie im Projektmappen-Explorer das standardmäßige Steuerelement des Projekts, indem Sie die Quelldatei "UserControl1.cs" oder "UserControl1.vb" löschen, je nach ausgewählter Sprache. Weitere Informationen finden Sie unter NIB:How to: Remove, Delete, and Exclude Items.

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

  4. Erstellen Sie im Projektmappen-Explorer einen neuen Ordner im MarqueeControlLibrary-Projekt. Weitere Informationen finden Sie unter NIB:How to: Add New Project Items. Nennen Sie den neuen Ordner "Design".

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

  6. Sie müssen Typen aus der System.Design-Assembly verwenden; fügen Sie also diesen Verweis zum MarqueeControlLibrary-Projekt hinzu.

    System_CAPS_noteHinweis

    Um die System.Design-Assembly verwenden zu können, muss das Projekt die Vollversion von .NET Framework und nicht das .NET Framework Client Profile als Zielversion verwenden. Weitere Informationen zum Ändern des Zielframeworks finden Sie unter Gewusst wie: .NET Framework-Version als Ziel.

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

So verweisen Sie auf das benutzerdefinierte Steuerelementprojekt

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

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

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

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

So definieren Sie ein benutzerdefiniertes Steuerelement und seinen benutzerdefinierten Designer

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

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

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

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

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

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

    public MarqueeControlRootDesigner()
    {
        Trace.WriteLine("MarqueeControlRootDesigner ctor");
    }
    

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

So erstellen Sie eine Instanz des benutzerdefinierten Steuerelements

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

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

using MarqueeControlLibrary;
  1. Ändern Sie die Deklaration von DemoMarqueeControl, um von der MarqueeControl-Klasse zu erben.

  2. Erstellen Sie das Projekt.

  3. Öffnen Sie Form1 im Windows Forms-Designer.

  4. Öffnen Sie in der Toolbox die Registerkarte MarqueeControlTest-Komponenten. Ziehen Sie ein DemoMarqueeControl aus der Toolbox auf das Formular.

  5. Erstellen Sie das Projekt.

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

So richten Sie das Projekt zum Entwurfszeitdebuggen ein

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

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

  3. Wählen Sie im Abschnitt Startaktion die Option Externes Programm starten aus. Da Sie eine separate Instanz von Visual Studio debuggen, klicken Sie auf die Schaltfläche mit den Auslassungszeichen (VisualStudioEllipsesButton-Bildschirmabbildung), um nach der Visual Studio IDE zu suchen. Der Name der ausführbaren Datei lautet devenv.exe. Bei der Installation am Standardspeicherort lautet der Pfad %programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.

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

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

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

So testen Sie die Debugumgebung und die Designerzuordnung

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

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

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

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

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

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

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

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

So implementieren Sie das benutzerdefinierte Steuerelement

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

    public void Start()
    {
        // The MarqueeControl may contain any number of 
        // controls that implement IMarqueeWidget, so 
        // find each IMarqueeWidget child and call its
        // StartMarquee method.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StartMarquee();
            }
        }
    }
    
    public void Stop()
    {
        // The MarqueeControl may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StopMarquee
        // method.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StopMarquee();
            }
        }
    }
    
  2. Überschreiben der OnLayout-Methode.

    protected 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();
            }
        }
    }
    

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

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

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

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

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

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

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

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

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

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

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

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

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

  7. Öffnen Sie die MarqueeText-Quelldatei im Code-Editor. Importieren Sie am Anfang der Datei folgende 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;
    
  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
    {
    
  9. Deklarieren Sie die Instanzvariablen, die den verfügbar gemachten Eigenschaften entsprechen, und initialisieren Sie sie im Konstruktor. Das isLit-Feld legt fest, ob der Text in der von der LightColor-Eigenschaft vorgegebenen Farbe gezeichnet wird.

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

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

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

    public virtual void StartMarquee()
    {
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
  11. Implementieren Sie die Eigenschaftenaccessoren. Sie machen Clients zwei Eigenschaften verfügbar: LightColor und DarkColor. Das Category-Attribut und das Browsable-Attribut werden auf diese Eigenschaften angewendet, sodass die Eigenschaften in einem benutzerdefinierten Abschnitt des Eigenschaftenfensters mit dem Namen "Marquee" angezeigt werden.

    [Category("Marquee")]
    [Browsable(true)]
    public Color LightColor
    {
        get
        {
            return this.lightColorValue;
        }
        set
        {
            // The LightColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.lightColorValue.ToArgb() != value.ToArgb())
            {
                this.lightColorValue = value;
                this.lightBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color DarkColor
    {
        get
        {
            return this.darkColorValue;
        }
        set
        {
            // The DarkColor property is only changed if the 
            // client provides a different value. Comparing values 
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.darkColorValue.ToArgb() != value.ToArgb())
            {
                this.darkColorValue = value;
                this.darkBrush = new SolidBrush(value);
            }
        }
    }
    
  12. Implementieren Sie die Handler für das DoWork-Ereignis und das ProgressChanged-Ereignis der BackgroundWorker-Komponente.

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

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

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

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

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

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

So erstellen Sie das MarqueeBorder-Steuerelement

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

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

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

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

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

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

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

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

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

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

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

    public virtual void StartMarquee()
    {
        // The MarqueeBorder control may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StartMarquee
        // method.
        foreach (Control cntrl in this.Controls)
        {
            if (cntrl is IMarqueeWidget)
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StartMarquee();
            }
        }
    
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // The MarqueeBorder control may contain any number of 
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StopMarquee
        // method.
        foreach (Control cntrl in this.Controls)
        {
            if (cntrl is IMarqueeWidget)
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StopMarquee();
            }
        }
    
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public virtual int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
    
  10. Implementieren Sie die Eigenschaftenaccessoren. Das MarqueeBorder-Steuerelement verfügt über mehrere Eigenschaften, die sein Aussehen steuern.

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

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

    Der ProgressChanged-Ereignishandler erhöht die Position des "Basislichts", durch die der Hell-/Dunkelzustand der anderen Lichter bestimmt wird, und ruft die Refresh-Methode auf, damit das Steuerelement sich selbst neu zeichnet.

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

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

    Die DrawLight-Methode zeichnet ein Licht mit der entsprechenden Farbe, Form und Position.

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

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

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

    protected 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++;
            }
        }
    }
    

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    namespace MarqueeControlLibrary.Design
    {
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeBorderDesigner : 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]);
    }
    
  5. Implementieren Sie die Enabled-Eigenschaft und die Visible-Eigenschaft. Diese Implementierungen führen für die Eigenschaften des Steuerelements ein Shadowing aus.

    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;
        }
    }
    

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

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

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

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

So behandeln Sie Komponentenänderungen

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

    base.Initialize(component);
    
    IComponentChangeService cs =
        GetService(typeof(IComponentChangeService)) 
        as IComponentChangeService;
    
    if (cs != null)
    {
        cs.ComponentChanged +=
            new ComponentChangedEventHandler(OnComponentChanged);
    }
    
  2. Implementieren Sie den OnComponentChanged-Ereignishandler. Testen Sie den Typ der sendenden Komponente. Wenn es sich um IMarqueeWidget handelt, rufen Sie die entsprechende Refresh-Methode auf.

    private void OnComponentChanged(
        object sender,
        ComponentChangedEventArgs e)
    {
        if (e.Component is IMarqueeWidget)
        {
            this.Control.Refresh();
        }
    }
    

Ein Designerverb ist ein mit einem Ereignishandler verknüpfter Menübefehl. Designerverben werden zur Entwurfszeit dem Kontextmenü einer Komponente hinzugefügt. Weitere Informationen finden Sie unter DesignerVerb.

Sie fügen den Designern zwei Designerverben hinzu: Test ausführen und Test beenden. Diese Verben ermöglichen es Ihnen, das Laufzeitverhalten des MarqueeControl zur Entwurfszeit anzuzeigen. Diese Verben werden dem MarqueeControlRootDesigner hinzugefügt.

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

So fügen Sie Designerverben den benutzerdefinierten Designern hinzu

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

    private void OnVerbRunTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Start();
    }
    
    private void OnVerbStopTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Stop();
    }
    
  2. Verbinden Sie diese Ereignishandler mit den entsprechenden Designerverben. MarqueeControlRootDesigner erbt von der DesignerVerbCollection-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))
        );
    

Wenn Sie eine benutzerdefinierte Entwurfszeiterfahrung für Benutzer erstellen, ist es häufig angebracht, eine benutzerdefinierte Interaktion mit dem Eigenschaftenfenster zu erstellen. Hierzu können Sie einen UITypeEditor erstellen. Weitere Informationen finden Sie unter How to: Create a UI Type Editor.

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

So erstellen Sie einen benutzerdefinierten UI-Typ-Editor

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

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

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

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

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

    public 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;
    }
    

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

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

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

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

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

using System.Windows.Forms.Design;
  1. Implementieren Sie Click-Ereignishandler für das squarePanel-Steuerelement und das circlePanel-Steuerelement. Diese Methoden rufen CloseDropDown auf, 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();
    }
    
  2. Deklarieren Sie eine IWindowsFormsEditorService-Instanzvariable mit der Bezeichnung editorService.

private IWindowsFormsEditorService editorService;
  1. Deklarieren Sie eine MarqueeLightShape-Instanzvariable mit der Bezeichnung lightShapeValue.

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
  2. Fügen Sie im LightShapeSelectionControl-Konstruktor die Click-Ereignishandler an die Click-Ereignisse des squarePanel-Steuerelements und des circlePanel-Steuerelements an. Definieren Sie darüber hinaus auch eine Konstruktorüberladung, die dem lightShapeValue-Feld den MarqueeLightShape-Wert aus der Entwurfsumgebung 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);
    }
    
  3. Trennen Sie die Click-Ereignishandler in der Dispose-Methode.

          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 );
    }
    
  4. Klicken Sie im Projektmappen-Explorer auf die Schaltfläche Alle Dateien anzeigen. Öffnen Sie die Datei LightShapeSelectionControl.Designer.cs oder die Datei LightShapeSelectionControl.Designer.vb, und entfernen Sie die Standarddefinition der Dispose-Methode.

  5. 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;
    		}
    	}
    }
    
  6. Überschreiben Sie die OnPaint-Methode. Mit dieser Implementierung wird ein ausgefülltes Quadrat und ein ausgefüllter Kreis gezeichnet. Zudem wird der ausgewählte Wert durch Zeichnen eines Rahmens um eine der Formen hervorgehoben.

          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);
    		}
    	}	
    }
    

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

So erstellen Sie eine benutzerdefinierte MarqueeControl-Implementierung

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

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

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

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

  5. Erstellen Sie die Projektmappe.

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

  7. Öffnen Sie Form1 in der Entwurfsansicht.

  8. Platzieren Sie zwei Button-Steuerelemente auf dem Formular. Nennen Sie die Steuerelemente startButton und stopButton, und ändern Sie die Text-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 wird angezeigt und kann ausgewählt werden.

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

  12. Rufen Sie in den Click-Ereignishandlern die Start-Methode und die Stop-Methode für das DemoMarqueeControl auf.

private void startButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Start();
}

private void stopButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Stop();
}
  1. Legen Sie das MarqueeControlTest-Projekt als Startprojekt fest, und führen Sie es aus. Im Formular wird Ihr DemoMarqueeControl angezeigt. Klicken Sie auf die Schaltfläche Start, um die Animation zu starten. Der Text sollte blinken, und die Lichter bewegen sich am Rand entlang.

Nächste Schritte

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

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

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

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

  • Lizenzieren Sie das MarqueeControl. Weitere Informationen finden Sie unter How to: License Components and Controls.

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

Anzeigen: