Przewodnik: tworzenie kontrolki korzystającej z funkcji czasu projektowania

Środowisko czasu projektowania dla kontrolki niestandardowej można zwiększyć, tworząc skojarzonego projektanta niestandardowego.

Uwaga

Ta zawartość została napisana dla programu .NET Framework. Jeśli używasz platformy .NET 6 lub nowszej wersji, użyj tej zawartości ostrożnie. System projektanta został zmieniony dla formularzy systemu Windows i ważne jest, aby przejrzeć zmiany Projektant od artykułu .NET Framework.

W tym artykule pokazano, jak utworzyć projektanta niestandardowego dla kontrolki niestandardowej. Zaimplementujesz MarqueeControl typ i skojarzona klasa projektanta o nazwie MarqueeControlRootDesigner.

Typ MarqueeControl implementuje wyświetlacz podobny do markizy teatru z animowanymi światłami i migającym tekstem.

Projektant tej kontrolki współdziała ze środowiskiem projektowym, aby zapewnić niestandardowe środowisko czasu projektowania. Za pomocą projektanta niestandardowego można utworzyć niestandardową MarqueeControl implementację z animowanymi światłami i migającym tekstem w wielu kombinacjach. Możesz użyć zmontowanej kontrolki na formularzu, podobnie jak w przypadku każdej innej kontrolki Windows Forms.

Po zakończeniu pracy z tym przewodnikiem kontrolka niestandardowa będzie wyglądać mniej więcej tak:

The app showing a marquee saying Text and a Start and Stop buttons.

Aby uzyskać pełną listę kodu, zobacz Instrukcje: tworzenie kontrolki formularzy systemu Windows, która korzysta z funkcji czasu projektowania.

Wymagania wstępne

Aby ukończyć ten przewodnik, potrzebny będzie program Visual Studio.

Tworzenie projektu

Pierwszym krokiem jest utworzenie projektu aplikacji. Użyjesz tego projektu do skompilowania aplikacji, która hostuje kontrolkę niestandardową.

W programie Visual Studio utwórz nowy projekt aplikacji Windows Forms i nadaj mu nazwę MarqueeControlTest.

Tworzenie projektu biblioteki kontrolek

  1. Dodaj projekt Biblioteka kontrolek formularzy systemu Windows do rozwiązania. Nadaj projektowi nazwę MarqueeControlLibrary.

  2. Za pomocą Eksplorator rozwiązań usuń domyślną kontrolkę projektu, usuwając plik źródłowy o nazwie "UserControl1.cs" lub "UserControl1.vb", w zależności od wybranego języka.

  3. Dodaj nowy UserControl element do MarqueeControlLibrary projektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową MarqueeControl.

  4. Za pomocą Eksplorator rozwiązań utwórz nowy folder w projekcieMarqueeControlLibrary.

  5. Kliknij prawym przyciskiem myszy folder Projekt i dodaj nową klasę. Nadaj mu nazwę MarqueeControlRoot Projektant.

  6. Musisz użyć typów z zestawu System.Design, więc dodaj to odwołanie do MarqueeControlLibrary projektu.

Odwołanie do projektu kontrolki niestandardowej

Użyjesz MarqueeControlTest projektu do przetestowania kontrolki niestandardowej. Projekt testowy będzie wiedział o kontrolce niestandardowej podczas dodawania odwołania do projektu do MarqueeControlLibrary zestawu.

W projekcie MarqueeControlTest dodaj odwołanie do projektu do MarqueeControlLibrary zestawu. Pamiętaj, aby użyć karty Projekty w oknie dialogowym Dodawanie odwołania zamiast odwoływać się bezpośrednio do MarqueeControlLibrary zestawu.

Definiowanie kontrolki niestandardowej i jej niestandardowej Projektant

Kontrolka niestandardowa UserControl będzie pochodzić z klasy . Dzięki temu kontrolka może zawierać inne kontrolki i zapewnia kontrolę bardzo wiele domyślnych funkcji.

Kontrolka niestandardowa będzie mieć skojarzonego projektanta niestandardowego. Dzięki temu można utworzyć unikatowe środowisko projektowe dostosowane specjalnie do niestandardowej kontrolki.

Kontrolkę można skojarzyć z jej projektantem DesignerAttribute przy użyciu klasy . Ponieważ opracowujesz całe zachowanie kontrolki niestandardowej w czasie projektowania, projektant niestandardowy zaimplementuje IRootDesigner interfejs.

Aby zdefiniować kontrolkę niestandardową i jej projektant niestandardowy

  1. MarqueeControl Otwórz plik źródłowy w Edytorze kodu. W górnej części pliku zaimportuj następujące przestrzenie nazw:

    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. Dodaj element DesignerAttribute do deklaracji MarqueeControl klasy. Spowoduje to skojarzenie kontrolki niestandardowej z jej projektantem.

    [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
    public class MarqueeControl : UserControl
    {
    
    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
  3. MarqueeControlRootDesigner Otwórz plik źródłowy w Edytorze kodu. W górnej części pliku zaimportuj następujące przestrzenie nazw:

    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. Zmień deklarację , MarqueeControlRootDesigner aby dziedziczyć z DocumentDesigner klasy . Zastosuj element , ToolboxItemFilterAttribute aby określić interakcję projektanta z przybornikiem.

    Uwaga

    Definicja MarqueeControlRootDesigner klasy została ujęta w przestrzeni nazw o nazwie MarqueeControlLibrary.Design. Ta deklaracja umieszcza projektanta w specjalnej przestrzeni nazw zarezerwowanej dla typów związanych z projektowaniem.

    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. Zdefiniuj MarqueeControlRootDesigner konstruktor dla klasy . Wstaw instrukcję WriteLine w treści konstruktora. Będzie to przydatne do debugowania.

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

Tworzenie wystąpienia kontrolki niestandardowej

  1. Dodaj nowy UserControl element do MarqueeControlTest projektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową DemoMarqueeControl.

  2. DemoMarqueeControl Otwórz plik w Edytorze kodu. W górnej części pliku zaimportuj MarqueeControlLibrary przestrzeń nazw:

    Imports MarqueeControlLibrary
    
    using MarqueeControlLibrary;
    
  3. Zmień deklarację , DemoMarqueeControl aby dziedziczyć z MarqueeControl klasy .

  4. Skompiluj projekt.

  5. Otwórz formularz Form1 w Projektant formularzy systemu Windows.

  6. Znajdź kartę MarqueeControlTest Components (Składniki MarqueeControlTest) w przyborniku i otwórz ją. Przeciągnij element DemoMarqueeControl z przybornika na formularz.

  7. Skompiluj projekt.

Konfigurowanie projektu na potrzeby debugowania w czasie projektowania

Podczas tworzenia niestandardowego środowiska czasu projektowania konieczne będzie debugowanie kontrolek i składników. Istnieje prosty sposób konfigurowania projektu w celu umożliwienia debugowania w czasie projektowania. Aby uzyskać więcej informacji, zobacz Przewodnik: debugowanie niestandardowych kontrolek formularzy systemu Windows w czasie projektowania.

  1. Kliknij projekt prawym przyciskiem myszy MarqueeControlLibrary i wybierz polecenie Właściwości.

  2. W oknie dialogowym MarqueeControlLibrary Property Pages (Strony właściwości MarqueeControlLibrary) wybierz stronę Debug (Debugowanie).

  3. W sekcji Rozpocznij akcję wybierz pozycję Uruchom program zewnętrzny. Będziesz debugować oddzielne wystąpienie programu Visual Studio, dlatego kliknij przycisk wielokropka (The Ellipsis button (...) in the Properties window of Visual Studio), aby przejść do środowiska IDE programu Visual Studio. Nazwa pliku wykonywalnego to devenv.exe, a jeśli zainstalowano plik domyślny, jego ścieżka to %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.

  4. Kliknij przycisk OK, aby zamknąć okno dialogowe.

  5. Kliknij prawym przyciskiem myszy projekt MarqueeControlLibrary i wybierz polecenie Ustaw jako projekt startowy, aby włączyć tę konfigurację debugowania.

Punkt kontrolny

Teraz możesz debugować zachowanie kontrolki niestandardowej w czasie projektowania. Po ustaleniu, że środowisko debugowania jest skonfigurowane poprawnie, przetestujesz skojarzenie między kontrolką niestandardową a projektantem niestandardowym.

Aby przetestować środowisko debugowania i skojarzenie projektanta

  1. Otwórz plik źródłowy MarqueeControlRoot Projektant w Edytorze kodu i umieść punkt przerwania w instrukcji WriteLine .

  2. Naciśnij klawisz F5 , aby rozpocząć sesję debugowania.

    Zostanie utworzone nowe wystąpienie programu Visual Studio.

  3. W nowym wystąpieniu programu Visual Studio otwórz rozwiązanie MarqueeControlTest. Rozwiązanie można łatwo znaleźć, wybierając pozycję Ostatnie projekty z menu Plik . Plik rozwiązania MarqueeControlTest.sln zostanie wyświetlony jako ostatnio używany plik.

  4. Otwórz element DemoMarqueeControl w projektancie.

    Wystąpienie debugowania programu Visual Studio uzyskuje fokus i wykonywanie zatrzymuje się w punkcie przerwania. Naciśnij klawisz F5 , aby kontynuować sesję debugowania.

W tym momencie wszystko jest gotowe do opracowywania i debugowania niestandardowej kontrolki i skojarzonego z nią projektanta niestandardowego. Pozostała część tego artykułu koncentruje się na szczegółach implementowania funkcji kontrolki i projektanta.

Implementowanie kontrolki niestandardowej

Element MarqueeControl jest z odrobiną UserControl dostosowywania. Uwidacznia dwie metody: Start, która uruchamia animację markizy i Stop, która zatrzymuje animację. Ponieważ element MarqueeControl zawiera kontrolki podrzędne, które implementują IMarqueeWidget interfejs, Start i Stop wyliczają każdą kontrolkę podrzędną i wywołuje StartMarquee odpowiednio metody i StopMarquee w każdej kontrolce podrzędnej, która implementuje IMarqueeWidgetelement .

Wygląd MarqueeBorder kontrolek i MarqueeText jest zależny od układu, dlatego MarqueeControl zastępuje OnLayout metodę i wywołuje PerformLayout kontrolki podrzędne tego typu.

Jest to zakres MarqueeControl dostosowań. Funkcje w czasie wykonywania są implementowane przez kontrolki MarqueeBorder i MarqueeText , a funkcje czasu projektowania są implementowane przez MarqueeBorderDesigner klasy i MarqueeControlRootDesigner .

Aby zaimplementować kontrolkę niestandardową

  1. MarqueeControl Otwórz plik źródłowy w Edytorze kodu. Zaimplementuj Start metody i 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. Zastąpij metodę 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
    

Tworzenie kontrolki podrzędnej dla kontrolki niestandardowej

Element MarqueeControl będzie hostować dwa rodzaje kontroli podrzędnej: kontrolkę MarqueeBorder i kontrolkę MarqueeText .

  • MarqueeBorder: Ta kontrolka maluje obramowanie "świateł" wokół jego krawędzi. Światła migają w sekwencji, więc wydają się poruszać po obramowanie. Szybkość, z jaką światła migają, jest kontrolowana przez właściwość o nazwie UpdatePeriod. Kilka innych właściwości niestandardowych określa inne aspekty wyglądu kontrolki. Dwie metody, nazywane StartMarquee i StopMarquee, kontrolują, kiedy animacja rozpoczyna się i zatrzymuje.

  • MarqueeText: Ta kontrolka maluje ciąg migający. Podobnie jak kontrolka MarqueeBorder , szybkość, z jaką tekst miga, jest kontrolowana UpdatePeriod przez właściwość . Kontrolka MarqueeText ma StartMarquee również metody i StopMarquee wspólne z kontrolką MarqueeBorder .

W czasie projektowania parametr MarqueeControlRootDesigner umożliwia dodanie tych dwóch typów kontrolek do MarqueeControl elementu w dowolnej kombinacji.

Typowe funkcje tych dwóch kontrolek są uwzględniane w interfejsie o nazwie IMarqueeWidget. Pozwala MarqueeControl to odkryć wszelkie kontrole dziecka związane z markizą i dać im specjalne traktowanie.

Aby zaimplementować funkcję animacji okresowej, użyjesz BackgroundWorker obiektów z System.ComponentModel przestrzeni nazw. Można użyć Timer obiektów, ale jeśli istnieje wiele IMarqueeWidget obiektów, pojedynczy wątek interfejsu użytkownika może nie być w stanie nadążyć za animacją.

Aby utworzyć kontrolkę podrzędną dla kontrolki niestandardowej

  1. Dodaj nowy element klasy do MarqueeControlLibrary projektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową "IMarqueeWidget".

  2. IMarqueeWidget Otwórz plik źródłowy w Edytorze kodu i zmień deklarację z class na interface:

    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
    
    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
  3. Dodaj następujący kod do interfejsu IMarqueeWidget , aby uwidocznić dwie metody i właściwość, która manipuluje animacją markizy:

    // 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. Dodaj nowy element Kontrolka niestandardowa MarqueeControlLibrary do projektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową "MarqueeText".

  5. Przeciągnij BackgroundWorker składnik z przybornika do kontrolkiMarqueeText. Ten składnik pozwoli kontrolce MarqueeText na asynchroniczną aktualizację.

  6. W oknie Właściwości ustaw BackgroundWorker właściwości i WorkerSupportsCancellation składnik WorkerReportsProgress na wartość true. Te ustawienia umożliwiają składnikowi BackgroundWorker okresowe wywoływanie ProgressChanged zdarzenia i anulowanie aktualizacji asynchronicznych.

    Aby uzyskać więcej informacji, zobacz BackgroundWorker Component (Składnik BackgroundWorker).

  7. MarqueeText Otwórz plik źródłowy w Edytorze kodu. W górnej części pliku zaimportuj następujące przestrzenie nazw:

    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. Zmień deklarację , MarqueeText aby dziedziczyć z Label i, IMarqueeWidget aby zaimplementować interfejs:

    [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. Zadeklaruj zmienne wystąpienia, które odpowiadają uwidocznionych właściwościom, i zainicjuj je w konstruktorze. Pole isLit określa, czy tekst ma być malowany w kolorze podanym LightColor przez właściwość.

    // 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. Zaimplementuj interfejs IMarqueeWidget.

    Metody StartMarquee i StopMarquee wywołują BackgroundWorker metody i CancelAsync składnikaRunWorkerAsync, aby uruchomić i zatrzymać animację.

    Atrybuty Category i Browsable są stosowane do UpdatePeriod właściwości, więc są wyświetlane w sekcji niestandardowej okno Właściwości o nazwie "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. Zaimplementuj metody dostępu właściwości. Uwidocznisz dwie właściwości klientom: LightColor i DarkColor. Atrybuty Category i Browsable są stosowane do tych właściwości, więc właściwości są wyświetlane w sekcji niestandardowej okno Właściwości o nazwie "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. Zaimplementuj programy obsługi dla BackgroundWorker zdarzeń i ProgressChanged składnikówDoWork.

    Procedura DoWork obsługi zdarzeń śpi dla liczby milisekund określonych przez UpdatePeriod program , a następnie zgłasza ProgressChanged zdarzenie, dopóki kod nie zatrzyma animacji przez wywołanie metody CancelAsync.

    Program ProgressChanged obsługi zdarzeń przełącza tekst między jego jasnym i ciemnym stanem, aby dać wygląd migania.

    // 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. Zastąpi metodę OnPaint , aby włączyć animację.

    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. Naciśnij klawisz F6 , aby skompilować rozwiązanie.

Tworzenie kontrolki podrzędnej MarqueeBorder

Kontrolka MarqueeBorder jest nieco bardziej wyrafinowana niż kontrolka MarqueeText . Ma więcej właściwości, a animacja w metodzie jest bardziej zaangażowana OnPaint . W zasadzie jest to dość podobne do MarqueeText kontroli.

Ponieważ kontrolka MarqueeBorder może mieć kontrolki podrzędne, musi mieć świadomość zdarzeń Layout .

Aby utworzyć kontrolkę MarqueeBorder

  1. Dodaj nowy element Kontrolka niestandardowa MarqueeControlLibrary do projektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową "MarqueeBorder".

  2. Przeciągnij BackgroundWorker składnik z przybornika do kontrolkiMarqueeBorder. Ten składnik pozwoli kontrolce MarqueeBorder na asynchroniczną aktualizację.

  3. W oknie Właściwości ustaw BackgroundWorker właściwości i WorkerSupportsCancellation składnik WorkerReportsProgress na wartość true. Te ustawienia umożliwiają składnikowi BackgroundWorker okresowe wywoływanie ProgressChanged zdarzenia i anulowanie aktualizacji asynchronicznych. Aby uzyskać więcej informacji, zobacz BackgroundWorker Component (Składnik BackgroundWorker).

  4. W oknie Właściwości wybierz przycisk Zdarzenia. Dołączanie programów obsługi dla zdarzeń DoWork i ProgressChanged .

  5. MarqueeBorder Otwórz plik źródłowy w Edytorze kodu. W górnej części pliku zaimportuj następujące przestrzenie nazw:

    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. Zmień deklarację , MarqueeBorder aby dziedziczyć z Panel i, IMarqueeWidget aby zaimplementować interfejs.

    [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. Zadeklaruj dwie wyliczenia do zarządzania MarqueeBorder stanem kontrolki: MarqueeSpinDirection, która określa kierunek, w którym światła "obracają się" wokół obramowania, i MarqueeLightShape, które określają kształt świateł (kwadrat lub okrągły). Umieść te deklaracje przed deklaracją MarqueeBorder klasy.

    // 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. Zadeklaruj zmienne wystąpienia, które odpowiadają uwidocznionych właściwościom, i zainicjuj je w konstruktorze.

    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. Zaimplementuj interfejs IMarqueeWidget.

    Metody StartMarquee i StopMarquee wywołują BackgroundWorker metody i CancelAsync składnikaRunWorkerAsync, aby uruchomić i zatrzymać animację.

    Ponieważ kontrolka MarqueeBorder może zawierać kontrolki podrzędne, StartMarquee metoda wylicza wszystkie kontrolki podrzędne i wywołuje StartMarquee te, które implementują IMarqueeWidgetelement . Metoda StopMarquee ma podobną implementację.

    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. Zaimplementuj metody dostępu właściwości. Kontrolka MarqueeBorder ma kilka właściwości do kontrolowania wyglądu.

    [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. Zaimplementuj programy obsługi dla BackgroundWorker zdarzeń i ProgressChanged składnikówDoWork.

    Procedura DoWork obsługi zdarzeń śpi dla liczby milisekund określonych przez UpdatePeriod program , a następnie zgłasza ProgressChanged zdarzenie, dopóki kod nie zatrzyma animacji przez wywołanie metody CancelAsync.

    Procedura ProgressChanged obsługi zdarzeń zwiększa położenie światła "bazowego", z którego jest określany stan światła/ciemności innych świateł, i wywołuje Refresh metodę, aby spowodować, że kontrolka zostanie przemalować.

    // 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. Zaimplementuj metody IsLit pomocnika i DrawLight.

    Metoda IsLit określa kolor światła na danej pozycji. Światła , które są "oświetlone" są rysowane w kolorze LightColor podanym przez właściwość, a te, które są "ciemne" są rysowane w kolorze podanym DarkColor przez właściwość.

    Metoda DrawLight rysuje światło przy użyciu odpowiedniego koloru, kształtu i położenia.

    // 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. Zastąpij OnLayout metody i OnPaint .

    Metoda OnPaint rysuje światła wzdłuż krawędzi kontrolki MarqueeBorder .

    OnPaint Ponieważ metoda zależy od wymiarów MarqueeBorder kontrolki, należy ją wywołać za każdym razem, gdy układ się zmieni. Aby to osiągnąć, zastąpij metodę OnLayout i wywołaj metodę 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
    

Tworzenie niestandardowych Projektant do właściwości cienia i filtrowania

Klasa MarqueeControlRootDesigner udostępnia implementację projektanta głównego. Oprócz tego projektanta, który działa w systemie MarqueeControl, potrzebujesz projektanta niestandardowego, który jest specjalnie skojarzony z kontrolką MarqueeBorder . Ten projektant zapewnia niestandardowe zachowanie, które jest odpowiednie w kontekście niestandardowego projektanta głównego.

W szczególności MarqueeBorderDesigner element będzie "cień" i filtruje niektóre właściwości kontrolki MarqueeBorder , zmieniając ich interakcję ze środowiskiem projektowym.

Przechwytywanie wywołań metody dostępu właściwości składnika jest nazywane "cieniowaniem". Umożliwia projektantowi śledzenie wartości ustawionej przez użytkownika i opcjonalnie przekazanie tej wartości do projektu składnika.

W tym przykładzie VisibleMarqueeBorderDesignerwłaściwości i Enabled będą w tle przez element , co uniemożliwia użytkownikowi MarqueeBorder tworzenie kontrolki niewidocznej lub wyłączonej w czasie projektowania.

Projektant mogą również dodawać i usuwać właściwości. W tym przykładzie właściwość zostanie usunięta w czasie projektowania, Padding ponieważ MarqueeBorder kontrolka programowo ustawia dopełnienie na podstawie rozmiaru światła określonego LightSize przez właściwość .

Klasa bazowa dla MarqueeBorderDesigner klasy to ComponentDesigner, która zawiera metody, które mogą zmieniać atrybuty, właściwości i zdarzenia uwidocznione przez kontrolkę w czasie projektowania:

Podczas zmieniania interfejsu publicznego składnika przy użyciu tych metod postępuj zgodnie z następującymi regułami:

  • Dodawanie lub usuwanie elementów tylko w metodach PreFilter

  • Modyfikowanie istniejących elementów tylko w metodach PostFilter

  • Zawsze należy najpierw wywołać implementację podstawową w metodach PreFilter

  • Zawsze wywołaj implementację podstawową ostatnio w metodach PostFilter

Przestrzeganie tych reguł gwarantuje, że wszyscy projektanci w środowisku projektowania mają spójny widok wszystkich składników, które są projektowane.

Klasa ComponentDesigner udostępnia słownik do zarządzania wartościami właściwości w tle, co zmniejsza konieczność tworzenia określonych zmiennych wystąpienia.

Aby utworzyć projektanta niestandardowego do cieniowania i filtrowania właściwości

  1. Kliknij prawym przyciskiem myszy folder Projekt i dodaj nową klasę. Nadaj plikowi źródłowemu nazwę podstawową MarqueeBorder Projektant.

  2. Otwórz plik źródłowy MarqueeBorder Projektant w Edytorze kodu. W górnej części pliku zaimportuj następujące przestrzenie nazw:

    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. Zmień deklarację , MarqueeBorderDesigner aby dziedziczyć z ParentControlDesignerelementu .

    Ponieważ kontrolka MarqueeBorder może zawierać kontrolki podrzędne, MarqueeBorderDesigner dziedziczy z ParentControlDesignerklasy , która obsługuje interakcję elementu nadrzędnego-podrzędnego.

    namespace MarqueeControlLibrary.Design
    {
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
  4. Zastąpij podstawową implementację klasy 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. Zaimplementuj Enabled właściwości i Visible . Te implementacje w tle właściwości kontrolki.

    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
    

Obsługa zmian składników

Klasa MarqueeControlRootDesigner zapewnia niestandardowe środowisko czasu projektowania dla MarqueeControl wystąpień. Większość funkcji czasu projektowania jest dziedziczona z DocumentDesigner klasy . Kod implementuje dwa konkretne dostosowania: obsługę zmian składników i dodawanie czasowników projektanta.

Gdy użytkownicy projektują swoje MarqueeControl wystąpienia, projektant główny będzie śledzić zmiany w kontrolkach MarqueeControl podrzędnych i . Środowisko czasu projektowania oferuje wygodną usługę , IComponentChangeServicesłużącą do śledzenia zmian stanu składnika.

Aby uzyskać odwołanie do tej usługi, należy wykonać zapytanie względem środowiska za GetService pomocą metody . Jeśli zapytanie zakończy się pomyślnie, projektant może dołączyć program obsługi dla ComponentChanged zdarzenia i wykonać wszystkie zadania wymagane do zachowania spójnego stanu w czasie projektowania.

W przypadku MarqueeControlRootDesigner klasy wywołasz metodę Refresh dla każdego IMarqueeWidget obiektu zawartego MarqueeControlw obiekcie . IMarqueeWidget Spowoduje to, że obiekt zostanie odpowiednio przemalowane, gdy właściwości takie jak jego element nadrzędny Size zostaną zmienione.

Aby obsłużyć zmiany składników

  1. MarqueeControlRootDesigner Otwórz plik źródłowy w Edytorze kodu i zastąpij metodę Initialize . Wywołaj podstawową implementację Initialize metody i wykonaj zapytanie dla elementu 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. Zaimplementuj procedurę obsługi zdarzeń OnComponentChanged . Przetestuj typ składnika wysyłającego, a jeśli jest IMarqueeWidgetto , wywołaj jego Refresh metodę.

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

Dodawanie czasowników Projektant do niestandardowego Projektant

Czasownik projektanta to polecenie menu połączone z procedurą obsługi zdarzeń. Projektant czasowniki są dodawane do menu skrótów składnika w czasie projektowania. W celu uzyskania więcej informacji, zobacz następujący temat: DesignerVerb.

Do projektantów zostaną dodane dwa czasowniki projektanta: Uruchamianie testu i zatrzymywanie testów. Te czasowniki umożliwiają wyświetlanie zachowania MarqueeControl w czasie wykonywania w czasie projektowania. Te czasowniki zostaną dodane do elementu MarqueeControlRootDesigner.

Po wywołaniu polecenia Uruchom test program obsługi zdarzeń czasownika wywoła metodę StartMarquee w pliku MarqueeControl. Po wywołaniu zatrzymania testu program obsługi zdarzeń czasownika wywoła metodę StopMarquee w obiekcie MarqueeControl. Implementacja StartMarquee metod i StopMarquee wywołuje te metody na zawarte kontrolki, które implementują IMarqueeWidget, więc wszystkie zawarte IMarqueeWidget kontrolki również będą uczestniczyć w teście.

Aby dodać czasowniki projektanta do projektantów niestandardowych

  1. MarqueeControlRootDesigner W klasie dodaj programy obsługi zdarzeń o nazwach OnVerbRunTest i 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. Połączenie tych programów obsługi zdarzeń do odpowiadających im czasowników projektanta. MarqueeControlRootDesigner dziedziczy element DesignerVerbCollection z klasy bazowej. Utworzysz dwa nowe DesignerVerb obiekty i dodasz je do tej kolekcji w metodzie Initialize .

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

Tworzenie niestandardowego obiektu UITypeEditor

Podczas tworzenia niestandardowego środowiska czasu projektowania dla użytkowników często pożądane jest utworzenie niestandardowej interakcji z okno Właściwości. Można to zrobić, tworząc element UITypeEditor.

Kontrolka MarqueeBorder uwidacznia kilka właściwości w okno Właściwości. Dwie z tych właściwości MarqueeSpinDirection i MarqueeLightShape są reprezentowane przez wyliczenia. Aby zilustrować użycie edytora typów interfejsu użytkownika, MarqueeLightShape właściwość będzie mieć skojarzona UITypeEditor klasa.

Aby utworzyć niestandardowy edytor typów interfejsu użytkownika

  1. MarqueeBorder Otwórz plik źródłowy w Edytorze kodu.

  2. W definicji MarqueeBorder klasy zadeklaruj klasę o nazwie LightShapeEditor , która pochodzi z klasy 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. Zadeklaruj zmienną IWindowsFormsEditorService wystąpienia o nazwie editorService.

    private IWindowsFormsEditorService editorService = null;
    
    Private editorService As IWindowsFormsEditorService = Nothing
    
  4. Zastąpij metodę GetEditStyle . Ta implementacja zwraca wartość DropDown, która informuje środowisko projektowe o sposobie wyświetlania elementu 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. Zastąpij metodę EditValue . Ta implementacja wysyła zapytanie do środowiska projektowego IWindowsFormsEditorService dla obiektu. W przypadku pomyślnego utworzenia elementu LightShapeSelectionControl. Metoda jest wywoływana DropDownControl w celu uruchomienia LightShapeEditormetody . Wartość zwracana z tego wywołania jest zwracana do środowiska projektowego.

    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
    

Tworzenie kontrolki widoku dla niestandardowego interfejsu użytkownikaTypeEditor

Właściwość MarqueeLightShape obsługuje dwa typy lekkich kształtów: Square i Circle. Utworzysz niestandardową kontrolkę używaną wyłącznie do celów graficznego wyświetlania tych wartości w okno Właściwości. Ta kontrolka niestandardowa będzie używana przez Użytkownika UITypeEditor do interakcji z okno Właściwości.

Aby utworzyć kontrolkę widoku dla niestandardowego edytora typów interfejsu użytkownika

  1. Dodaj nowy UserControl element do MarqueeControlLibrary projektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową kontrolki LightShapeSelectionControl.

  2. Przeciągnij dwie Panel kontrolki z przybornika na LightShapeSelectionControl. Nazwij je squarePanel i circlePanel. Ułóż je obok siebie. Size Ustaw właściwość obu Panel kontrolek na (60, 60). Location Ustaw właściwość kontrolki squarePanel na (8, 10). Location Ustaw właściwość kontrolki circlePanel na (80, 10). Na koniec ustaw Size właściwość LightShapeSelectionControlna (150, 80).

  3. LightShapeSelectionControl Otwórz plik źródłowy w Edytorze kodu. W górnej części pliku zaimportuj System.Windows.Forms.Design przestrzeń nazw:

    Imports System.Windows.Forms.Design
    
    using System.Windows.Forms.Design;
    
  4. Zaimplementuj ClicksquarePanel programy obsługi zdarzeń dla kontrolek i .circlePanel Te metody wywołują CloseDropDown , aby zakończyć sesję edycji niestandardowej 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. Zadeklaruj zmienną IWindowsFormsEditorService wystąpienia o nazwie editorService.

    Private editorService As IWindowsFormsEditorService
    
    private IWindowsFormsEditorService editorService;
    
  6. Zadeklaruj zmienną MarqueeLightShape wystąpienia o nazwie lightShapeValue.

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
  7. W konstruktorze LightShapeSelectionControl dołącz Click programy obsługi zdarzeń do zdarzeń squarePanel i circlePanel kontrolek Click . Ponadto zdefiniuj przeciążenie konstruktora, które przypisuje MarqueeLightShape wartość ze środowiska projektowego lightShapeValue do pola.

    // 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. W metodzie Dispose odłącz programy obsługi zdarzeń 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. W Eksplorator rozwiązań kliknij przycisk Pokaż wszystkie pliki. Otwórz kontrolkę LightShapeSelectionControl. Projektant.cs lub LightShapeSelectionControl. Projektant.vb i usuń domyślną definicję Dispose metody.

  10. Zaimplementuj LightShape właściwość .

    // 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. Zastąpij metodę OnPaint . Ta implementacja narysuje wypełniony kwadrat i okrąg. Spowoduje to również wyróżnienie wybranej wartości przez rysowanie obramowania wokół jednego lub drugiego kształtu.

    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
    

Testowanie kontrolki niestandardowej w Projektant

W tym momencie możesz skompilować MarqueeControlLibrary projekt. Przetestuj implementację, tworząc kontrolkę dziedziczą z MarqueeControl klasy i używając jej w formularzu.

Aby utworzyć niestandardową implementację MarqueeControl

  1. Otwórz DemoMarqueeControl plik w Projektant Windows Forms. Spowoduje to utworzenie wystąpienia DemoMarqueeControl typu i wyświetlenie go w wystąpieniu MarqueeControlRootDesigner typu.

  2. W przyborniku otwórz kartę MarqueeControlLibrary Components (Składniki marqueeControlLibrary). Zobaczysz kontrolki MarqueeBorder i MarqueeText dostępne do wyboru.

  3. Przeciągnij wystąpienie kontrolki MarqueeBorder na powierzchnię projektową DemoMarqueeControl . Zadokuj tę MarqueeBorder kontrolkę do kontrolki nadrzędnej.

  4. Przeciągnij wystąpienie kontrolki MarqueeText na powierzchnię projektową DemoMarqueeControl .

  5. Stwórz rozwiązanie.

  6. Kliknij prawym przyciskiem myszy ikonę DemoMarqueeControl i z menu skrótów wybierz opcję Uruchom test , aby uruchomić animację. Kliknij pozycję Zatrzymaj test , aby zatrzymać animację.

  7. Otwórz formularz Form1 w widoku projektu.

  8. Umieść dwie Button kontrolki w formularzu. Nazwij je startButton i stopButton, i zmień wartości właściwości odpowiednio na Start i Stop.Text

  9. Zaimplementuj Click programy obsługi zdarzeń dla obu Button kontrolek.

  10. W przyborniku otwórz kartę MarqueeControlTest Components (Składniki MarqueeControlTest). Zostanie wyświetlona DemoMarqueeControl opcja dostępna do wyboru.

  11. Przeciągnij wystąpienie DemoMarqueeControl obiektu na powierzchni projektowej Form1 .

  12. W programach obsługi zdarzeń Click wywołaj Start metody i Stop w pliku 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. Ustaw projekt jako projekt startowy MarqueeControlTest i uruchom go. Zostanie wyświetlony formularz z wyświetlonym elementem DemoMarqueeControl. Wybierz przycisk Start, aby uruchomić animację. Powinien zostać wyświetlony tekst migający i światła poruszające się wokół obramowania.

Następne kroki

Demonstruje MarqueeControlLibrary prostą implementację kontrolek niestandardowych i skojarzonych projektantów. Ten przykład można ulepszyć na kilka sposobów:

  • Zmień wartości właściwości elementu DemoMarqueeControl w projektancie. Dodaj więcej MarqueBorder kontrolek i zadokuj je w swoich wystąpieniach nadrzędnych, aby utworzyć efekt zagnieżdżony. Poeksperymentuj UpdatePeriod z różnymi ustawieniami właściwości i powiązanymi z światłem.

  • Utwórz własne implementacje programu IMarqueeWidget. Możesz na przykład utworzyć migające "znak neonowy" lub animowany znak z wieloma obrazami.

  • Dodatkowo dostosuj środowisko projektowania. Możesz spróbować zaciemniać więcej właściwości niż Enabled i Visible, i można dodać nowe właściwości. Dodaj nowe czasowniki projektanta, aby uprościć typowe zadania, takie jak zadokowanie kontrolek podrzędnych.

  • Licencjonuj element MarqueeControl.

  • Określ sposób serializacji kontrolek i sposób ich generowania. Aby uzyskać więcej informacji, zobacz Dynamiczne generowanie i kompilacja kodu źródłowego.

Zobacz też