Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Środowisko czasu projektowania dla kontrolki niestandardowej można poprawić, tworząc skojarzonego projektanta niestandardowego.
Ostrzeżenie
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 Windows i ważne jest, aby przejrzeć zmiany projektanta zgodnie z artykułem .NET Framework.
W tym artykule pokazano, jak utworzyć niestandardowego projektanta dla niestandardowej kontrolki. Zaimplementujesz typ MarqueeControl oraz powiązaną klasę projektanta o nazwie MarqueeControlRootDesigner.
Typ MarqueeControl implementuje wyświetlacz podobny do markizy teatru z animowanymi światłami i tekstem.
Projektant tej kontrolki współdziała ze środowiskiem projektowym, aby zapewnić spersonalizowane środowisko projektowania. Za pomocą projektanta niestandardowego można zestawić niestandardową MarqueeControl implementację z animowanymi światłami i migoczącym tekstem w różnych 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 Twoja kontrolka niestandardowa będzie wyglądała mniej więcej tak:
Aby uzyskać pełną listę kodu, zobacz Jak: utworzyć kontrolkę Windows Forms, która wykorzystuje funkcje Design-Time.
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
Dodaj projekt biblioteki kontrolek formularzy Windows Forms do rozwiązania. Nadaj projektowi nazwę MarqueeControlLibrary.
Za pomocą Eksploratora 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.
Dodaj nowy UserControl element do
MarqueeControlLibraryprojektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową MarqueeControl.Za pomocą Eksploratora rozwiązań utwórz nowy folder w projekcie
MarqueeControlLibrary.Kliknij prawym przyciskiem myszy folder Projekt i dodaj nową klasę. Nadaj mu nazwę MarqueeControlRootDesigner.
Musisz użyć typów z zestawu System.Design, więc dodaj to odwołanie do
MarqueeControlLibraryprojektu.
Odwołanie do projektu kontrolki niestandardowej
Użyjesz MarqueeControlTest projektu do przetestowania kontrolki niestandardowej. Projekt testowy zyska dostęp do kontrolki niestandardowej podczas dodawania odwołania do zestawu MarqueeControlLibrary.
W projekcie MarqueeControlTest dodaj odwołanie do zestawu MarqueeControlLibrary. 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 projektanta niestandardowego
Kontrolka niestandardowa UserControl będzie pochodzić z klasy . Pozwala to twojej kontrolce zawierać inne kontrolki i zapewnia jej szeroki zakres domyślnej funkcjonalności.
Kontrolka niestandardowa będzie mieć skojarzonego projektanta niestandardowego. Dzięki temu można utworzyć unikatowe środowisko projektowe dostosowane specjalnie do niestandardowej kontrolki.
Możesz skojarzyć kontrolkę z jej projektantem, używając klasy DesignerAttribute. Ponieważ opracowujesz całe zachowanie kontrolki niestandardowej w czasie projektowania, projektant niestandardowy zaimplementuje IRootDesigner interfejs.
Aby zdefiniować kontrolkę niestandardową i jej projektant niestandardowy
MarqueeControlOtwó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.DesignDodaj element DesignerAttribute do deklaracji
MarqueeControlklasy. 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 UserControlMarqueeControlRootDesignerOtwó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.DesignZmień deklarację
MarqueeControlRootDesigner, aby dziedziczyć z klasy DocumentDesigner. Zastosuj ToolboxItemFilterAttribute, aby określić interakcję projektanta z Toolbox.Uwaga / Notatka
Definicja
MarqueeControlRootDesignerklasy 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 DocumentDesignerZdefiniuj
MarqueeControlRootDesignerkonstruktor 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
Dodaj nowy UserControl element do
MarqueeControlTestprojektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową DemoMarqueeControl.DemoMarqueeControlOtwórz plik w Edytorze kodu. W górnej części pliku zaimportujMarqueeControlLibraryprzestrzeń nazw:Imports MarqueeControlLibraryusing MarqueeControlLibrary;Zmień deklarację
DemoMarqueeControl, aby dziedziczyć z klasyMarqueeControl.Skompiluj projekt.
Otwórz formularz Form1 w programie Windows Forms Designer.
Znajdź kartę MarqueeControlTest Components w przyborniku i otwórz ją. Przeciągnij
DemoMarqueeControlz przybornika na formularz.Skompiluj projekt.
Konfiguracja projektu do debugowania Design-Time
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.
Kliknij projekt prawym przyciskiem myszy
MarqueeControlLibraryi wybierz polecenie Właściwości.W oknie dialogowym MarqueeControlLibrary Property Pages (Strony właściwości MarqueeControlLibrary ) wybierz stronę Debug (Debugowanie ).
W sekcji Rozpocznij akcję wybierz pozycję Uruchom program zewnętrzny. Będziesz debugować oddzielne wystąpienie programu Visual Studio, dlatego kliknij wielokropek (
), aby przejść do środowiska IDE programu Visual Studio. Nazwa pliku wykonywalnego to devenv.exe, a jeśli zainstalowano ją w domyślnej lokalizacji, jej ścieżka to %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.Kliknij przycisk OK, aby zamknąć okno dialogowe.
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 integrację projektanta
Otwórz plik źródłowy MarqueeControlRootDesigner w Edytorze kodu i umieść punkt przerwania w instrukcji WriteLine .
Naciśnij F5 , aby rozpocząć sesję debugowania.
Zostanie utworzone nowe wystąpienie programu Visual Studio.
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.
Otwórz element
DemoMarqueeControlw projektancie.Wystąpienie debugowania programu Visual Studio uzyskuje fokus i wykonywanie zatrzymuje się w punkcie przerwania. Naciśnij 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 funkcjonalności kontrolki oraz projektanta.
Implementuj kontrolkę niestandardową
Element MarqueeControl jest UserControl z odrobiną personalizacji. 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ą interfejs IMarqueeWidget, Start i Stop wyliczają każdą kontrolkę podrzędną i odpowiednio wywołują metody StartMarquee i StopMarquee w każdej kontrolce podrzędnej, która implementuje element IMarqueeWidget.
Wygląd kontrolek MarqueeBorder i MarqueeText jest zależny od układu, dlatego MarqueeControl nadpisuje metodę OnLayout i wywołuje PerformLayout na kontrolach podrzędnych tego typu.
Jest to zakres MarqueeControl dostosowań. Funkcje w czasie wykonywania są implementowane przez kontrolki MarqueeBorder i MarqueeText, a funkcje projektowe są implementowane przez klasy MarqueeBorderDesigner i MarqueeControlRootDesigner.
Aby zaimplementować kontrolkę niestandardową
MarqueeControlOtwórz plik źródłowy w Edytorze kodu. ZaimplementujStartmetody iStop.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 SubZastąp 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
Stwórz kontrolkę podrzędną dla swojej kontrolki niestandardowej
Element MarqueeControl będzie hostować dwa rodzaje kontroli podrzędnej: kontrolkę MarqueeBorder i kontrolkę MarqueeText .
MarqueeBorder: Ten element sterujący rysuje obramowanie "świateł" wokół swoich krawędzi. Światła błyskają w sekwencji, więc wydają się poruszać wzdłuż obramowania. Szybkość migania świateł jest kontrolowana przez właściwość o nazwieUpdatePeriod. Kilka innych właściwości niestandardowych określa inne aspekty wyglądu kontrolki. Dwie metody, nazywaneStartMarqueeiStopMarquee, kontrolują, kiedy animacja rozpoczyna się i zatrzymuje.MarqueeText: Ten element sterujący wyświetla migający ciąg. Podobnie jak kontrolkaMarqueeBorder, szybkość, z jaką tekst miga, jest kontrolowana przez właściwośćUpdatePeriod. KontrolkaMarqueeTextma również metodyStartMarqueeiStopMarqueewspó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 odkryć wszelkie podrzędne kontrole związane z Marquee i zapewnić 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 stworzyć kontrolkę podrzędną dla kontrolki niestandardowej
Dodaj nowy element klasy do
MarqueeControlLibraryprojektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową "IMarqueeWidget".IMarqueeWidgetOtwórz plik źródłowy w Edytorze kodu i zmień deklarację zclassnainterface:// 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 IMarqueeWidgetDodaj 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 InterfaceDodaj nowy element Niestandardowa kontrolka
MarqueeControlLibrarydo projektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową "MarqueeText".Przeciągnij BackgroundWorker komponent z przybornika na kontrolkę
MarqueeText. Ten składnik pozwoli kontrolceMarqueeTextna asynchroniczną aktualizację.W oknie Właściwości ustaw właściwości BackgroundWorker i
WorkerReportsProgressskładnika WorkerSupportsCancellation na 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).
MarqueeTextOtwó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.DesignZmień deklarację
MarqueeText, aby dziedziczyła z Label i implementowała interfejsIMarqueeWidget.[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidgetZadeklaruj zmienne instancji, które odpowiadają ujawnionym właściwościom, i zainicjuj je w konstruktorze. Pole
isLitokreśla, czy tekst ma być malowany w kolorze, który jest podany przez właściwośćLightColor.// When isLit is true, the text is painted in the light color; // When isLit is false, the text is painted in the dark color. // This value changes whenever the BackgroundWorker component // raises the ProgressChanged event. private bool isLit = true; // These fields back the public properties. private int updatePeriodValue = 50; private Color lightColorValue; private Color darkColorValue; // These brushes are used to paint the light and dark // colors of the text. private Brush lightBrush; private Brush darkBrush; // This component updates the control asynchronously. private BackgroundWorker backgroundWorker1; public MarqueeText() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // Initialize light and dark colors // to the control's default values. this.lightColorValue = this.ForeColor; this.darkColorValue = this.BackColor; this.lightBrush = new SolidBrush(this.lightColorValue); this.darkBrush = new SolidBrush(this.darkColorValue); }' When isLit is true, the text is painted in the light color; ' When isLit is false, the text is painted in the dark color. ' This value changes whenever the BackgroundWorker component ' raises the ProgressChanged event. Private isLit As Boolean = True ' These fields back the public properties. Private updatePeriodValue As Integer = 50 Private lightColorValue As Color Private darkColorValue As Color ' These brushes are used to paint the light and dark ' colors of the text. Private lightBrush As Brush Private darkBrush As Brush ' This component updates the control asynchronously. Private WithEvents backgroundWorker1 As BackgroundWorker Public Sub New() ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Initialize light and dark colors ' to the control's default values. Me.lightColorValue = Me.ForeColor Me.darkColorValue = Me.BackColor Me.lightBrush = New SolidBrush(Me.lightColorValue) Me.darkBrush = New SolidBrush(Me.darkColorValue) End SubZaimplementuj interfejs
IMarqueeWidget.Metody
StartMarqueeiStopMarqueewywołują metody BackgroundWorker i RunWorkerAsync składnika CancelAsync, aby uruchomić i zatrzymać animację.Atrybuty Category i Browsable są zastosowane do właściwości
UpdatePeriod, aby była ona widoczna w niestandardowej sekcji okna 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 PropertyZaimplementuj metody dostępu właściwości. Uwidocznisz dwie właściwości klientom:
LightColoriDarkColor. Atrybuty Category i Browsable są stosowane do tych właściwości, więc właściwości są wyświetlane w sekcji niestandardowej okna 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 PropertyZaimplementuj procedury obsługi dla zdarzeń BackgroundWorker i DoWork komponentu ProgressChanged.
Procedura obsługi zdarzeń DoWork śpi przez liczbę milisekund określoną przez
UpdatePeriod, a następnie zgłasza zdarzenie ProgressChanged, dopóki kod nie zatrzyma animacji, wywołując metodę CancelAsync.Program ProgressChanged obsługi zdarzeń przełącza tekst między jego jasnym i ciemnym stanem, aby stworzyć efekt migotania.
// 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 SubZastąp 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 SubNaciśnij F6 , aby skompilować rozwiązanie.
Utwórz kontrolkę podrzędną MarqueeBorder
Kontrolka MarqueeBorder jest nieco bardziej wyrafinowana niż kontrolka MarqueeText . Ma więcej właściwości, a animacja w metodzie OnPaint jest bardziej skomplikowana. W zasadzie jest to dość podobne do kontrola MarqueeText.
Ponieważ kontrolka MarqueeBorder może mieć kontrolki podrzędne, musi być świadoma zdarzeń Layout.
Aby utworzyć kontrolkę MarqueeBorder
Dodaj nowy element Niestandardowa kontrolka
MarqueeControlLibrarydo projektu. Nadaj nowemu plikowi źródłowemu nazwę podstawową "MarqueeBorder".Przeciągnij BackgroundWorker komponent z przybornika na kontrolkę
MarqueeBorder. Ten składnik pozwoli kontrolceMarqueeBorderna asynchroniczną aktualizację.W oknie Właściwości ustaw właściwości BackgroundWorker i
WorkerReportsProgressskładnika WorkerSupportsCancellation na 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).W oknie Właściwości wybierz przycisk Zdarzenia . Dołącz programy obsługi dla zdarzeń DoWork i ProgressChanged.
MarqueeBorderOtwó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.DesignZmień deklarację
MarqueeBorder, aby dziedziczyć z Panel oraz zaimplementować interfejsIMarqueeWidget.[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 IMarqueeWidgetZadeklaruj dwie wyliczenia do zarządzania
MarqueeBorderstanem kontrolki:MarqueeSpinDirection, która określa kierunek, w którym światła "obracają się" wokół obramowania, iMarqueeLightShape, które określają kształt świateł (kwadrat lub okrągły). Umieść te deklaracje przed deklaracjąMarqueeBorderklasy.// 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 EnumZadeklaruj zmienne instancji, które odpowiadają ujawnionym 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 SubZaimplementuj interfejs
IMarqueeWidget.Metody
StartMarqueeiStopMarqueewywołują metody BackgroundWorker i RunWorkerAsync składnika CancelAsync, aby uruchomić i zatrzymać animację.Ponieważ kontrolka
MarqueeBordermoże zawierać kontrolki podrzędne, metodaStartMarqueewylicza wszystkie kontrolki podrzędne i wywołujeStartMarqueena tych, które implementują elementIMarqueeWidget. MetodaStopMarqueema 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 PropertyZaimplementuj metody dostępu właściwości. Kontrolka
MarqueeBorderma 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 PropertyZaimplementuj procedury obsługi dla zdarzeń BackgroundWorker i DoWork komponentu ProgressChanged.
Procedura obsługi zdarzeń DoWork śpi przez liczbę milisekund określoną przez
UpdatePeriod, a następnie zgłasza zdarzenie ProgressChanged, dopóki kod nie zatrzyma animacji, wywołując metodę 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 SubZaimplementuj metody pomocnicze
IsLitiDrawLight.Metoda
IsLitokreśla kolor światła na danej pozycji. Światła , które są "oświetlone" są rysowane w kolorzeLightColorpodanym przez właściwość, a te, które są "ciemne" są rysowane w kolorze podanymDarkColorprzez właściwość.Metoda
DrawLightrysuje ś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 SubZastąp metody OnLayout i OnPaint.
Metoda OnPaint rysuje światła wzdłuż krawędzi kontrolki
MarqueeBorder.OnPaint Ponieważ metoda zależy od wymiarów
MarqueeBorderkontrolki, 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 niestandardowego projektanta do przyciemniania i filtrowania właściwości
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 . Projektant ten zapewnia dostosowane zachowanie, które jest odpowiednie w kontekście projektanta bazowego.
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ń do akcesora właściwości komponentu jest nazywane "cieniowaniem". Umożliwia projektantowi śledzenie wartości ustawionej przez użytkownika i opcjonalne przekazanie tej wartości do projektowanego komponentu.
W tym przykładzie właściwości Visible i Enabled zostaną przesłonięte przez MarqueeBorderDesigner, co uniemożliwia użytkownikowi uczynienie kontrolki MarqueeBorder niewidoczną lub wyłączoną podczas projektowania.
Projektanci mogą również dodawać i usuwać właściwości. W tym przykładzie właściwość Padding zostanie usunięta w czasie projektowania, ponieważ kontrolka MarqueeBorder programowo ustawia wypełnienie na podstawie rozmiaru świateł określonych przez właściwość LightSize.
Klasa bazowa MarqueeBorderDesigner to ComponentDesigner, która zawiera metody, które mogą zmieniać atrybuty, właściwości i zdarzenia odkryte 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:
Elementy dodawaj lub usuwaj tylko w metodach
PreFilterZmodyfikuj istniejące elementy tylko w metodach
PostFilterZawsze należy najpierw wywołać implementację podstawową w metodach
PreFilterZawsze na końcu wywołaj implementację podstawową 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 zacienionych właściwości, co zwalnia z konieczności tworzenia określonych zmiennych wystąpienia.
Aby utworzyć projektanta niestandardowego do cieniowania i filtrowania właściwości
Kliknij prawym przyciskiem myszy folder Projekt i dodaj nową klasę. Nadaj plikowi źródłowemu nazwę podstawową MarqueeBorderDesigner.
Otwórz plik źródłowy MarqueeBorderDesigner 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.DesignZmień deklarację
MarqueeBorderDesignertak, aby dziedziczyła z ParentControlDesigner.Ponieważ kontrolka
MarqueeBordermoże zawierać kontrolki podrzędne,MarqueeBorderDesignerdziedziczy z klasy ParentControlDesigner, która obsługuje interakcję między elementem nadrzędnym a podrzędnym.namespace MarqueeControlLibrary.Design { public class MarqueeBorderDesigner : ParentControlDesigner {Namespace MarqueeControlLibrary.Design Public Class MarqueeBorderDesigner Inherits ParentControlDesignerZastą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 SubZaimplementuj właściwości Enabled i Visible. Te implementacje osłaniają 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 komponentów
Klasa MarqueeControlRootDesigner zapewnia dostosowane środowisko projektowe dla wystąpień MarqueeControl. Większość funkcjonalności czasu projektowania jest dziedziczona z klasy DocumentDesigner. Kod będzie implementował dwa konkretne dostosowania: obsługę zmian w komponentach i dodawanie czasowników projektowych.
Gdy użytkownicy projektują swoje MarqueeControl wystąpienia, twój główny projektant będzie śledzić zmiany w MarqueeControl i jego kontrolkach podrzędnych. Środowisko czasu projektowania oferuje wygodną usługę, IComponentChangeService służącą do śledzenia zmian stanu składnika.
Aby uzyskać odwołanie do tej usługi, należy wykonać zapytanie względem środowiska za pomocą metody GetService. Jeśli zapytanie zakończy się pomyślnie, projektant może dołączyć program obsługi zdarzenia ComponentChanged i wykonać wszelkie zadania wymagane do zachowania spójnego stanu w czasie projektowania.
W przypadku MarqueeControlRootDesigner klasy wywołasz metodę Refresh dla każdego IMarqueeWidget obiektu zawartego w MarqueeControl. To spowoduje, że obiekt IMarqueeWidget zostanie odpowiednio przemalowany, gdy zmienią się właściwości związane z jego rodzicem Size.
Aby obsłużyć zmiany elementów
MarqueeControlRootDesignerOtwórz plik źródłowy w Edytorze kodu i zastąpij metodę Initialize . Wywołaj podstawową implementację Initialize i zapytaj o 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 IfZaimplementuj funkcję obsługi zdarzeń OnComponentChanged . Przetestuj typ składnika wysyłającego, a jeśli jest
IMarqueeWidget, wywołaj jego metodę Refresh.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
Dodaj czasowniki projektanta do swojego niestandardowego projektu
W terminologii projektowania, czasownik to polecenie menu powiązane z obsługą zdarzeń. Czasowniki projektowe są dodawane do menu kontekstowego składnika w czasie projektowania. Aby uzyskać więcej informacji, zobacz DesignerVerb.
Dodasz dwa czasowniki operacyjne do swoich projektantów: Uruchom test i Zatrzymaj test. Te czasowniki pozwalają na obserwowanie zachowania MarqueeControl w czasie wykonywania podczas projektowania. Te czasowniki zostaną dodane do elementu MarqueeControlRootDesigner.
Po wywołaniu Uruchom test program obsługi zdarzeń czasownika wywoła metodę StartMarquee na MarqueeControl. Kiedy wywołane zostanie zatrzymanie testu, obsługujący zdarzenie czasownika wywoła metodę StopMarquee w obiekcie MarqueeControl. Implementacja metod StartMarquee i StopMarquee wywołuje te metody na kontrolkach zawartych, które implementują IMarqueeWidget, więc jakiekolwiek zawarte kontrolki IMarqueeWidget również będą uczestniczyć w teście.
Aby dodać czasowniki projektanta do swoich niestandardowych projektantów
MarqueeControlRootDesignerW klasie dodaj programy obsługi zdarzeń o nazwachOnVerbRunTestiOnVerbStopTest.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 SubPołącz te programy obsługi zdarzeń z odpowiednimi poleceniami projektanta.
MarqueeControlRootDesignerdziedziczy 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 w projektowaniu dla użytkowników często wskazane jest utworzenie niestandardowej interakcji z oknem właściwości. Można to zrobić, tworząc element UITypeEditor.
Kontrolka MarqueeBorder uwidacznia kilka właściwości w oknie Właściwości. Dwie z tych właściwości MarqueeSpinDirection i MarqueeLightShape są reprezentowane przez wyliczenia. Aby zilustrować użycie edytora interfejsu użytkownika, MarqueeLightShape właściwość będzie mieć skojarzoną UITypeEditor klasę.
Aby utworzyć niestandardowy edytor typów interfejsu użytkownika
MarqueeBorderOtwórz plik źródłowy w Edytorze kodu.W definicji
MarqueeBorderklasy zadeklaruj klasę o nazwieLightShapeEditor, 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 UITypeEditorZadeklaruj zmienną instancji IWindowsFormsEditorService o nazwie
editorService.private IWindowsFormsEditorService editorService = null;Private editorService As IWindowsFormsEditorService = NothingZastąp 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 FunctionZastąp metodę EditValue. Ta implementacja zapytuje środowisko projektowe o obiekt IWindowsFormsEditorService. Jeśli się powiedzie, zostanie utworzony
LightShapeSelectionControl. Metoda DropDownControl jest wywoływana w celu uruchomieniaLightShapeEditor. 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 kontrolkę niestandardową używaną wyłącznie do celów graficznego wyświetlania tych wartości w oknie Właściwości. Ta kontrolka niestandardowa będzie używana przez Użytkownika UITypeEditor do interakcji z oknem Właściwości.
Aby utworzyć kontrolkę wyświetlania dla personalizowanego edytora typów interfejsu użytkownika
Dodaj nowy UserControl element do
MarqueeControlLibraryprojektu. Nadaj nowemu plikowi źródłowemu nazwę bazową LightShapeSelectionControl.Przeciągnij dwie Panel kontrolki z przybornika na
LightShapeSelectionControl. Nazwij jesquarePanelicirclePanel. Ułóż je obok siebie. Size Ustaw właściwość obu Panel kontrolek na (60, 60). Location Ustaw właściwość kontrolkisquarePanelna (8, 10). Location Ustaw właściwość kontrolkicirclePanelna (80, 10). Na koniec ustaw Size właściwośćLightShapeSelectionControlna (150, 80).LightShapeSelectionControlOtwó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.Designusing System.Windows.Forms.Design;Zaimplementuj programy obsługi zdarzeń dla kontrolek Click i
squarePanel. Te metody wywołują CloseDropDown, aby zakończyć niestandardową sesję edycji 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 SubZadeklaruj zmienną instancji IWindowsFormsEditorService o nazwie
editorService.Private editorService As IWindowsFormsEditorServiceprivate IWindowsFormsEditorService editorService;Zadeklaruj zmienną instancji
MarqueeLightShapeo nazwielightShapeValue.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.SquareW konstruktorze
LightShapeSelectionControlprzypisz programy obsługi zdarzeń Click do zdarzeń kontroleksquarePanelicirclePanelClick. Ponadto zdefiniuj przeciążenie konstruktora, które przypisuje wartośćMarqueeLightShapeze środowiska projektowego do polalightShapeValue.// 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 SubW 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 SubW eksploratorze rozwiązań kliknij przycisk Pokaż wszystkie pliki. Otwórz plik LightShapeSelectionControl.Designer.cs lub LightShapeSelectionControl.Designer.vb i usuń domyślną definicję Dispose metody.
Zaimplementuj
LightShapewł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 PropertyZastąp 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 projektancie
W tym momencie możesz zbudować projekt MarqueeControlLibrary. Przetestuj implementację, tworząc kontrolkę dziedziczą z MarqueeControl klasy i używając jej w formularzu.
Aby utworzyć niestandardową implementację MarqueeControl
Otwórz
DemoMarqueeControlw programie Windows Forms Designer. Powoduje to utworzenie wystąpienia typuDemoMarqueeControli wyświetlenie go w wystąpieniu typuMarqueeControlRootDesigner.W przyborniku otwórz kartę MarqueeControlLibrary Components. Zobaczysz kontrolki
MarqueeBorderiMarqueeTextdostępne do wyboru.Przeciągnij wystąpienie kontrolki
MarqueeBorderna powierzchnię projektowąDemoMarqueeControl. Zadokuj tęMarqueeBorderkontrolkę do kontrolki nadrzędnej.Przeciągnij wystąpienie kontrolki
MarqueeTextna powierzchnię projektowąDemoMarqueeControl.Skompiluj rozwiązanie.
Kliknij prawym przyciskiem myszy ikonę
DemoMarqueeControli z menu skrótów wybierz opcję Uruchom test , aby uruchomić animację. Kliknij pozycję Zatrzymaj test , aby zatrzymać animację.Otwórz formularz Form1 w widoku projektu.
Umieść dwie Button kontrolki w formularzu. Nazwij je
startButtonistopButton, oraz zmień wartości właściwości Text na Start i Stop.Zaimplementuj Click programy obsługi zdarzeń dla obu Button kontrolek.
W Przyborniku otwórz kartę MarqueeControlTest Components. Zobaczysz element
DemoMarqueeControldostępny do wyboru.Przeciągnij wystąpienie
DemoMarqueeControlna powierzchnię projektu Form1.W programach obsługi zdarzeń Click wywołaj metody
StartiStopnaDemoMarqueeControl.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_Clickprivate void startButton_Click(object sender, System.EventArgs e) { this.demoMarqueeControl1.Start(); } private void stopButton_Click(object sender, System.EventArgs e) { this.demoMarqueeControl1.Stop(); }Ustaw projekt jako projekt startowy
MarqueeControlTesti uruchom go. Zobaczysz formularz wyświetlający twójDemoMarqueeControl. Wybierz przycisk Start , aby uruchomić animację. Powinieneś zobaczyć migający tekst i światła poruszające się wokół obramowania.
Dalsze kroki
Demonstruje się MarqueeControlLibrary prostą implementację kontrolek niestandardowych oraz skojarzonych projektantów. Ten przykład można ulepszyć na kilka sposobów:
Zmień wartości właściwości elementu
DemoMarqueeControlw projektancie. Dodaj więcejMarqueBorderkontrolek i zadokuj je we ich nadrzędnych wystąpieniach, aby utworzyć efekt zagnieżdżony. Eksperymentuj z różnymi ustawieniami dlaUpdatePeriodi właściwościami związanymi ze światłem.Utwórz własne implementacje programu
IMarqueeWidget. Możesz na przykład utworzyć "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.Zarządzaj sposobem, w jaki twoje elementy sterujące są serializowane oraz jak generowany jest dla nich kod. Aby uzyskać więcej informacji, zobacz Dynamiczne generowanie i kompilacja kodu źródłowego.
Zobacz także
.NET Desktop feedback