Sdílet prostřednictvím


Návod: Vytvoření ovládacího prvku, který využívá funkce návrhu a času

Prostředí pro návrh vlastního ovládacího prvku je možné vylepšit vytvořením přidruženého vlastního návrháře.

Opatrnost

Tento obsah byl napsán pro rozhraní .NET Framework. Pokud používáte .NET 6 nebo novější verzi, použijte tento obsah s opatrností. Systém návrháře se změnil pro Windows Forms a je důležité, abyste si prošli změny návrháře od verze .NET Framework článku.

Tento článek ukazuje, jak vytvořit vlastního návrháře pro vlastní ovládací prvek. Implementujete typ MarqueeControl a přidruženou třídu návrháře s názvem MarqueeControlRootDesigner.

Typ MarqueeControl implementuje zobrazení podobné kino marquee s animovanými světly a blikajícím textem.

Návrhář pro tento ovládací prvek interaguje s návrhovým prostředím, aby poskytoval vlastní uživatelský zážitek při návrhu. Pomocí vlastního návrháře můžete sestavit vlastní implementaci MarqueeControl s animovanými světly a blikajícím textem v mnoha kombinacích. Sestavený ovládací prvek můžete použít na formuláři stejně jako jakýkoli jiný ovládací prvek Windows Forms.

Až tento názorný postup dokončíte, bude váš vlastní ovládací prvek vypadat přibližně takto:

Aplikace zobrazuje bleskovou zprávu s nápisem Text a tlačítka Start a Stop.

Úplný výpis kódu najdete v části Jak: Vytvořit ovládací prvek Windows Forms, který využívá vlastnosti Design-Time.

Požadavky

K dokončení tohoto návodu budete potřebovat Visual Studio.

Vytvoření projektu

Prvním krokem je vytvoření projektu aplikace. Tento projekt použijete k sestavení aplikace, která je hostitelem vlastního ovládacího prvku.

V sadě Visual Studio vytvořte nový projekt aplikace Windows Forms a pojmenujte ho MarqueeControlTest.

Vytvoření projektu knihovny ovládacích prvků

  1. Přidejte do řešení projekt knihovny ovládacích prvků Windows Forms. Pojmenujte projekt MarqueeControlLibrary.

  2. Pomocí Průzkumníka řešeníodstraňte výchozí ovládací prvek projektu odstraněním zdrojového souboru s názvem "UserControl1.cs" nebo "UserControl1.vb" v závislosti na zvoleném jazyce.

  3. Do projektu MarqueeControlLibrary přidejte novou položku UserControl. Dejte novému zdrojovému souboru základní název MarqueeControl.

  4. Pomocí Průzkumníka řešenívytvořte novou složku v projektu MarqueeControlLibrary.

  5. Klikněte pravým tlačítkem na složku Návrh a přidejte novou třídu. Pojmenujte ji MarqueeControlRootDesigner.

  6. Budete muset použít typy ze sestavení System.Design, takže tento odkaz přidejte do projektu MarqueeControlLibrary.

Odkazování na projekt vlastního ovládacího prvku

K otestování vlastního ovládacího prvku použijete projekt MarqueeControlTest. Testovací projekt rozpozná vlastní ovládací prvek, když přidáte odkaz na projekt do sestavení MarqueeControlLibrary.

V projektu MarqueeControlTest přidejte odkaz na projekt do sestavení MarqueeControlLibrary. Nezapomeňte použít kartu Projekty v dialogovém okně Přidat odkaz místo přímého odkazování na sestavení MarqueeControlLibrary.

Definování vlastního ovládacího prvku a vlastního návrháře

Váš vlastní ovládací prvek bude odvozen z třídy UserControl. Ovládací prvek tak může obsahovat další ovládací prvky a poskytuje vám velkou část výchozích funkcí.

Váš vlastní ovládací prvek bude mít přidružený vlastní návrhář. Tímto můžete vytvořit jedinečné designové prostředí speciálně přizpůsobené pro váš vlastní ovládací prvek.

Ovládací prvek přidružíte k jeho návrháři pomocí třídy DesignerAttribute. Jelikož vyvíjíte kompletní návrhové chování svého vlastního ovládacího prvku, vlastní návrhář implementuje rozhraní IRootDesigner.

Definování vlastního ovládacího prvku a vlastního návrháře

  1. Otevřete zdrojový soubor veditoru kódu . V horní části souboru naimportujte následující obory názvů:

    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. Přidejte DesignerAttribute do deklarace třídy MarqueeControl. Tím se vlastní ovládací prvek přidruží k jeho návrháři.

    [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
    public class MarqueeControl : UserControl
    {
    
    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
  3. Otevřete zdrojový soubor veditoru kódu . V horní části souboru naimportujte následující obory názvů:

    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. Změňte deklaraci MarqueeControlRootDesigner tak, aby dědila z třídy DocumentDesigner. Použijte ToolboxItemFilterAttribute k určení interakce návrháře s Panelem nástrojů .

    Poznámka

    Definice třídy MarqueeControlRootDesigner byla uzavřena do jmenného prostoru MarqueeControlLibrary.Design. Tato deklarace umístí návrháře do speciálního oboru názvů vyhrazeného pro typy související s návrhem.

    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. Definujte konstruktor pro třídu MarqueeControlRootDesigner. Do těla konstruktoru vložte příkaz WriteLine. To bude užitečné pro ladění.

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

Vytvoření instance vlastního ovládacího prvku

  1. Do projektu MarqueeControlTest přidejte novou položku UserControl. Pojmenujte nový zdrojový soubor základním jménem DemoMarqueeControl.

  2. Otevřete soubor veditoru kódu . V horní části souboru naimportujte obor názvů MarqueeControlLibrary:

    Imports MarqueeControlLibrary
    
    using MarqueeControlLibrary;
    
  3. Změňte deklaraci DemoMarqueeControl tak, aby dědila z třídy MarqueeControl.

  4. Sestavte projekt.

  5. Otevřete formulář 1 v návrháři Windows Forms.

  6. Najděte kartu MarqueeControlTest Components v panelu nástrojů a otevřete ji. Přetáhněte DemoMarqueeControl ze sady nástrojů do formuláře.

  7. Sestavte projekt.

Nastavení projektu Design-Time pro ladění

Při vývoji vlastního prostředí pro návrh bude nutné ladit ovládací prvky a komponenty. Existuje jednoduchý způsob, jak nastavit projekt tak, aby umožňoval ladění v době návrhu. Další informace naleznete v tématu Návod: Ladění vlastních ovládacích prvků Windows Forms v době návrhu.

  1. Klikněte pravým tlačítkem na projekt MarqueeControlLibrary a vyberte Vlastnosti.

  2. V dialogovém okně MarqueeControlLibrary Stránky vlastností vyberte stránku Ladění.

  3. V části Spustit akci vyberte Spustit externí program. Budete ladit samostatnou instanci sady Visual Studio, takže klikněte na tři tečky (tlačítko se třemi tečkami (...) v okně Vlastnosti v sadě Visual Studio) a vyhledejte integrované vývojové prostředí sady Visual Studio. Název spustitelného souboru je devenv.exea pokud jste ho nainstalovali do výchozího umístění, jeho cesta je %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.

  4. Výběrem OK dialogové okno zavřete.

  5. Klikněte pravým tlačítkem myši na projekt MarqueeControlLibrary a vyberte Nastavit jako spouštěcí projekt, aby se povolila tato konfigurace ladění.

Kontrolní bod

Nyní jste připraveni ladit chování vlastního ovládacího prvku během návrhu. Jakmile zjistíte, že je prostředí ladění správně nastavené, otestujete přidružení mezi vlastním ovládacím prvkem a vlastním návrhářem.

Otestovat prostředí ladění a asociaci návrháře

  1. Otevřete zdrojový soubor MarqueeControlRootDesigner v editoru kódu a umístěte zarážku na příkaz WriteLine.

  2. Stisknutím klávesy F5 spusťte ladicí relaci.

    Vytvoří se nová instance sady Visual Studio.

  3. V nové instanci sady Visual Studio otevřete řešení MarqueeControlTest. Řešení snadno najdete tak, že v nabídce Soubor vyberete Poslední projekty. Soubor řešení MarqueeControlTest.sln bude uvedený jako naposledy použitý soubor.

  4. Otevřete DemoMarqueeControl v designéru.

    Instance ladění sady Visual Studio získá fokus a spuštění se zastaví na zarážce. Stisknutím klávesy F5 pokračujte v ladicí relaci.

V tuto chvíli je vše na místě, abyste mohli vyvíjet a ladit vlastní ovládací prvek a souvisejícího vlastního návrháře. Zbývající část tohoto článku se zaměřuje na podrobnosti implementace funkcí ovládacího prvku a návrháře.

Implementace vlastního ovládacího prvku

MarqueeControl je UserControl s trochou přizpůsobení. Zpřístupňuje dvě metody: Start, která spustí animaci marquee, a Stop, která zastaví animaci. Vzhledem k tomu, že MarqueeControl obsahuje podřízené ovládací prvky, které implementují rozhraní IMarqueeWidget, Start a Stop prochází každý podřízený ovládací prvek a volá metody StartMarquee a StopMarquee, v uvedeném pořadí, na každém podřízeném ovládacím prvku, který implementuje IMarqueeWidget.

Podoba ovládacích prvků MarqueeBorder a MarqueeText závisí na rozložení, proto MarqueeControl přepisuje metodu OnLayout a volá PerformLayout u podřízených ovládacích prvků tohoto typu.

Takto vypadá rozsah úprav MarqueeControl. Funkce za běhu jsou implementovány ovládacími prvky MarqueeBorder a MarqueeText a funkce návrhu jsou implementovány třídami MarqueeBorderDesigner a MarqueeControlRootDesigner.

Pro implementaci vlastního ovládacího prvku

  1. Otevřete zdrojový soubor veditoru kódu . Implementujte metody Start a 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. Přepište metodu 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
    

Vytvoření podřízeného ovládacího prvku pro vlastní ovládací prvek

MarqueeControl bude obsahovat dva typy podřízených ovládacích prvků: ovládací prvek MarqueeBorder a ovládací prvek MarqueeText.

  • MarqueeBorder: Tento ovládací prvek vytváří ohraničení "světel" kolem svých okrajů. Světla postupně blikají, takže se zdá, že se pohybují kolem ohraničení. Rychlost, při které světla bliká, je řízena vlastností s názvem UpdatePeriod. Několik dalších vlastních vlastností určuje další aspekty vzhledu ovládacího prvku. Dvě metody, které se nazývají StartMarquee a StopMarquee, řídí, kdy se animace spustí a zastaví.

  • MarqueeText: Tento ovládací prvek maluje blikající řetězec. Stejně jako ovládací prvek MarqueeBorder je rychlost, při které text bliká, řízena vlastností UpdatePeriod. Ovládací prvek MarqueeText má také společné StartMarquee a StopMarquee metody s ovládacím MarqueeBorder.

V době návrhu MarqueeControlRootDesigner umožňuje přidání těchto dvou typů ovládacích prvků do MarqueeControl v libovolné kombinaci.

Běžné funkce těchto dvou ovládacích prvků jsou faktorovány do rozhraní nazývaného IMarqueeWidget. To umožňuje MarqueeControl zjistit všechny podřízené ovládací prvky související s Marquee a poskytnout jim zvláštní zacházení.

K implementaci funkce pravidelné animace použijete BackgroundWorker objekty z oboru názvů System.ComponentModel. Můžete použít Timer objekty, ale pokud existuje mnoho IMarqueeWidget objektů, jedno vlákno uživatelského rozhraní nemusí držet krok s animací.

Vytvořit podřízený ovládací prvek pro váš vlastní ovládací prvek

  1. Přidání nové položky třídy do projektu MarqueeControlLibrary Dejte novému zdrojovému souboru základní název "IMarqueeWidget".

  2. Otevřete zdrojový soubor v editoru kódu a změňte deklaraci z na :

    // 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. Do rozhraní IMarqueeWidget přidejte následující kód, který zpřístupní dvě metody a vlastnost, která manipuluje s animací marquee:

    // 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. Do projektu MarqueeControlLibrary přidejte novou položku vlastního ovládacího prvku. Dejte novému zdrojovému souboru základní název "MarqueeText".

  5. Přetáhněte komponentu BackgroundWorker z panelu nástrojů do ovládacího prvku MarqueeText. Tato komponenta umožní, aby se ovládací prvek MarqueeText aktualizoval asynchronně.

  6. V okně Vlastnosti nastavte komponentu BackgroundWorker a její vlastnosti WorkerReportsProgress a WorkerSupportsCancellation na true. Tato nastavení umožňují komponentě BackgroundWorker pravidelně vyvolat událost ProgressChanged a zrušit asynchronní aktualizace.

    Další informace naleznete v komponentu BackgroundWorker.

  7. Otevřete zdrojový soubor veditoru kódu . V horní části souboru naimportujte následující obory názvů:

    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. Změňte deklaraci MarqueeText tak, aby dědila z Label a implementovala rozhraní IMarqueeWidget.

    [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. Deklarujte proměnné instance, které odpovídají vystaveným vlastnostem, a inicializují je v konstruktoru. Pole isLit určuje, zda má být text malován barvou danou vlastností 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. Implementujte rozhraní IMarqueeWidget.

    Metody StartMarquee a StopMarquee vyvolávají metody RunWorkerAsync a CancelAsync komponenty BackgroundWorker pro spuštění a zastavení animace.

    Atributy Category a Browsable se použijí na vlastnost UpdatePeriod, takže se zobrazí ve vlastní části okna Vlastnosti s názvem "Marquee".

    public virtual void StartMarquee()
    {
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' Start the updating thread and pass it the UpdatePeriod.
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub
    
    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' Stop the updating thread.
        Me.backgroundWorker1.CancelAsync()
    End Sub
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod
    
        Get
            Return Me.updatePeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.updatePeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0")
            End If
        End Set
    
    End Property
    
  11. Implementujte metody pro přístup k vlastnostem. Klientům zpřístupníte dvě vlastnosti: LightColor a DarkColor. Na tyto vlastnosti se použijí atributy Category a Browsable, takže vlastnosti se zobrazí ve vlastní části okna Vlastnosti s názvem "Marquee".

    [Category("Marquee")]
    [Browsable(true)]
    public Color LightColor
    {
        get
        {
            return this.lightColorValue;
        }
        set
        {
            // The LightColor property is only changed if the
            // client provides a different value. Comparing values
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.lightColorValue.ToArgb() != value.ToArgb())
            {
                this.lightColorValue = value;
                this.lightBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color DarkColor
    {
        get
        {
            return this.darkColorValue;
        }
        set
        {
            // The DarkColor property is only changed if the
            // client provides a different value. Comparing values
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.darkColorValue.ToArgb() != value.ToArgb())
            {
                this.darkColorValue = value;
                this.darkBrush = new SolidBrush(value);
            }
        }
    }
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color
    
        Get
            Return Me.lightColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If
        End Set
    
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color
    
        Get
            Return Me.darkColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If
        End Set
    
    End Property
    
  12. Implementujte obslužné rutiny pro události DoWork a ProgressChanged komponenty BackgroundWorker.

    Obslužná rutina události DoWork čeká po dobu milisekund určených UpdatePeriod a vyvolá událost ProgressChanged, dokud váš kód nezastaví animaci voláním CancelAsync.

    Obslužná rutina události ProgressChanged přepíná text mezi jeho světlým a tmavým stavem, aby vytvářel efekt blikání.

    // 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. Přepište metodu OnPaint, abyste umožnili animaci.

    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. Stisknutím klávesy F6 sestavte řešení.

Vytvořte podřízený ovládací prvek MarqueeBorder

Ovládací prvek MarqueeBorder je o něco důmyslnější než ovládací prvek MarqueeText. Má více vlastností a animace v metodě OnPaint je více zapojena. V zásadě je to docela podobné ovládacímu prvku MarqueeText.

Vzhledem k tomu, že ovládací prvek MarqueeBorder může mít podřízené ovládací prvky, je potřeba vědět o Layout událostech.

Vytvoření ovládacího prvku MarqueeBorder

  1. Do projektu MarqueeControlLibrary přidejte novou položku vlastního ovládacího prvku . Dejte novému zdrojovému souboru základní název "MarqueeBorder".

  2. Přetáhněte komponentu BackgroundWorker z panelu nástrojů do ovládacího prvku MarqueeBorder. Tato komponenta umožní, aby se ovládací prvek MarqueeBorder aktualizoval asynchronně.

  3. V okně Vlastnosti nastavte hodnotu vlastností WorkerReportsProgress a WorkerSupportsCancellation komponenty BackgroundWorker na true. Tato nastavení umožňují komponentě BackgroundWorker pravidelně vyvolat událost ProgressChanged a zrušit asynchronní aktualizace. Další informace naleznete v BackgroundWorker komponenta.

  4. V okně Vlastnosti vyberte tlačítko Události. Připojte obslužné rutiny pro události DoWork a ProgressChanged.

  5. Otevřete zdrojový soubor veditoru kódu . V horní části souboru naimportujte následující obory názvů:

    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. Změňte deklaraci MarqueeBorder tak, aby dědila z Panel a implementuje IMarqueeWidget rozhraní.

    [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. Deklarujte dva výčty pro správu stavu ovládacího prvku MarqueeBorder: MarqueeSpinDirection, který určuje směr, ve kterém se světla "otočí" kolem ohraničení, a MarqueeLightShape, který určuje tvar světel (čtverec nebo kruh). Tyto deklarace umístěte před deklaraci třídy 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. Deklarujte proměnné instance, které odpovídají vystaveným vlastnostem, a inicializují je v konstruktoru.

    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. Implementujte rozhraní IMarqueeWidget.

    Metody StartMarquee a StopMarquee vyvolávají metody RunWorkerAsync a CancelAsync komponenty BackgroundWorker pro spuštění a zastavení animace.

    Vzhledem k tomu, že ovládací prvek MarqueeBorder může obsahovat podřízené ovládací prvky, metoda StartMarquee provádí výčet všech těchto podřízených ovládacích prvků a volá StartMarquee u těch, které implementují IMarqueeWidget. Metoda StopMarquee má podobnou implementaci.

    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. Implementujte akcesory vlastností. Ovládací prvek MarqueeBorder má několik vlastností pro řízení jejího vzhledu.

    [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. Implementujte obslužné rutiny pro události DoWork a ProgressChanged komponenty BackgroundWorker.

    Obslužná rutina události DoWork spí po dobu, která je určena v milisekundách UpdatePeriod, pak vyvolá událost ProgressChanged, dokud váš kód nezastaví animaci voláním CancelAsync.

    Obslužný program události ProgressChanged posune pozici "základního" světla, od kterého je určen světelný/stínový stav ostatních světel, a volá metodu Refresh, která způsobí, že se ovládací prvek překreslí.

    // 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. Implementujte pomocné metody, IsLit a DrawLight.

    Metoda IsLit určuje barvu světla na dané pozici. Světla, která jsou „rozsvícená“, jsou vykreslena barvou danou vlastností LightColor, a ta, která jsou „tmavá“, jsou vykreslena barvou danou vlastností DarkColor.

    Metoda DrawLight nakreslí světlo s použitím odpovídající barvy, tvaru a pozice.

    // 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. Přepište metody OnLayout a OnPaint.

    Metoda OnPaint kreslí světlo podél okrajů ovládacího prvku MarqueeBorder.

    Protože metoda OnPaint závisí na rozměrech ovládacího prvku MarqueeBorder, je třeba ji volat pokaždé, když se změní rozložení. Chcete-li toho dosáhnout, přepište OnLayout a zavolejte 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
    

Vytvoření vlastního návrháře pro vlastnosti stínu a filtru

Třída MarqueeControlRootDesigner poskytuje implementaci kořenového návrháře. Kromě tohoto návrháře, který pracuje s MarqueeControl, budete potřebovat vlastního návrháře, který je speciálně přidružen k ovládacímu prvku MarqueeBorder. Tento návrhář rozhraní poskytuje uživatelsky definované chování, které je vhodné v kontextu uživatelsky definovaného kořenového návrháře.

Konkrétně MarqueeBorderDesigner vyfiltruje určité vlastnosti ovládacího prvku MarqueeBorder a změní jejich interakci s návrhovým prostředím.

Zachycení volání vlastnosti komponenty se označuje jako stínování. Umožňuje návrháři sledovat hodnotu nastavenou uživatelem a volitelně předat danou hodnotu komponentě, která je navrhována.

V tomto příkladu budou vlastnosti Visible a Enabled stínovány MarqueeBorderDesigner, což uživateli brání v tom, aby byl ovládací prvek MarqueeBorder neviditelný nebo zakázaný během návrhu.

Návrháři mohou také přidávat a odebírat vlastnosti. V tomto příkladu bude vlastnost Padding odstraněna v čase návrhu, protože ovládací prvek MarqueeBorder programově nastaví vnitřní okraj na základě velikosti světel určené vlastností LightSize.

Základní třída pro MarqueeBorderDesigner je ComponentDesigner, která má metody, které mohou změnit atributy, vlastnosti a události vystavené ovládacím prvku v době návrhu:

Při změně veřejného rozhraní komponenty pomocí těchto metod postupujte podle těchto pravidel:

  • Přidání nebo odebrání položek pouze v metodách PreFilter

  • Úprava existujících položek pouze v metodách PostFilter

  • Vždy volejte základní implementaci jako první v PreFilter metodách.

  • Vždy volejte základní implementaci jako poslední v PostFilter metodách.

Dodržování těchto pravidel zajišťuje, aby všichni návrháři v prostředí návrhu měli konzistentní zobrazení všech komponent, které jsou navrženy.

Třída ComponentDesigner poskytuje slovník pro správu hodnot stínovaných vlastností, což vám snižuje nutnost vytvořit konkrétní proměnné instance.

Vytvoření vlastního návrháře pro zastínění a filtrování vlastností

  1. Klikněte pravým tlačítkem na složku Návrh a přidejte novou třídu. Dejte zdrojovému souboru základní název MarqueeBorderDesigner.

  2. Otevřete zdrojový soubor MarqueeBorderDesigner v editoru kódu . V horní části souboru naimportujte následující obory názvů:

    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. Změňte deklaraci MarqueeBorderDesigner tak, aby dědila z ParentControlDesigner.

    Protože ovládací prvek MarqueeBorder může obsahovat podřízené ovládací prvky, MarqueeBorderDesigner dědí z ParentControlDesigner, která zpracovává interakci nadřazeného a podřízeného objektu.

    namespace MarqueeControlLibrary.Design
    {
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
  4. Přepsat základní implementaci 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. Implementujte vlastnosti Enabled a Visible. Tyto implementace stínují vlastnosti ovládacího prvku.

    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
    

Zpracování změn součástí

Třída MarqueeControlRootDesigner poskytuje přizpůsobený zážitek z návrhu pro vaše instance MarqueeControl. Většina funkcí dostupných během návrhu je zděděna z třídy DocumentDesigner. Váš kód implementuje dvě konkrétní přizpůsobení: zpracování změn součástí a přidání příkazů návrháře.

Když uživatelé navrhují své MarqueeControl instance, váš kořenový návrhář bude sledovat změny MarqueeControl a jeho podřízených ovládacích prvků. Prostředí v době návrhu nabízí pohodlnou službu IComponentChangeServicepro sledování změn stavu součástí.

Odkaz na tuto službu získáte dotazováním prostředí pomocí metody GetService. Pokud je dotaz úspěšný, může návrhář připojit obslužnou rutinu události ComponentChanged a provádět jakékoli úlohy potřebné k zachování konzistentního stavu v době návrhu.

V případě třídy MarqueeControlRootDesigner zavoláte metodu Refresh na každém objektu IMarqueeWidget obsaženém v MarqueeControl. To způsobí, že se objekt IMarqueeWidget odpovídajícím způsobem překreslí, když se změní například vlastnosti nadřazeného objektu, jako je Size.

Řešení změn komponentů

  1. Otevřete zdrojový soubor v editoru kódu a přepište metodu. Zavolejte základní implementaci Initialize a dotazujte se na 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. Implementujte obslužnou rutinu události OnComponentChanged. Otestujte typ odesílající komponenty a pokud se jedná o IMarqueeWidget, zavolejte jeho Refresh metodu.

    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
    

Přidejte slovesa Návrháře do Vašeho Vlastního Návrháře

Návrhářské sloveso je příkaz nabídky propojený s obslužnou rutinou události. Návrhářské příkazy jsou přidány do místní nabídky komponenty během návrhu. Další informace najdete v tématu DesignerVerb.

Do svých návrhářů přidáte dvě návrhářské slovesá: Spustit test a Zastavit test. Tyto příkazy vám umožní zobrazit chování MarqueeControl za běhu v době návrhu. Tato slovesa budou přidána do MarqueeControlRootDesigner.

Když je vyvolána obslužná rutina události příkazu při spuštění testovacího, zavolá StartMarquee metodu na MarqueeControl. Když je vyvolána obslužná rutina události pro zastavení testu, zavolá metodu StopMarquee na MarqueeControl. Implementace metod StartMarquee a StopMarquee volá tyto metody na ovládacích prvcích, které implementují IMarqueeWidget, takže se také všechny obsažené ovládací prvky IMarqueeWidget účastní testu.

Přidání sloves návrháře do vlastních návrhů.

  1. Do třídy MarqueeControlRootDesigner přidejte obslužné rutiny událostí s názvem OnVerbRunTest a 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. Připojte tyto obslužné rutiny událostí k odpovídajícím příkazům návrhového prostředí. MarqueeControlRootDesigner dědí DesignerVerbCollection ze své základní třídy. V metodě Initialize vytvoříte dva nové DesignerVerb objekty a přidáte je do této kolekce.

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

Vytvoření vlastního uživatelského rozhraní UiTypeEditor

Když pro uživatele vytvoříte vlastní prostředí pro návrh, je často žádoucí vytvořit vlastní interakci s oknem Vlastnosti. Toho můžete dosáhnout vytvořením UITypeEditor.

Ovládací prvek MarqueeBorder zveřejňuje několik vlastností v okně Vlastnosti. Dvě z těchto vlastností, MarqueeSpinDirection a MarqueeLightShape jsou reprezentovány výčty. Pro ilustraci použití editoru typů uživatelského rozhraní bude vlastnost MarqueeLightShape mít přidruženou UITypeEditor třídu.

Vytvoření vlastního editoru typů uživatelského rozhraní

  1. Otevřete zdrojový soubor veditoru kódu .

  2. V definici třídy MarqueeBorder deklarujte třídu s názvem LightShapeEditor, která je odvozena z 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. Deklarujte proměnnou instance IWindowsFormsEditorService s názvem editorService.

    private IWindowsFormsEditorService editorService = null;
    
    Private editorService As IWindowsFormsEditorService = Nothing
    
  4. Přepište metodu GetEditStyle. Tato implementace vrátí DropDown, který určuje návrhovému prostředí, jak zobrazit 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. Přepište metodu EditValue. Tato implementace dotazuje návrhové prostředí pro objekt IWindowsFormsEditorService. V případě úspěchu se vytvoří LightShapeSelectionControl. Metoda DropDownControl je vyvolána pro spuštění LightShapeEditor. Návratová hodnota z tohoto vyvolání se vrátí do návrhového prostředí.

    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
    

Vytvoření řídicího prvku zobrazení pro vlastní UITypeEditor

Vlastnost MarqueeLightShape podporuje dva typy světlých obrazců: Square a Circle. Vytvoříte vlastní ovládací prvek použitý výhradně pro účely grafického zobrazení těchto hodnot v okně Vlastnosti. Tento vlastní ovládací prvek bude váš UITypeEditor používat k interakci s oknem Vlastnosti.

Vytvoření ovládacího prvku zobrazení pro vlastní editor typů uživatelského rozhraní

  1. Do projektu MarqueeControlLibrary přidejte novou položku UserControl. Dejte novému zdrojovému souboru základní název LightShapeSelectionControl.

  2. Přetáhněte dva ovládací prvky Panel ze sady nástrojů na LightShapeSelectionControl. Pojmenujte je squarePanel a circlePanel. Uspořádejte je vedle sebe. Nastavte vlastnost Size obou ovládacích prvků Panel na (60, 60). Nastavte vlastnost Location ovládacího prvku squarePanel na (8, 10). Nastavte vlastnost Location ovládacího prvku circlePanel na (80, 10). Nakonec nastavte vlastnost SizeLightShapeSelectionControl na (150, 80).

  3. Otevřete zdrojový soubor veditoru kódu . V horní části souboru naimportujte obor názvů System.Windows.Forms.Design:

    Imports System.Windows.Forms.Design
    
    using System.Windows.Forms.Design;
    
  4. Implementujte obslužné rutiny událostí Click pro ovládací prvky squarePanel a circlePanel. Tyto metody vyvolávají CloseDropDown, aby ukončily vlastní editační relaci UITypeEditor.

    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. Deklarujte proměnnou instance IWindowsFormsEditorService s názvem editorService.

    Private editorService As IWindowsFormsEditorService
    
    private IWindowsFormsEditorService editorService;
    
  6. Deklarujte proměnnou instance MarqueeLightShape s názvem lightShapeValue.

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
  7. V konstruktoru LightShapeSelectionControl připojte obslužné rutiny událostí Click k událostem squarePanel a circlePanel ovládacích prvků Click. Definujte také přetížení konstruktoru, které přiřadí MarqueeLightShape hodnotu z návrhového prostředí k poli 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. V metodě Dispose odpojte obslužné funkce událostí Click.

    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. V Průzkumníku řešeníklikněte na tlačítko Zobrazit všechny soubory. Otevřete soubor LightShapeSelectionControl.Designer.cs nebo LightShapeSelectionControl.Designer.vb a odeberte výchozí definici Dispose metody.

  10. Implementujte vlastnost 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. Přepište metodu OnPaint. Tato implementace nakreslí vyplněný čtverec a kruh. Zvýrazní také vybranou hodnotu nakreslením ohraničení kolem jednoho obrazce nebo druhého obrazce.

    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
    

Testování vlastního ovládacího prvku v Návrháři

V tuto chvíli můžete sestavit projekt MarqueeControlLibrary. Otestujte implementaci vytvořením ovládacího prvku, který dědí z třídy MarqueeControl a jeho použití ve formuláři.

Vytvoření vlastní implementace MarqueeControl

  1. Otevřete DemoMarqueeControl v nástroji Windows Forms Designer. Tím se vytvoří instance typu DemoMarqueeControl a zobrazí se v instanci typu MarqueeControlRootDesigner.

  2. Vpanelu nástrojů otevřete kartu MarqueeControlLibrary Components. Zobrazí se ovládací prvky a , které jsou k dispozici pro výběr.

  3. Přetáhněte instanci ovládacího prvku MarqueeBorder na návrhovou plochu DemoMarqueeControl. Ukotvit tento ovládací prvek MarqueeBorder k nadřazenému ovládacímu prvku.

  4. Přetáhněte instanci ovládacího prvku MarqueeText na návrhovou plochu DemoMarqueeControl.

  5. Sestavte řešení.

  6. Pravým tlačítkem myši klikněte na DemoMarqueeControl a v místní nabídce vyberte možnost Spustit test k zahájení animace. Klikněte na Zastavit test, abyste zastavili animaci.

  7. Otevřete Form1 v návrhovém zobrazení.

  8. Umístěte do formuláře dva Button ovládací prvky. Pojmenujte je startButton a stopButtona změňte hodnoty vlastností Text na Start a Stop.

  9. Implementujte obslužné rutiny událostí Click pro oba ovládací prvky Button.

  10. Otevřete kartu MarqueeControlTest Components v panelu nástrojů . DemoMarqueeControl se zobrazí jako dostupné pro výběr.

  11. Přetáhněte instanci DemoMarqueeControl na návrhovou plochu Form1.

  12. V obslužných rutinách událostí Click vyvolejte metody Start a Stop na DemoMarqueeControl.

    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. Nastavte MarqueeControlTest projekt jako spouštěný projekt a spusťte ho. Zobrazí se formulář zobrazující DemoMarqueeControl. Výběrem tlačítka Start spusťte animaci. Měli byste vidět, že text bliká a světla se pohybují kolem ohraničení.

Další kroky

MarqueeControlLibrary demonstruje jednoduchou implementaci uživatelsky definovaných ovládacích prvků a přidružených návrhářů. Tuto ukázku můžete udělat propracovanější několika způsoby:

  • Změňte hodnoty vlastností pro DemoMarqueeControl v návrháři. Přidejte další ovládací prvky MarqueBorder, ukotvěte je v nadřazených instancích a vytvořte vnořený efekt. Experimentujte s různými nastaveními pro UpdatePeriod a vlastnosti související se světly.

  • Vytvořte vlastní implementace IMarqueeWidget. Můžete například vytvořit blikající "neonový znaménko" nebo animované znaménko s více obrázky.

  • Další přizpůsobení zážitku při návrhu. Můžete zkusit stínovat více vlastností než Enabled a Visiblea přidat nové vlastnosti. Přidejte nové příkazy návrháře, které zjednodušují běžné úlohy, jako je ukotvení podřízených ovládacích prvků.

  • Licencujte MarqueeControl.

  • Řídí, jak jsou ovládací prvky serializovány a jak se pro ně generuje kód. Další informace najdete v tématu generování a kompilace dynamického zdrojového kódu.

Viz také