Dela via


Genomgång: Skapa en kontroll som drar nytta av designtidsfunktioner

Designtidsupplevelsen för en anpassad kontroll kan förbättras genom att skapa en associerad anpassad designer.

Försiktighet

Det här innehållet skrevs för .NET Framework. Om du använder .NET 6 eller en senare version använder du det här innehållet med försiktighet. Designersystemet har ändrats för Windows Forms och det är viktigt att du granskar Designer-ändringarna sedan artikeln om .NET Framework.

Den här artikeln visar hur du skapar en anpassad designer för en anpassad kontroll. Du implementerar en MarqueeControl typ och en associerad designerklass med namnet MarqueeControlRootDesigner.

Den MarqueeControl typen implementerar en skärm som liknar en teaterram med animerade lampor och blinkande text.

Designern för den här kontrollen interagerar med designmiljön för att ge en anpassad designtidsupplevelse. Med den anpassade designern kan du sätta ihop en anpassad MarqueeControl implementering med animerade lampor och blinkande text i många kombinationer. Du kan använda den sammansatta kontrollen i ett formulär som alla andra Windows Forms-kontroller.

När du är klar med den här genomgången ser din anpassade kontroll ut ungefär så här:

Appen visar en markeringsram med texten Text och knapparna Start och Stopp.

Den fullständiga kodlistan finns i Hur man: Skapar en Windows Forms-kontroll som utnyttjar Design-Time Funktioner.

Förutsättningar

För att slutföra den här genomgången behöver du Visual Studio.

Skapa projektet

Det första steget är att skapa programprojektet. Du använder det här projektet för att skapa det program som är värd för den anpassade kontrollen.

Skapa ett nytt Windows Forms-programprojekt i Visual Studio och ge det namnet MarqueeControlTest.

Skapa kontrollbiblioteksprojektet

  1. Lägg till ett Windows Forms Control Library-projekt i lösningen. Ge projektet namnet MarqueeControlLibrary.

  2. Med Solution Explorertar du bort projektets standardkontroll genom att ta bort källfilen med namnet "UserControl1.cs" eller "UserControl1.vb", beroende på vilket språk du väljer.

  3. Lägg till ett nytt UserControl objekt i MarqueeControlLibrary-projektet. Ge den nya källfilen ett basnamn för MarqueeControl.

  4. Använd Solution Exploreroch skapa en ny mapp i MarqueeControlLibrary-projektet.

  5. Högerklicka på mappen Design och lägg till en ny klass. Ge den namnet MarqueeControlRootDesigner.

  6. Du måste använda typer från System.Design sammansättning, så lägg till den här referensen i MarqueeControlLibrary-projektet.

Referera till projektet för anpassad kontroll

Du kommer att använda projektet MarqueeControlTest för att testa den anpassade kontrollen. Testprojektet blir medvetet om den anpassade kontrollen när du lägger till en projektreferens till MarqueeControlLibrary sammansättning.

I MarqueeControlTest-projektet lägger du till en projektreferens till assemblyn MarqueeControlLibrary. Använd fliken Projects i dialogrutan Lägg till referens istället för att referera direkt till MarqueeControlLibrary assembly.

Definiera en anpassad kontroll och den anpassade designern

Din anpassade kontroll härleds från klassen UserControl. Detta gör att din kontroll kan innehålla andra kontroller, och det ger din kontroll en hel del standardfunktioner.

Din anpassade kontroll kommer ha ett associerat anpassat designverktyg. På så sätt kan du skapa en unik designupplevelse som skräddarsytts specifikt för din anpassade kontroll.

Du associerar kontrollen med dess designer med hjälp av klassen DesignerAttribute. Eftersom du utvecklar hela designtidsbeteendet för din anpassade kontroll implementerar den anpassade designern IRootDesigner-gränssnittet.

Så här definierar du en anpassad kontroll och dess anpassade designer

  1. Öppna MarqueeControl-källfilen i Code Editor. Importera följande namnområden överst i filen:

    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. Lägg till DesignerAttribute i MarqueeControl-klassdeklarationen. Detta associerar den anpassade kontrollen med dess designer.

    [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
    public class MarqueeControl : UserControl
    {
    
    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
  3. Öppna MarqueeControlRootDesigner-källfilen i Code Editor. Importera följande namnområden överst i filen:

    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. Ändra deklarationen för MarqueeControlRootDesigner så att den ärver från klassen DocumentDesigner. Använd ToolboxItemFilterAttribute för att ange designerens interaktion med Toolbox.

    Not

    Definitionen för klassen MarqueeControlRootDesigner har omslutits av ett namnområde kallat MarqueeControlLibrary.Design. Den här deklarationen placerar designern i ett särskilt namnområde som är reserverat för designrelaterade typer.

    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. Definiera konstruktorn för klassen MarqueeControlRootDesigner. Infoga en WriteLine-instruktion i konstruktorns brödtext. Detta är användbart för felsökning.

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

Skapa en instans av din anpassade kontroll

  1. Lägg till ett nytt UserControl objekt i MarqueeControlTest-projektet. Ge den nya källfilen ett basnamn för DemoMarqueeControl.

  2. Öppna filen DemoMarqueeControl i Code Editor. Överst i filen, importera namnrymd MarqueeControlLibrary:

    Imports MarqueeControlLibrary
    
    using MarqueeControlLibrary;
    
  3. Ändra deklarationen för DemoMarqueeControl så att den ärver från klassen MarqueeControl.

  4. Skapa projektet.

  5. Öppna Formulär1 i Windows Forms Designer.

  6. Leta upp fliken MarqueeControlTest Components i Toolbox och öppna den. Dra en DemoMarqueeControl från Toolbox- till formuläret.

  7. Skapa projektet.

Konfigurera projektet för Design-Time-debuggning

När du utvecklar en anpassad designtidsupplevelse är det nödvändigt att felsöka dina kontroller och komponenter. Det finns ett enkelt sätt att konfigurera projektet så att felsökning kan utföras vid designtillfället. Mer information finns i Genomgång: Felsöka anpassade Windows Forms-kontroller under designtid.

  1. Högerklicka på projektet MarqueeControlLibrary och välj Egenskaper.

  2. I dialogrutan MarqueeControlLibrary Property Pages väljer du sidan Felsöka.

  3. I avsnittet Starta åtgärd väljer du Starta externt program. Du kommer att felsöka en separat instans av Visual Studio, så klicka på ellipsen (knappen Ellips (...) i fönstret Egenskaper i Visual Studio) för att bläddra efter Visual Studio IDE. Namnet på den körbara filen är devenv.exeoch om du har installerat till standardplatsen är sökvägen %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.

  4. Välj OK för att stänga dialogrutan.

  5. Högerklicka på Projektet MarqueeControlLibrary och välj Ange som StartUp Project för att aktivera den här felsökningskonfigurationen.

Kontrollpunkt

Nu är du redo att felsöka designtidsbeteendet för din anpassade kontroll. När du har fastställt att felsökningsmiljön har konfigurerats korrekt testar du associationen mellan den anpassade kontrollen och den anpassade designern.

Testa felsökningsmiljön och designerassociationen

  1. Öppna källfilen MarqueeControlRootDesigner i Code Editor och placera en brytpunkt på WriteLine-instruktionen.

  2. Tryck på F5- för att starta felsökningssessionen.

    En ny instans av Visual Studio skapas.

  3. I den nya instansen av Visual Studio öppnar du Lösningen MarqueeControlTest. Du kan enkelt hitta lösningen genom att välja Senaste projekt från menyn Arkiv. Den MarqueeControlTest.sln lösningsfilen visas som den senast använda filen.

  4. Öppna DemoMarqueeControl i designern.

    Felsökningsinstansen av Visual Studio får fokus och körningen stannar vid din brytpunkt. Tryck på F5 för att fortsätta felsökningssessionen.

Nu är allt på plats för att du ska kunna utveckla och felsöka din anpassade kontroll och dess associerade anpassade designer. Resten av den här artikeln fokuserar på information om hur du implementerar funktioner i kontrollen och designern.

Implementera anpassad kontroll

MarqueeControl är en UserControl med lite anpassning. Den visar två metoder: Start, som startar markeringsramens animering och Stop, som stoppar animeringen. Eftersom MarqueeControl innehåller underordnade kontroller som implementerar IMarqueeWidget-gränssnittet Start och Stop räkna upp varje underordnad kontroll och anropa metoderna StartMarquee respektive StopMarquee på varje underordnad kontroll som implementerar IMarqueeWidget.

Utseendet på kontrollerna MarqueeBorder och MarqueeText är beroende av layouten, så MarqueeControl åsidosätter metoden OnLayout och anropar PerformLayout på underordnade kontroller av den här typen.

Det här är omfattningen av MarqueeControl-anpassningarna. Körningsfunktionerna implementeras av kontrollerna MarqueeBorder och MarqueeText och designtidsfunktionerna implementeras av MarqueeBorderDesigner- och MarqueeControlRootDesigner-klasserna.

Implementera din anpassade kontroll

  1. Öppna MarqueeControl-källfilen i Code Editor. Implementera metoderna Start och 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. Åsidosätt metoden 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
    

Skapa en barnkontroll för din egen anpassade kontroll

MarqueeControl är värd för två typer av barnkontroll: MarqueeBorder-kontroll och MarqueeText-kontroll.

  • MarqueeBorder: Den här kontrollen ritar en kant av "ljus" runt kanterna. Lamporna blinkar i följd, så de verkar röra sig runt kanten. Hastigheten med vilken lamporna blinkar styrs av en egenskap som kallas UpdatePeriod. Flera andra anpassade egenskaper bestämmer andra aspekter av kontrollens utseende. Två metoder, som kallas StartMarquee och StopMarquee, styr när animeringen startar och stoppas.

  • MarqueeText: Det här kontrollelementet renderar en blinkande sträng. Precis som den MarqueeBorder kontrollen styrs den hastighet med vilken texten blinkar av egenskapen UpdatePeriod. MarqueeText-kontrollen har också de StartMarquee- och StopMarquee metoder som är gemensamma för MarqueeBorder kontroll.

Vid designtillfället gör MarqueeControlRootDesigner att dessa två kontrolltyper kan läggas till i en MarqueeControl i valfri kombination.

Vanliga funktioner i de två kontrollerna räknas in i ett gränssnitt som kallas IMarqueeWidget. Detta gör det möjligt för MarqueeControl att upptäcka alla Marquee-relaterade barnkontroller och ge dem särskild behandling.

Om du vill implementera funktionen periodisk animering använder du BackgroundWorker objekt från System.ComponentModel-namnområdet. Du kan använda Timer objekt, men när många IMarqueeWidget objekt finns kan det hända att den enda användargränssnittstråden inte kan hänga med i animeringen.

Skapa en barnkontroll för din anpassade kontroll

  1. Lägg till ett nytt klassobjekt i MarqueeControlLibrary-projektet. Ge den nya källfilen ett basnamn för "IMarqueeWidget".

  2. Öppna IMarqueeWidget-källfilen i Code Editor och ändra deklarationen från class till 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. Lägg till följande kod i IMarqueeWidget-gränssnittet för att exponera två metoder och en egenskap som ändrar markeringsramens animering:

    // 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. Lägg till ett nytt anpassat objekt i MarqueeControlLibrary-projektet. Ge den nya källfilen ett basnamn för "MarqueeText".

  5. Dra en BackgroundWorker komponent från Toolbox- till din MarqueeText kontroll. Med den här komponenten kan MarqueeText-kontrollen uppdateras asynkront.

  6. I fönstret Egenskaper anger du BackgroundWorker komponentens egenskaper för WorkerReportsProgress och WorkerSupportsCancellation till sant. Med de här inställningarna kan BackgroundWorker komponenten regelbundet generera händelsen ProgressChanged och avbryta asynkrona uppdateringar.

    Mer information finns i BackgroundWorker Component.

  7. Öppna MarqueeText-källfilen i Code Editor. Importera följande namnområden överst i filen:

    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. Ändra deklarationen av MarqueeText så att den ärver från Label och implementera IMarqueeWidget-gränssnittet:

    [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. Deklarera de instansvariabler som motsvarar de exponerade egenskaperna och initiera dem i konstruktorn. Fältet isLit avgör om texten ska målas i den färg som anges av egenskapen 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. Implementera IMarqueeWidget-gränssnittet.

    Metoderna StartMarquee och StopMarquee anropar BackgroundWorker komponentens RunWorkerAsync- och CancelAsync metoder för att starta och stoppa animeringen.

    Attributen Category och Browsable tillämpas på egenskapen UpdatePeriod så att den visas i ett anpassat avsnitt i fönstret Egenskaper med namnet "Markeringsram".

    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. Implementera egenskapsåtkomsterna. Du exponerar två egenskaper för klienter: LightColor och DarkColor. Attributen Category och Browsable tillämpas på dessa egenskaper, så egenskaperna visas i ett anpassat avsnitt i fönstret Egenskaper med namnet "Markeringsram".

    [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. Implementera hanterare för BackgroundWorker komponentens DoWork- och ProgressChanged-händelser.

    Händelsehanteraren DoWork väntar det antal millisekunder som specificeras av UpdatePeriod och genererar sedan händelsen ProgressChanged tills din kod stoppar animeringen genom att anropa CancelAsync.

    Händelsehanteraren ProgressChanged växlar texten mellan ljus och mörk för att ge intrycket av att blinka.

    // 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. Åsidosätt metoden OnPaint för att aktivera animeringen.

    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. Tryck på F6 för att skapa lösningen.

Skapa barnkontrollen MarqueeBorder

Den MarqueeBorder kontrollen är något mer avancerad än den MarqueeText kontrollen. Den har fler egenskaper och animeringen i metoden OnPaint är mer involverad. I princip liknar det MarqueeText-kontrollen.

Eftersom MarqueeBorder-kontrollen kan ha underordnade kontroller måste den vara medveten om Layout-händelser.

Så här skapar du MarkisBorder-kontrollen

  1. Lägg till ett nytt anpassat-objekt i MarqueeControlLibrary-projektet. Ge den nya källfilen ett basnamn för "MarqueeBorder".

  2. Dra en komponent BackgroundWorker från Toolbox- till din MarqueeBorder-kontroll. Med den här komponenten kan MarqueeBorder-kontrollen uppdateras asynkront.

  3. I fönstret Egenskaper ställer du in BackgroundWorker komponentens egenskaperna WorkerReportsProgress och WorkerSupportsCancellation till sant. Med de här inställningarna kan BackgroundWorker komponenten regelbundet generera händelsen ProgressChanged och avbryta asynkrona uppdateringar. Mer information finns i BackgroundWorker Component.

  4. I fönstret Egenskaper väljer du knappen Händelser. Koppla hanterare för händelserna DoWork och ProgressChanged.

  5. Öppna MarqueeBorder-källfilen i Code Editor. Importera följande namnområden överst i filen:

    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. Ändra deklarationen för MarqueeBorder till att ärva från Panel och implementera IMarqueeWidget-gränssnittet.

    [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. Deklarera två uppräkningar för att hantera MarqueeBorder kontrolltillstånd: MarqueeSpinDirection, som bestämmer i vilken riktning lamporna "snurrar" runt kantlinjen och MarqueeLightShape, som bestämmer ljusens form (kvadrat eller cirkel). Placera dessa deklarationer före klassdeklarationen för MarqueeBorder.

    // 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. Deklarera de instansvariabler som motsvarar de exponerade egenskaperna och initiera dem i konstruktorn.

    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. Implementera IMarqueeWidget-gränssnittet.

    Metoderna StartMarquee och StopMarquee anropar BackgroundWorker komponentens RunWorkerAsync- och CancelAsync metoder för att starta och stoppa animeringen.

    Eftersom kontrollen MarqueeBorder kan innehålla underordnade kontroller, räknar StartMarquee-metoden upp alla underordnade kontroller och anropar StartMarquee för de som implementerar IMarqueeWidget. Metoden StopMarquee har en liknande implementering.

    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. Implementera egenskapsåtkomsterna. Kontrollen MarqueeBorder har flera egenskaper för att kontrollera dess utseende.

    [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. Implementera hanterare för BackgroundWorker komponentens DoWork- och ProgressChanged-händelser.

    Händelsehanteraren DoWork vilar under det antal millisekunder som anges av UpdatePeriod, och utlöser sedan händelsen ProgressChanged, tills din kod stoppar animeringen genom att anropa CancelAsync.

    Händelsehanteraren för ProgressChanged ökar positionen för "basljuset", från vilket de andra lampornas ljus/mörka tillstånd bestäms, och anropar metoden Refresh för att få kontrollen att måla om sig själv.

    // 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. Implementera hjälpmetoderna, IsLit och DrawLight.

    Metoden IsLit bestämmer färgen på ett ljus vid en viss position. Ljus som är "tända" ritas i den färg som ges av egenskapen LightColor, och de som är "mörka" ritas i den färg som ges av egenskapen DarkColor.

    Metoden DrawLight ritar ett ljus med lämplig färg, form och position.

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

    Metoden OnPaint ritar lamporna längs kanterna på MarqueeBorder kontrollen.

    Eftersom OnPaint-metoden är beroende av dimensionerna för MarqueeBorder-kontrollen måste du anropa den när layouten ändras. För att uppnå detta åsidosätter du OnLayout och anropar Refresh.

    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
    

Skapa en anpassad designer för skugg- och filteregenskaper

Klassen MarqueeControlRootDesigner tillhandahåller implementeringen för rotdesignern. Förutom den här designern, som fungerar på MarqueeControl, behöver du en anpassad designer som är specifikt associerad med MarqueeBorder-kontrollen. Den här designern tillhandahåller ett anpassat beteende som är lämpligt i kontexten för den anpassade rotdesignern.

Mer specifikt kommer MarqueeBorderDesigner att "skugga" och filtrera vissa egenskaper på MarqueeBorder kontroll, vilket ändrar deras interaktion med designmiljön.

Att fånga upp anrop till en komponents egenskapsåtkomstor kallas "skuggning". Det gör att en designer kan spåra värdet som angetts av användaren och eventuellt skicka det värdet till komponenten som utformas.

I det här exemplet skuggas egenskaperna Visible och Enabled av MarqueeBorderDesigner, vilket hindrar användaren från att göra MarqueeBorder kontrollen osynlig eller inaktiverad under designtiden.

Designers kan också lägga till och ta bort egenskaper. I det här exemplet tas egenskapen Padding bort vid designtillfället, eftersom MarqueeBorder-kontrollen programmatiskt anger utfyllnad baserat på storleken på de lampor som anges av egenskapen LightSize.

Basklassen för MarqueeBorderDesigner är ComponentDesigner, som har metoder som kan ändra attribut, egenskaper och händelser som exponeras av en kontroll vid designtillfället:

När du ändrar det offentliga gränssnittet för en komponent med hjälp av dessa metoder följer du dessa regler:

  • Lägg endast till eller ta bort objekt i PreFilter metoder

  • Ändra endast befintliga objekt i PostFilter metoder

  • Anropa alltid basimplementeringen först i PreFilter metoder

  • Anropa alltid basimplementeringen sist i de PostFilter metoderna

Genom att följa dessa regler ser du till att alla designers i designmiljön har en konsekvent vy över alla komponenter som utformas.

Klassen ComponentDesigner innehåller en ordlista för att hantera värdena för skuggade egenskaper, vilket gör att du slipper skapa specifika instansvariabler.

Skapa en anpassad designer för att skugga och filtrera egenskaper

  1. Högerklicka på mappen Design och lägg till en ny klass. Ge källfilen ett basnamn för MarqueeBorderDesigner.

  2. Öppna källfilen MarqueeBorderDesigner i Code Editor. Importera följande namnområden överst i filen:

    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. Ändra deklarationen för MarqueeBorderDesigner så att den ärver från ParentControlDesigner.

    Eftersom kontrollen MarqueeBorder kan innehålla barnkontroller ärver MarqueeBorderDesigner från ParentControlDesigner, som hanterar interaktionen mellan förälder och barn.

    namespace MarqueeControlLibrary.Design
    {
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
  4. Åsidosätt basimplementeringen av PreFilterProperties.

    protected override void PreFilterProperties(IDictionary properties)
    {
        base.PreFilterProperties(properties);
    
        if (properties.Contains("Padding"))
        {
            properties.Remove("Padding");
        }
    
        properties["Visible"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Visible"],
            new Attribute[0]);
    
        properties["Enabled"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Enabled"],
            new Attribute[0]);
    }
    
    Protected Overrides Sub PreFilterProperties( _
    ByVal properties As IDictionary)
    
        MyBase.PreFilterProperties(properties)
    
        If properties.Contains("Padding") Then
            properties.Remove("Padding")
        End If
    
        properties("Visible") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Visible"), PropertyDescriptor), _
        New Attribute(-1) {})
    
        properties("Enabled") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Enabled"), _
        PropertyDescriptor), _
        New Attribute(-1) {})
    
    End Sub
    
  5. Implementera egenskaperna Enabled och Visible. Dessa implementeringar skuggar kontrollens egenskaper.

    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
    

Hantera komponentändringar

Klassen MarqueeControlRootDesigner ger en anpassad designtidsupplevelse för dina MarqueeControl-instanser. De flesta designtidsfunktionerna ärvs från klassen DocumentDesigner. Koden implementerar två specifika anpassningar: hantering av komponentändringar och tillägg av designerverb.

När användarna utformar sina MarqueeControl instanser spårar rotdesignern ändringar i MarqueeControl och dess underordnade kontroller. Designmiljön erbjuder en praktisk tjänst, IComponentChangeService, för att spåra ändringar i komponenttillståndet.

Du skaffar en referens till den här tjänsten genom att fråga systemmiljön med metoden GetService. Om frågan lyckas kan din designer koppla en hanterare för händelsen ComponentChanged och utföra de uppgifter som krävs för att upprätthålla ett konsekvent tillstånd under designstadiet.

När det gäller klassen MarqueeControlRootDesigner anropar du metoden Refresh på varje IMarqueeWidget objekt som finns i MarqueeControl. Detta gör att IMarqueeWidget-objektet målas om på rätt sätt när egenskaper som dess överordnade Size ändras.

Hantera komponentändringar

  1. Öppna MarqueeControlRootDesigner-källfilen i Code Editor och åsidosätt metoden Initialize. Anropa basimplementeringen av Initialize och fråga efter 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. Implementera OnComponentChanged händelsehanterare. Testa den sändande komponentens typ och om det är en IMarqueeWidgetanropar du dess Refresh-metod.

    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
    

Lägg till designerverb i din anpassade designer

Ett designerverb är ett menykommando som är länkat till en händelsehanterare. Designerverb läggs till i en komponents snabbmeny vid designtillfället. Mer information finns i DesignerVerb.

Du lägger till två designerverb till dina designers: Kör test och Stoppa test. Med hjälp av dessa verb kan du visa körningsbeteendet för MarqueeControl under designfasen. Dessa verb läggs till i MarqueeControlRootDesigner.

När Kör test anropas anropar verbhändelsehanteraren metoden StartMarqueeMarqueeControl. När Stop Test anropas anropar verbhändelsehanteraren metoden StopMarqueeMarqueeControl. Implementeringen av metoderna StartMarquee och StopMarquee anropar dessa metoder på inneslutna kontroller som implementerar IMarqueeWidget, vilket gör att alla inneslutna IMarqueeWidget-kontroller också kommer att delta i testet.

Så här lägger du till designerverb i dina anpassade designerverktyg

  1. I klassen MarqueeControlRootDesigner lägger du till händelsehanterare med namnet OnVerbRunTest och 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. Anslut dessa händelsehanterare till motsvarande designerverb. MarqueeControlRootDesigner ärver en DesignerVerbCollection från basklassen. Du skapar två nya DesignerVerb objekt och lägger till dem i den här samlingen i metoden 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)))
    

Skapa en anpassad användargränssnittsredigerare

När du skapar en anpassad designtidsupplevelse för användare är det ofta önskvärt att skapa en anpassad interaktion med fönstret Egenskaper. Du kan göra detta genom att skapa en UITypeEditor.

Kontrollen MarqueeBorder exponerar flera egenskaper i fönstret Egenskaper. Två av dessa egenskaper, MarqueeSpinDirection och MarqueeLightShape representeras av uppräkningar. För att illustrera användningen av en redigerare av användargränssnittstyp har egenskapen MarqueeLightShape en associerad UITypeEditor-klass.

Så här skapar du en redigerare av anpassad användargränssnittstyp

  1. Öppna MarqueeBorder-källfilen i Code Editor.

  2. I definitionen av klassen MarqueeBorder deklarerar du en klass med namnet LightShapeEditor som härleds från 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. Deklarera en IWindowsFormsEditorService instansvariabel med namnet editorService.

    private IWindowsFormsEditorService editorService = null;
    
    Private editorService As IWindowsFormsEditorService = Nothing
    
  4. Åsidosätt metoden GetEditStyle. Den här implementeringen returnerar DropDown, som talar om för designmiljön hur du visar LightShapeEditor.

    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. Åsidosätt metoden EditValue. Den här implementeringen frågar designmiljön efter ett IWindowsFormsEditorService objekt. Om det lyckas skapar den en LightShapeSelectionControl. Metoden DropDownControl anropas för att starta LightShapeEditor. Returvärdet från det här anropet returneras till designmiljön.

    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
    

Skapa en vykontroll för din anpassade UITypeEditor

Egenskapen MarqueeLightShape stöder två typer av ljusformer: Square och Circle. Du skapar en anpassad kontroll som endast används för att grafiskt visa dessa värden i fönstret Egenskaper. Den här anpassade kontrollen används av din UITypeEditor för att interagera med fönstret Egenskaper.

Så här skapar du en vykontroll för din anpassade redigerare av användargränssnittstyp

  1. Lägg till ett nytt UserControl objekt i MarqueeControlLibrary-projektet. Ge den nya källfilen ett basnamn för LightShapeSelectionControl.

  2. Dra två Panel-kontroller från -verktygslådan till LightShapeSelectionControl. Ge dem namnet squarePanel och circlePanel. Ordna dem sida vid sida. Ange egenskapen Size för båda Panel kontrollerna till (60, 60). Ange egenskapen Location för kontrollen squarePanel till (8, 10). Ange egenskapen Location för kontrollen circlePanel till (80, 10). Ange slutligen egenskapen Size för LightShapeSelectionControl till (150, 80).

  3. Öppna LightShapeSelectionControl-källfilen i Code Editor. Importera namnrymden System.Windows.Forms.Design överst i den filen.

    Imports System.Windows.Forms.Design
    
    using System.Windows.Forms.Design;
    
  4. Implementera Click händelsehanterare för kontrollerna squarePanel och circlePanel. Dessa metoder anropar CloseDropDown för att avsluta den anpassade UITypeEditor redigeringssessionen.

    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. Deklarera en IWindowsFormsEditorService instansvariabel med namnet editorService.

    Private editorService As IWindowsFormsEditorService
    
    private IWindowsFormsEditorService editorService;
    
  6. Deklarera en MarqueeLightShape instansvariabel med namnet lightShapeValue.

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
  7. I LightShapeSelectionControl konstruktorn kopplar du Click händelsehanterare till squarePanel- och circlePanel-kontrollernas Click-händelser. Definiera också en konstruktoröverladdning som tilldelar MarqueeLightShape-värdet från designmiljön till fältet lightShapeValue.

    // 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. I metoden Dispose kopplar du bort Click händelsehanterare.

    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. I Solution Explorerklickar du på knappen Visa alla filer. Öppna filen LightShapeSelectionControl.Designer.cs eller LightShapeSelectionControl.Designer.vb och ta bort standarddefinitionen för metoden Dispose.

  10. Implementera egenskapen 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. Åsidosätt metoden OnPaint. Den här implementeringen ritar en fylld fyrkant och cirkel. Det markerar också det markerade värdet genom att rita en kantlinje runt den ena eller den andra formen.

    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
    

Testa din anpassade kontroll i designern

Nu kan du skapa MarqueeControlLibrary projektet. Testa implementeringen genom att skapa en kontroll som ärver från klassen MarqueeControl och använda den i ett formulär.

Så här skapar du en anpassad MarqueeControl-implementering

  1. Öppna DemoMarqueeControl i Windows Forms Designer. Detta skapar en instans av DemoMarqueeControl typ och visar den i en instans av MarqueeControlRootDesigner typ.

  2. Öppna fliken MarqueeControlLibrary Components i Toolbox. Du ser kontrollerna MarqueeBorder och MarqueeText tillgängliga för markering.

  3. Dra en instans av MarqueeBorder-kontrollen till DemoMarqueeControl-designområdet. Docka den här MarqueeBorder-kontrollen till föräldrakontrollen.

  4. Dra en instans av MarqueeText-kontrollen till designytan DemoMarqueeControl.

  5. Skapa lösningen.

  6. Högerklicka på DemoMarqueeControl och välj alternativet Kör test på snabbmenyn för att starta animeringen. Klicka på Stoppa testet för att stoppa animationen.

  7. Öppna Form1 i designvyn.

  8. Placera två Button kontroller i formuläret. Ge dem namnet startButton och stopButtonoch ändra egenskapsvärdena för Text till Start respektive Stop.

  9. Implementera Click händelsehanterare för båda Button kontroller.

  10. Öppna fliken MarqueeControlTest Components i Toolbox. Du ser DemoMarqueeControl tillgängliga för val.

  11. Dra en instans av DemoMarqueeControl på designytan Form1.

  12. I Click händelsehanterare anropar du metoderna Start och StopDemoMarqueeControl.

    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. Ange MarqueeControlTest projektet som startprojekt och kör det. Du kommer att se formuläret som visar din DemoMarqueeControl. Välj knappen Starta för att starta animeringen. Du bör se texten blinka och lamporna som rör sig runt kantlinjen.

Nästa steg

MarqueeControlLibrary visar en enkel implementering av anpassade kontroller och tillhörande designers. Du kan göra det här exemplet mer avancerat på flera sätt:

  • Ändra egenskapsvärdena för DemoMarqueeControl i designern. Lägg till fler MarqueBorder kontroller och docka dem i sina överordnade instanser för att skapa en kapslad effekt. Experimentera med olika inställningar för UpdatePeriod och ljusrelaterade egenskaper.

  • Skapa egna implementeringar av IMarqueeWidget. Du kan till exempel skapa en blinkande "neonskylt" eller en animerad skylt med flera bilder.

  • Anpassa designtidsupplevelsen ytterligare. Du kan prova att skugga fler egenskaper än Enabled och Visible, och du kan lägga till nya egenskaper. Lägg till nya designerverb för att förenkla vanliga uppgifter som dockning av barnkontroller.

  • Bevilja licens för MarqueeControl.

  • Kontrollera hur dina kontroller serialiseras och hur kod genereras för dem. Mer information finns i dynamisk källkodsgenerering och kompilering.

Se även