Delen via


Stapsgewijze uitleg: Een besturingselement maken dat gebruikmaakt van ontwerpfunctionaliteiten

De ontwerptijdservaring voor een aangepast besturingselement kan worden verbeterd door een bijbehorende aangepaste ontwerper te creëren.

Voorzichtigheid

Deze inhoud is geschreven voor .NET Framework. Als u .NET 6 of een latere versie gebruikt, gebruikt u deze inhoud voorzichtig. Het ontwerpsysteem is gewijzigd voor Windows Forms en het is belangrijk dat u de Designer-wijzigingen bekijkt sinds .NET Framework artikel.

In dit artikel wordt uitgelegd hoe u een aangepaste ontwerpfunctie maakt voor een aangepast besturingselement. U implementeert een MarqueeControl type en een bijbehorende ontwerpklasse met de naam MarqueeControlRootDesigner.

Het MarqueeControl type implementeert een display dat vergelijkbaar is met een theaterkader met geanimeerde lichten en knipperende tekst.

De ontwerper voor dit besturingselement communiceert met de ontwerpomgeving om tijdens het ontwerpen een aangepaste ontwerpervaring te bieden. Met de aangepaste ontwerpfunctie kunt u een aangepaste MarqueeControl implementatie samenstellen met geanimeerde lichten en knipperende tekst in veel combinaties. U kunt het samengestelde besturingselement op een formulier zoals elk ander Besturingselement voor Windows Forms gebruiken.

Wanneer u klaar bent met deze handleiding, ziet uw aangepaste besturingselement er ongeveer als volgt uit:

De app toont een scrollende tekst met de knoppen Start en Stop.

Zie Procedure: Een Windows Forms-besturingselement maken dat gebruikmaakt van Design-Time functiesvoor de volledige lijst met code.

Voorwaarden

Als u dit scenario wilt voltooien, hebt u Visual Studio nodig.

Het project maken

De eerste stap is het maken van het toepassingsproject. U gebruikt dit project om de toepassing te bouwen die als host fungeert voor het aangepaste besturingselement.

Maak in Visual Studio een nieuw Windows Forms-toepassingsproject en noem het MarqueeControlTest.

Het besturingselementbibliotheekproject maken

  1. Voeg een Windows Forms-besturingselementbibliotheekproject toe aan de oplossing. Geef het project de naam MarqueeControlLibrary.

  2. Verwijder met Solution Explorerhet standaardbeheer van het project door het bronbestand met de naam 'UserControl1.cs' of 'UserControl1.vb' te verwijderen, afhankelijk van uw gewenste taal.

  3. Voeg een nieuw UserControl-item toe aan het MarqueeControlLibrary project. Geef het nieuwe bronbestand een basisnaam van MarqueeControl.

  4. Maak met Solution Explorereen nieuwe map in het MarqueeControlLibrary project.

  5. Klik met de rechtermuisknop op de map Ontwerp en voeg een nieuwe klasse toe. Geef de naam MarqueeControlRootDesigner.

  6. U moet typen uit de System.Design assembly gebruiken, dus voeg deze verwijzing toe aan het MarqueeControlLibrary project.

Verwijs naar het project voor aangepaste besturingselementen.

U gebruikt het project MarqueeControlTest om de aangepaste controle te testen. Het testproject wordt op de hoogte van het aangepaste besturingselement wanneer u een projectreferentie toevoegt aan de MarqueeControlLibrary assembly.

Voeg in het MarqueeControlTest project een projectreferentie toe aan de MarqueeControlLibrary assembly. Zorg ervoor dat u het tabblad Projecten in het dialoogvenster Verwijzing toevoegen gebruikt in plaats van rechtstreeks naar de MarqueeControlLibrary assembly te verwijzen.

Een aangepaste controle en zijn aangepaste ontwerper definiëren

Je aangepaste besturing wordt afgeleid van de UserControl-klasse. Hierdoor kan uw besturingselement andere besturingselementen bevatten en biedt uw besturingselement veel standaardfunctionaliteit.

Uw aangepaste besturingselement heeft een bijbehorende aangepaste ontwerper. Hiermee kunt u een unieke ontwerpervaring maken die speciaal is afgestemd op uw aangepaste controle.

U koppelt het besturingselement aan de ontwerpfunctie met behulp van de DesignerAttribute-klasse. Omdat u het volledige ontwerpgedrag tijdens het ontwerpen van uw aangepaste controle ontwikkelt, implementeert de aangepaste ontwerper de interface IRootDesigner.

Om een aangepast besturingselement en de bijbehorende aangepaste ontwerper te definiëren

  1. Open het MarqueeControl bronbestand in de Code-editor. Importeer bovenaan het bestand de volgende naamruimten:

    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. Voeg de DesignerAttribute toe aan de MarqueeControl klassedeclaratie. Hiermee koppelt u het aangepaste besturingselement aan de ontwerper.

    [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
    public class MarqueeControl : UserControl
    {
    
    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
  3. Open het MarqueeControlRootDesigner bronbestand in de Code-editor. Importeer bovenaan het bestand de volgende naamruimten:

    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. Wijzig de declaratie van MarqueeControlRootDesigner om over te nemen van de DocumentDesigner-klasse. Pas de ToolboxItemFilterAttribute toe om de interactie van de ontwerper met de Gereedschapskistte specificeren.

    Notitie

    De definitie voor de klasse MarqueeControlRootDesigner is ingesloten in een naamruimte met de naam MarqueeControlLibrary.Design. Deze declaratie plaatst de ontwerper in een speciale naamruimte die is gereserveerd voor ontwerpgerelateerde typen.

    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. Definieer de constructor voor de klasse MarqueeControlRootDesigner. Voeg een WriteLine instructie in de hoofdtekst van de constructor in. Dit is handig voor foutopsporing.

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

Een exemplaar van uw aangepaste besturingselement maken

  1. Voeg een nieuw UserControl-item toe aan het MarqueeControlTest project. Geef het nieuwe bronbestand een basisnaam van DemoMarqueeControl-.

  2. Open het DemoMarqueeControl bestand in de Code-editor. Importeer bovenaan het bestand de MarqueeControlLibrary naamruimte:

    Imports MarqueeControlLibrary
    
    using MarqueeControlLibrary;
    
  3. Wijzig de declaratie van DemoMarqueeControl om over te nemen van de MarqueeControl-klasse.

  4. Bouw het project.

  5. Open Form1 in de Windows Forms-ontwerper.

  6. Zoek het tabblad MarqueeControlTest Components in de Werkset en open het. Sleep een DemoMarqueeControl van de Werkset naar het formulier.

  7. Bouw het project.

Het project instellen voor Design-Time foutopsporing

Wanneer u een aangepaste ontwerptijdervaring ontwikkelt, moet u fouten opsporen in uw besturingselementen en onderdelen. Er is een eenvoudige manier om uw project in te stellen om foutopsporing tijdens het ontwerp mogelijk te maken. Voor meer informatie, zie Walkthrough: Foutopsporing van aangepaste Windows Forms Controls tijdens het ontwerpen.

  1. Klik met de rechtermuisknop op het MarqueeControlLibrary project en selecteer Eigenschappen.

  2. Selecteer in het dialoogvenster MarqueeControlLibrary Property Pages de Debug pagina.

  3. Selecteer in de sectie Start actie de optie Extern programma starten. U gaat foutopsporing uitvoeren van een aparte instantie van Visual Studio. Klik daarom op het beletselteken (Knop Beletselteken (...) in het venster Eigenschappen van Visual Studio) om naar de Visual Studio IDE te bladeren. De naam van het uitvoerbare bestand wordt devenv.exeen als u op de standaardlocatie hebt geïnstalleerd, is het pad %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<-editie>\Common7\IDE\devenv.exe.

  4. Selecteer OK- om het dialoogvenster te sluiten.

  5. Klik met de rechtermuisknop op het project MarqueeControlLibrary en selecteer Instellen als opstartproject om deze foutopsporingsconfiguratie in te schakelen.

Doorlaatpost

U bent nu klaar om het ontwerptijdgedrag van uw aangepaste controle-element te debuggen. Zodra u hebt vastgesteld dat de foutopsporingsomgeving correct is ingesteld, test u de koppeling tussen het aangepaste besturingselement en de aangepaste ontwerpfunctie.

Om de foutopsporingsomgeving en de ontwerpkoppeling te testen

  1. Open het bronbestand MarqueeControlRootDesigner in de Code-editor en plaats een onderbrekingspunt op de WriteLine-instructie.

  2. Druk op F5- om de foutopsporingssessie te starten.

    Er wordt een nieuw exemplaar van Visual Studio gemaakt.

  3. Open in het nieuwe exemplaar van Visual Studio de oplossing MarqueeControlTest. U kunt de oplossing eenvoudig vinden door recente projecten te selecteren in het menu Bestand. Het MarqueeControlTest.sln oplossingsbestand wordt vermeld als het meest recent gebruikte bestand.

  4. Open de DemoMarqueeControl in de ontwerpfunctie.

    De foutopsporingsinstantie van Visual Studio krijgt de focus en de uitvoering stopt bij je onderbrekingspunt. Druk op F5- om door te gaan met de foutopsporingssessie.

Op dit moment is alles aanwezig om uw aangepaste besturingselement en de bijbehorende aangepaste ontwerpfunctie te ontwikkelen en er fouten in op te sporen. De rest van dit artikel is gericht op de details van het implementeren van functies van het besturingselement en de ontwerper.

Het aangepaste besturingselement implementeren

De MarqueeControl is een UserControl met een beetje aanpassing. Er worden twee methoden weergegeven: Start, waarmee de animatie wordt gestart en Stop, waardoor de animatie wordt gestopt. Omdat de MarqueeControl onderliggende besturingselementen bevat die de IMarqueeWidget-interface implementeren, Start en Stop elk onderliggend besturingselement opsommen en respectievelijk de methoden StartMarquee en StopMarquee aanroepen op elk onderliggend besturingselement dat IMarqueeWidgetimplementeert.

Het uiterlijk van de besturingselementen MarqueeBorder en MarqueeText is afhankelijk van de indeling, dus overschrijft MarqueeControl de methode OnLayout en roept PerformLayout aan op onderliggende besturingselementen van dit type.

Dit is de omvang van de MarqueeControl aanpassingen. De runtimefuncties worden geïmplementeerd door de besturingselementen MarqueeBorder en MarqueeText en de ontwerpfuncties worden geïmplementeerd door de MarqueeBorderDesigner- en MarqueeControlRootDesigner klassen.

Uw aangepaste besturingselement implementeren

  1. Open het MarqueeControl bronbestand in de Code-editor. Implementeer de methoden Start en 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. Overschrijf de methode OnLayout.

    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
    

Maak een kindcontrole voor uw aangepaste controle

De MarqueeControl zal twee soorten onderliggende besturingselementen hosten: het besturingselement MarqueeBorder en het besturingselement MarqueeText.

  • MarqueeBorder: Met dit controle-element wordt een rand van 'lichten' rond de randen weergegeven. De lichten knipperen in een volgorde, zodat ze over de omtrek lijken te bewegen. De snelheid waarmee de lichten knipperen wordt geregeld door een eigenschap genaamd UpdatePeriod. Verschillende andere aangepaste eigenschappen bepalen andere aspecten van het uiterlijk van het besturingselement. Twee methoden, StartMarquee en StopMarqueegenoemd, bepalen wanneer de animatie wordt gestart en gestopt.

  • MarqueeText: Deze controle toont een knipperende tekst. Net als het MarqueeBorder besturingselement wordt de snelheid waarmee de tekst knippert, bepaald door de eigenschap UpdatePeriod. Het MarqueeText besturingselement heeft ook de StartMarquee- en StopMarquee methoden die gemeenschappelijk zijn met het besturingselement MarqueeBorder.

Tijdens het ontwerp kunnen deze MarqueeControlRootDesigner twee besturingstypen worden toegevoegd aan een MarqueeControl in elke combinatie.

Algemene functies van de twee besturingselementen worden in een interface met de naam IMarqueeWidgetmeegerekend. Hierdoor kan de MarqueeControl alle aan Marquee gerelateerde kindercontroles ontdekken en hen een speciale behandeling geven.

Als u de periodieke animatiefunctie wilt implementeren, gebruikt u BackgroundWorker objecten uit de System.ComponentModel naamruimte. U kunt Timer objecten gebruiken, maar als er veel IMarqueeWidget objecten aanwezig zijn, kan de enkele UI-thread de animatie mogelijk niet bijhouden.

Om een onderliggend controle-element te maken voor uw aangepaste besturingselement

  1. Voeg een nieuw klasse-item toe aan het MarqueeControlLibrary project. Geef het nieuwe bronbestand een basisnaam van 'IMarqueeWidget'.

  2. Open het IMarqueeWidget bronbestand in de Code-editor en wijzig de declaratie van class in interface:

    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
    
    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
  3. Voeg de volgende code toe aan de IMarqueeWidget-interface om twee methoden en een eigenschap beschikbaar te maken die de animatie van de marqueetekst bewerken.

    // 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. Voeg een nieuw aangepast besturingselement toe aan het MarqueeControlLibrary project. Geef het nieuwe bronbestand een basisnaam van 'MarqueeText'.

  5. Sleep een BackgroundWorker-component van de Gereedschapskist naar uw MarqueeText-controle. Met dit onderdeel kan het MarqueeText besturingselement zichzelf asynchroon bijwerken.

  6. Stel in het venster Eigenschappen de eigenschappen van het BackgroundWorker onderdeel WorkerReportsProgress en WorkerSupportsCancellation in op true. Met deze instellingen kan het BackgroundWorker onderdeel periodiek de ProgressChanged gebeurtenis genereren en asynchrone updates annuleren.

    Zie BackgroundWorker Componentvoor meer informatie.

  7. Open het MarqueeText bronbestand in de Code-editor. Importeer bovenaan het bestand de volgende naamruimten:

    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. Wijzig de declaratie van MarqueeText om over te nemen van Label en de IMarqueeWidget-interface te implementeren:

    [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. Declareer de instantievariabelen die overeenkomen met de weergegeven eigenschappen en initialiseer deze in de constructor. Het isLit veld bepaalt of de tekst moet worden geschilderd in de kleur die wordt opgegeven door de eigenschap LightColor.

    // 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. Implementeer de IMarqueeWidget-interface.

    Met de methoden StartMarquee en StopMarquee worden de RunWorkerAsync en CancelAsync methoden van het BackgroundWorker onderdeel aangeroepen om de animatie te starten en te stoppen.

    De kenmerken Category en Browsable worden toegepast op de eigenschap UpdatePeriod, zodat deze wordt weergegeven in een aangepaste sectie van het venster Eigenschappen met de naam 'Marquee'.

    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. Implementeer de eigenschapstoegangen. U maakt twee eigenschappen beschikbaar voor clients: LightColor en DarkColor. De kenmerken Category en Browsable worden toegepast op deze eigenschappen, zodat de eigenschappen worden weergegeven in een aangepaste sectie van het venster Eigenschappen met de naam 'Marquee'.

    [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. Implementeer de handlers voor de DoWork- en ProgressChanged-gebeurtenissen van het BackgroundWorker onderdeel.

    De DoWork gebeurtenis-handler pauzeert voor het aantal milliseconden dat door UpdatePeriod is opgegeven, roept vervolgens de ProgressChanged gebeurtenis aan totdat je code de animatie stopt door CancelAsyncaan te roepen.

    De ProgressChanged gebeurtenis-handler schakelt de tekst tussen de lichte en donkere toestand om het uiterlijk van knipperen te geven.

    // 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. Overschrijf de methode OnPaint om de animatie in te schakelen.

    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. Druk op F6- om de oplossing te bouwen.

Het kindbesturingselement MarqueeBorder maken

Het MarqueeBorder besturingselement is iets geavanceerder dan het MarqueeText besturingselement. Het heeft meer eigenschappen en de animatie in de OnPaint methode is meer betrokken. In principe lijkt het op de MarqueeText controle.

Omdat het besturingselement MarqueeBorder onderliggende besturingselementen kan hebben, moet het rekening houden met Layout gebeurtenissen.

Het besturingselement MarqueeBorder maken

  1. Voeg een nieuw aangepast besturingselement toe aan het MarqueeControlLibrary project. Geef het nieuwe bronbestand een basisnaam van 'MarqueeBorder'.

  2. Sleep een BackgroundWorker onderdeel van de Werkbalk naar uw MarqueeBorder controle. Met dit onderdeel kan het MarqueeBorder besturingselement zichzelf asynchroon bijwerken.

  3. Stel in het venster Eigenschappen de eigenschappen van het BackgroundWorker onderdeel WorkerReportsProgress en WorkerSupportsCancellation in op true. Met deze instellingen kan het BackgroundWorker onderdeel periodiek de ProgressChanged gebeurtenis genereren en asynchrone updates annuleren. Zie BackgroundWorker Componentvoor meer informatie.

  4. Selecteer in het venster Eigenschappen de knop Gebeurtenissen. Koppel handlers voor de gebeurtenissen DoWork en ProgressChanged.

  5. Open het MarqueeBorder bronbestand in de Code-editor. Importeer bovenaan het bestand de volgende naamruimten:

    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. Wijzig de declaratie van MarqueeBorder om over te nemen van Panel en de IMarqueeWidget-interface te implementeren.

    [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. Declareer twee opsommingen voor het beheren van de status van de MarqueeBorder controle: MarqueeSpinDirection, die de richting bepaalt waarin de lichten rond de rand draaien en MarqueeLightShape, die de vorm van de lichten (vierkant of cirkelvormig) bepaalt. Plaats deze verklaringen vóór de MarqueeBorder klassedeclaratie.

    // 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. Declareer de instantievariabelen die overeenkomen met de weergegeven eigenschappen en initialiseer deze in de constructor.

    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. Implementeer de IMarqueeWidget-interface.

    Met de methoden StartMarquee en StopMarquee worden de RunWorkerAsync en CancelAsync methoden van het BackgroundWorker onderdeel aangeroepen om de animatie te starten en te stoppen.

    Omdat het besturingselement MarqueeBorder onderliggende besturingselementen kan bevatten, worden met de StartMarquee methode alle onderliggende besturingselementen opgesomd en worden StartMarquee aangeroepen voor besturingselementen die IMarqueeWidgetimplementeren. De methode StopMarquee heeft een vergelijkbare implementatie.

    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. Implementeer de eigenschapstoegangsors. Het besturingselement MarqueeBorder heeft verschillende eigenschappen voor het controleren van het uiterlijk.

    [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. Implementeer de handlers voor de DoWork- en ProgressChanged-gebeurtenissen van het BackgroundWorker onderdeel.

    De DoWork gebeurtenishandler slaapt gedurende het aantal milliseconden dat is opgegeven door UpdatePeriod, vervolgens wordt de ProgressChanged gebeurtenis verhoogd, totdat je code de animatie stopt door aanroep van CancelAsync.

    De ProgressChanged gebeurtenishandler verhoogt de positie van het "basislicht", van waaruit de licht-/donkere toestand van de andere lichten wordt bepaald, en roept de Refresh-methode aan om het besturingselement zichzelf opnieuw te laten tekenen.

    // 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. Implementeer de helpermethoden, IsLit en DrawLight.

    De methode IsLit bepaalt de kleur van een licht op een bepaalde positie. Lichten die 'verlicht' zijn, worden getekend in de kleur die wordt gegeven door de eigenschap LightColor en de lichten die 'donker' zijn, worden getekend in de kleur die wordt gegeven door de eigenschap DarkColor.

    De methode DrawLight tekent een licht met de juiste kleur, vorm en positie.

    // 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. Overschrijf de methoden OnLayout en OnPaint.

    De methode OnPaint tekent de lichten langs de randen van de MarqueeBorder controle.

    Omdat de methode OnPaint afhankelijk is van de afmetingen van het besturingselement MarqueeBorder, moet u deze aanroepen wanneer de indeling verandert. Hiervoor overschrijft u OnLayout en roept u Refreshaan.

    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
    

Een aangepaste ontwerpfunctie maken voor schaduw- en filtereigenschappen

De MarqueeControlRootDesigner-klasse biedt de implementatie voor de hoofdontwerper. Naast deze ontwerper, die fungeert op de MarqueeControl, hebt u een aangepaste ontwerper nodig die specifiek is gekoppeld aan de MarqueeBorder controle. Deze ontwerper biedt aangepast gedrag dat geschikt is in de context van de aangepaste root-ontwerper.

Met name zal de MarqueeBorderDesigner bepaalde eigenschappen van het MarqueeBorder besturingselement 'schaduwen' en filteren, waardoor de interactie met de ontwerpomgeving verandert.

Het onderscheppen van aanroepen naar de eigenschapstoegang van een component wordt 'schaduwen' genoemd. Hiermee kan een ontwerper de waarde bewaken die door de gebruiker is ingesteld en optioneel die waarde doorgeven aan de component die wordt ontworpen.

In dit voorbeeld worden de eigenschappen Visible en Enabled in de schaduw geplaatst door de MarqueeBorderDesigner, waardoor de gebruiker het MarqueeBorder besturingselement niet zichtbaar of uitgeschakeld kan maken tijdens de ontwerptijd.

Ontwerpers kunnen ook eigenschappen toevoegen en verwijderen. In dit voorbeeld wordt de eigenschap Padding tijdens het ontwerp verwijderd, omdat de MarqueeBorder de opvulling programmatisch instelt op basis van de grootte van de lichten die zijn opgegeven door de eigenschap LightSize.

De basisklasse voor MarqueeBorderDesigner is ComponentDesigner, die methoden bevat waarmee de kenmerken, eigenschappen en gebeurtenissen die tijdens het ontwerp door een besturingselement worden weergegeven, kunnen worden gewijzigd:

Wanneer u de openbare interface van een onderdeel wijzigt met behulp van deze methoden, volgt u deze regels:

  • Items toevoegen aan of verwijderen uit de PreFilter methoden alleen.

  • Bestaande items in de PostFilter-methoden alleen wijzigen

  • Roep altijd eerst de basis-implementatie aan in de PreFilter methoden

  • De basis-implementatie altijd het laatst aanroepen in de PostFilter-methoden

Het naleven van deze regels zorgt ervoor dat alle ontwerpers in de ontwerpomgeving een consistente weergave hebben van alle onderdelen die worden ontworpen.

De ComponentDesigner-klasse biedt een woordenlijst voor het beheren van de waarden van schaduweigenschappen, waardoor u geen specifieke instantievariabelen hoeft te maken.

Een aangepaste ontwerpfunctie maken voor schaduw- en filtereigenschappen

  1. Klik met de rechtermuisknop op de map Ontwerp en voeg een nieuwe klasse toe. Geef het bronbestand een basisnaam van MarqueeBorderDesigner.

  2. Open het bronbestand MarqueeBorderDesigner in de Code-editor. Importeer bovenaan het bestand de volgende naamruimten:

    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. Wijzig de declaratie van MarqueeBorderDesigner om over te nemen van ParentControlDesigner.

    Omdat het MarqueeBorder besturingselement onderliggende besturingselementen kan bevatten, erft MarqueeBorderDesigner van ParentControlDesigner, waarmee de interactie tussen ouder- en kindelementen wordt verwerkt.

    namespace MarqueeControlLibrary.Design
    {
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
  4. De basisimplementatie van PreFilterPropertiesoverschrijven.

    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. Implementeer de eigenschappen Enabled en Visible. Deze implementaties schaduwen de eigenschappen van het besturingselement.

    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
    

Wijzigingen in onderdelen verwerken

De MarqueeControlRootDesigner-klasse biedt de aangepaste ontwerpomgeving voor uw MarqueeControl-exemplaren. De meeste ontwerptijdfunctionaliteit wordt overgenomen van de DocumentDesigner-klasse. Uw code implementeert twee specifieke aanpassingen: het afhandelen van wijzigingen in onderdelen en het toevoegen van ontwerpopdrachten.

Wanneer gebruikers hun MarqueeControl-exemplaren ontwerpen, zal uw hoofdontwerper wijzigingen bijhouden in de MarqueeControl en de onderliggende controles. De ontwerpomgeving biedt een handige service, IComponentChangeService, voor het bijhouden van wijzigingen in de onderdeelstatus.

U verkrijgt een verwijzing naar deze service door een query uit te voeren op de omgeving met de methode GetService. Als de query is geslaagd, kan uw ontwerper een handler koppelen voor de ComponentChanged event en de taken uitvoeren die nodig zijn om een consistente toestand te behouden tijdens de ontwerpperiode.

In het geval van de klasse MarqueeControlRootDesigner roept u de Refresh methode aan voor elk IMarqueeWidget object dat is opgenomen in de MarqueeControl. Hierdoor wordt het IMarqueeWidget-object op de juiste wijze hersteld wanneer eigenschappen zoals die van de ouder, Size, worden gewijzigd.

Onderdelenwijzigingen afhandelen

  1. Open het MarqueeControlRootDesigner bronbestand in de Code-editor en overschrijf de methode Initialize. Roep de basis-implementatie van Initialize aan en voer een query uit voor de 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. Implementeer de OnComponentChanged gebeurtenisafhandelaar. Test het type van het verzendende onderdeel en roep de Refresh methode aan als het een IMarqueeWidgetis.

    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
    

Designer-werkwoorden toevoegen aan uw Custom Designer

Een ontwerpwerkwoord is een menuopdracht die is gekoppeld aan een gebeurtenis-handler. Designer-werkwoorden worden toegevoegd aan het snelmenu van een onderdeel tijdens het ontwerp. Zie DesignerVerbvoor meer informatie.

U voegt twee ontwerpopdrachten toe aan uw ontwerpers: Test uitvoeren en Test stoppen. Met deze werkwoorden kunt u het runtime-gedrag van de MarqueeControl bekijken tijdens de ontwerptijd. Deze werkwoorden worden toegevoegd aan MarqueeControlRootDesigner.

Wanneer Test uitvoeren wordt aangeroepen, roept de gebeurtenis-handler voor werkwoorden de StartMarquee methode aan op de MarqueeControl. Wanneer Stop Test wordt aangeroepen, zal de gebeurtenis-handler de methode StopMarquee aanroepen op de MarqueeControl. De implementatie van de StartMarquee- en StopMarquee methoden roepen deze methoden aan op ingesloten besturingselementen die IMarqueeWidgetimplementeren, zodat alle ingesloten IMarqueeWidget besturingselementen ook deelnemen aan de test.

Ontwerpwerkwoorden toevoegen aan uw aangepaste ontwerpers

  1. Voeg in de MarqueeControlRootDesigner klasse gebeurtenis-handlers toe met de naam OnVerbRunTest en 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. Verbind deze eventhandlers met hun bijbehorende ontwerperwerkwoorden. MarqueeControlRootDesigner neemt een DesignerVerbCollection over van de basisklasse. U maakt twee nieuwe DesignerVerb-objecten en voegt deze toe aan deze verzameling in de methode Initialize.

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

Een Custom UITypeEditor maken

Wanneer u een aangepaste ontwerptijdervaring voor gebruikers creëert, is het vaak wenselijk om een aangepaste interactie met het venster Eigenschappen te maken. U kunt dit doen door een UITypeEditorte maken.

Het besturingselement MarqueeBorder toont verschillende eigenschappen in het venster Eigenschappen. Twee van deze eigenschappen, MarqueeSpinDirection en MarqueeLightShape worden vertegenwoordigd door opsommingen. Om het gebruik van een ui-typeeditor te illustreren, heeft de eigenschap MarqueeLightShape een gekoppelde UITypeEditor-klasse.

Een aangepaste UI-type-editor maken

  1. Open het MarqueeBorder bronbestand in de Code-editor.

  2. Declareer in de definitie van de klasse MarqueeBorder een klasse met de naam LightShapeEditor die is afgeleid van UITypeEditor.

    // This class demonstrates the use of a custom UITypeEditor.
    // It allows the MarqueeBorder control's LightShape property
    // to be changed at design time using a customized UI element
    // that is invoked by the Properties window. The UI is provided
    // by the LightShapeSelectionControl class.
    internal class LightShapeEditor : UITypeEditor
    {
    
    ' 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. Declareer een IWindowsFormsEditorService exemplaarvariabele met de naam editorService.

    private IWindowsFormsEditorService editorService = null;
    
    Private editorService As IWindowsFormsEditorService = Nothing
    
  4. Overschrijf de methode GetEditStyle. Deze implementatie retourneert DropDown, waarmee de ontwerpomgeving wordt aangegeven hoe de LightShapeEditormoet worden weergegeven.

    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. Overschrijf de methode EditValue. Met deze implementatie wordt een query uitgevoerd op de ontwerpomgeving voor een IWindowsFormsEditorService-object. Als dit lukt, wordt er een LightShapeSelectionControlgemaakt. De methode DropDownControl wordt aangeroepen om de LightShapeEditorte starten. De retourwaarde van deze aanroep wordt geretourneerd naar de ontwerpomgeving.

    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
    

Een weergavebesturingselement maken voor uw Custom UITypeEditor

De eigenschap MarqueeLightShape ondersteunt twee soorten lichte vormen: Square en Circle. U maakt een aangepast besturingselement dat uitsluitend wordt gebruikt voor het grafisch weergeven van deze waarden in het venster Eigenschappen. Dit aangepaste besturingselement wordt door uw UITypeEditor gebruikt om met het eigenschappenvenster te communiceren.

Een weergavecontrole maken voor uw aangepaste UI-type-editor

  1. Voeg een nieuw UserControl-item toe aan het MarqueeControlLibrary project. Geef het nieuwe bronbestand een basisnaam van LightShapeSelectionControl.

  2. Sleep twee Panel besturingselementen uit de Werkset naar de LightShapeSelectionControl. Noem ze squarePanel en circlePanel. Rangschik ze naast elkaar. Stel de eigenschap Size van beide Panel besturingselementen in op (60, 60). Stel de eigenschap Location van het besturingselement squarePanel in op (8, 10). Stel de eigenschap Location van het besturingselement circlePanel in op (80, 10). Stel ten slotte de eigenschap Size van de LightShapeSelectionControl in op (150, 80).

  3. Open het LightShapeSelectionControl bronbestand in de Code-editor. Importeer bovenaan het bestand de System.Windows.Forms.Design naamruimte:

    Imports System.Windows.Forms.Design
    
    using System.Windows.Forms.Design;
    
  4. Implementeer Click gebeurtenishandlers voor de squarePanel en circlePanel bedieningselementen. Met deze methoden wordt CloseDropDown aangeroepen om de aangepaste UITypeEditor bewerkingssessie te beëindigen.

    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. Declareer een IWindowsFormsEditorService exemplaarvariabele met de naam editorService.

    Private editorService As IWindowsFormsEditorService
    
    private IWindowsFormsEditorService editorService;
    
  6. Declareer een MarqueeLightShape exemplaarvariabele met de naam lightShapeValue.

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
  7. Koppel in de LightShapeSelectionControl constructor de Click gebeurtenis-handlers aan de squarePanel- en circlePanel-controls voor de Click-gebeurtenissen. Definieer ook een overbelasting van een constructor die de MarqueeLightShape waarde uit de ontwerpomgeving toewijst aan het lightShapeValue veld.

    // 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. Ontkoppel de Click gebeurtenis-handlers in de 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 );
    }
    
    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. Klik in Solution Explorerop de knop Alle bestanden weergeven. Open het LightShapeSelectionControl.Designer.cs- of LightShapeSelectionControl.Designer.vb-bestand en verwijder de standaarddefinitie van de Dispose methode.

  10. Implementeer de eigenschap LightShape.

    // 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. Overschrijf de methode OnPaint. Deze implementatie tekent een gevuld vierkant en een cirkel. De geselecteerde waarde wordt ook gemarkeerd door een rand rond de ene of de andere vorm te tekenen.

    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
    

Test uw aangepaste controle in de ontwerpfunctie

Op dit moment kunt u het MarqueeControlLibrary project bouwen. Test uw implementatie door een besturingselement te maken dat wordt overgenomen van de MarqueeControl-klasse en deze op een formulier gebruikt.

Een aangepaste MarqueeControl-implementatie maken

  1. Open DemoMarqueeControl in de Windows Forms Designer. Hiermee maakt u een exemplaar van het DemoMarqueeControl type en wordt dit weergegeven in een exemplaar van het MarqueeControlRootDesigner type.

  2. Open de MarqueeControlLibrary Components tab in de Werkset. U ziet de MarqueeBorder en MarqueeText bedieningselementen die beschikbaar zijn voor selectie.

  3. Sleep een exemplaar van het besturingselement MarqueeBorder naar het ontwerpoppervlak van de DemoMarqueeControl. Plaats dit MarqueeBorder besturingselement op het bovenliggende besturingselement.

  4. Sleep een exemplaar van het besturingselement MarqueeText naar het ontwerpoppervlak van de DemoMarqueeControl.

  5. Bouw de oplossing.

  6. Klik met de rechtermuisknop op de DemoMarqueeControl en selecteer in het snelmenu de optie Test uitvoeren om de animatie te starten. Klik op Stop Test om de animatie te stoppen.

  7. Open Form1- in de ontwerpweergave.

  8. Plaats twee Button bedieningselementen op het formulier. Noem ze startButton en stopButtonen wijzig respectievelijk de eigenschapswaarden van de Text in Start en Stop.

  9. Implementeer Click event handlers voor beide Button besturingselementen.

  10. Open in de Werksethet tabblad MarqueeControlTest Components. U ziet dat DemoMarqueeControl beschikbaar is voor selectie.

  11. Sleep een exemplaar van DemoMarqueeControl naar het ontwerpoppervlak Form1.

  12. Roep in de Click gebeurtenis-handlers de methoden Start en Stop op de DemoMarqueeControlaan.

    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. Stel het MarqueeControlTest project in als het opstartproject en voer het uit. U ziet het formulier dat uw DemoMarqueeControlweergeeft. Selecteer de knop Start om de animatie te starten. U ziet dat de tekst knippert en de lichten langs de rand bewegen.

Volgende stappen

De MarqueeControlLibrary toont een eenvoudige implementatie van aangepaste besturingselementen en bijbehorende ontwerpers. U kunt dit voorbeeld op verschillende manieren geavanceerder maken:

  • Wijzig de eigenschapswaarden voor de DemoMarqueeControl in de ontwerpfunctie. Voeg meer MarqueBorder-besturingselementen toe en dock ze binnen hun bovenliggende instanties om een geneste structuur te creëren. Experimenteer met verschillende instellingen voor de UpdatePeriod en de lichtgerelateerde eigenschappen.

  • Uw eigen implementaties van IMarqueeWidgetontwerpen. U kunt bijvoorbeeld een knipperend 'neonteken' of een geanimeerde teken met meerdere afbeeldingen maken.

  • Pas de ontwerp-tijdervaring verder aan. U kunt meer eigenschappen proberen te schaduwen dan Enabled en Visible, en u kunt nieuwe eigenschappen toevoegen. Voeg nieuwe ontwerpcommando's toe om veelvoorkomende taken te vereenvoudigen, zoals het verankeren van subbesturingselementen.

  • Geef een licentie aan MarqueeControl.

  • Bepalen hoe uw besturingselementen worden geserialiseerd en hoe code voor deze besturingselementen wordt gegenereerd. Zie dynamische broncode genereren en compilerenvoor meer informatie.

Zie ook