Genomgång: Skapa en kontroll som drar nytta av designtidsfunktioner
Designtidsupplevelsen för en anpassad kontroll kan förbättras genom att skapa en associerad anpassad designer.
Försiktighet
Det här innehållet skrevs för .NET Framework. Om du använder .NET 6 eller en senare version använder du det här innehållet med försiktighet. Designersystemet har ändrats för Windows Forms och det är viktigt att du granskar Designer-ändringarna sedan artikeln om .NET Framework.
Den här artikeln visar hur du skapar en anpassad designer för en anpassad kontroll. Du implementerar en MarqueeControl
typ och en associerad designerklass med namnet MarqueeControlRootDesigner
.
Den MarqueeControl
typen implementerar en skärm som liknar en teaterram med animerade lampor och blinkande text.
Designern för den här kontrollen interagerar med designmiljön för att ge en anpassad designtidsupplevelse. Med den anpassade designern kan du sätta ihop en anpassad MarqueeControl
implementering med animerade lampor och blinkande text i många kombinationer. Du kan använda den sammansatta kontrollen i ett formulär som alla andra Windows Forms-kontroller.
När du är klar med den här genomgången ser din anpassade kontroll ut ungefär så här:
Den fullständiga kodlistan finns i Hur man: Skapar en Windows Forms-kontroll som utnyttjar Design-Time Funktioner.
Förutsättningar
För att slutföra den här genomgången behöver du Visual Studio.
Skapa projektet
Det första steget är att skapa programprojektet. Du använder det här projektet för att skapa det program som är värd för den anpassade kontrollen.
Skapa ett nytt Windows Forms-programprojekt i Visual Studio och ge det namnet MarqueeControlTest.
Skapa kontrollbiblioteksprojektet
Lägg till ett Windows Forms Control Library-projekt i lösningen. Ge projektet namnet MarqueeControlLibrary.
Med Solution Explorertar du bort projektets standardkontroll genom att ta bort källfilen med namnet "UserControl1.cs" eller "UserControl1.vb", beroende på vilket språk du väljer.
Lägg till ett nytt UserControl objekt i
MarqueeControlLibrary
-projektet. Ge den nya källfilen ett basnamn för MarqueeControl.Använd Solution Exploreroch skapa en ny mapp i
MarqueeControlLibrary
-projektet.Högerklicka på mappen Design och lägg till en ny klass. Ge den namnet MarqueeControlRootDesigner.
Du måste använda typer från System.Design sammansättning, så lägg till den här referensen i
MarqueeControlLibrary
-projektet.
Referera till projektet för anpassad kontroll
Du kommer att använda projektet MarqueeControlTest
för att testa den anpassade kontrollen. Testprojektet blir medvetet om den anpassade kontrollen när du lägger till en projektreferens till MarqueeControlLibrary
sammansättning.
I MarqueeControlTest
-projektet lägger du till en projektreferens till assemblyn MarqueeControlLibrary
. Använd fliken Projects i dialogrutan Lägg till referens istället för att referera direkt till MarqueeControlLibrary
assembly.
Definiera en anpassad kontroll och den anpassade designern
Din anpassade kontroll härleds från klassen UserControl. Detta gör att din kontroll kan innehålla andra kontroller, och det ger din kontroll en hel del standardfunktioner.
Din anpassade kontroll kommer ha ett associerat anpassat designverktyg. På så sätt kan du skapa en unik designupplevelse som skräddarsytts specifikt för din anpassade kontroll.
Du associerar kontrollen med dess designer med hjälp av klassen DesignerAttribute. Eftersom du utvecklar hela designtidsbeteendet för din anpassade kontroll implementerar den anpassade designern IRootDesigner-gränssnittet.
Så här definierar du en anpassad kontroll och dess anpassade designer
Öppna
MarqueeControl
-källfilen i Code Editor. Importera följande namnområden överst i filen:using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Drawing Imports System.Windows.Forms Imports System.Windows.Forms.Design
Lägg till DesignerAttribute i
MarqueeControl
-klassdeklarationen. Detta associerar den anpassade kontrollen med dess designer.[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : UserControl {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControl
Öppna
MarqueeControlRootDesigner
-källfilen i Code Editor. Importera följande namnområden överst i filen:using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Drawing.Design; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Drawing.Design Imports System.Windows.Forms Imports System.Windows.Forms.Design
Ändra deklarationen för
MarqueeControlRootDesigner
så att den ärver från klassen DocumentDesigner. Använd ToolboxItemFilterAttribute för att ange designerens interaktion med Toolbox.Not
Definitionen för klassen
MarqueeControlRootDesigner
har omslutits av ett namnområde kallat MarqueeControlLibrary.Design. Den här deklarationen placerar designern i ett särskilt namnområde som är reserverat för designrelaterade typer.namespace MarqueeControlLibrary.Design { [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public class MarqueeControlRootDesigner : DocumentDesigner {
Namespace MarqueeControlLibrary.Design <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Public Class MarqueeControlRootDesigner Inherits DocumentDesigner
Definiera konstruktorn för klassen
MarqueeControlRootDesigner
. Infoga en WriteLine-instruktion i konstruktorns brödtext. Detta är användbart för felsökning.public MarqueeControlRootDesigner() { Trace.WriteLine("MarqueeControlRootDesigner ctor"); }
Public Sub New() Trace.WriteLine("MarqueeControlRootDesigner ctor") End Sub
Skapa en instans av din anpassade kontroll
Lägg till ett nytt UserControl objekt i
MarqueeControlTest
-projektet. Ge den nya källfilen ett basnamn för DemoMarqueeControl.Öppna filen
DemoMarqueeControl
i Code Editor. Överst i filen, importera namnrymdMarqueeControlLibrary
:Imports MarqueeControlLibrary
using MarqueeControlLibrary;
Ändra deklarationen för
DemoMarqueeControl
så att den ärver från klassenMarqueeControl
.Skapa projektet.
Öppna Formulär1 i Windows Forms Designer.
Leta upp fliken MarqueeControlTest Components i Toolbox och öppna den. Dra en
DemoMarqueeControl
från Toolbox- till formuläret.Skapa projektet.
Konfigurera projektet för Design-Time-debuggning
När du utvecklar en anpassad designtidsupplevelse är det nödvändigt att felsöka dina kontroller och komponenter. Det finns ett enkelt sätt att konfigurera projektet så att felsökning kan utföras vid designtillfället. Mer information finns i Genomgång: Felsöka anpassade Windows Forms-kontroller under designtid.
Högerklicka på projektet
MarqueeControlLibrary
och välj Egenskaper.I dialogrutan MarqueeControlLibrary Property Pages väljer du sidan Felsöka.
I avsnittet Starta åtgärd väljer du Starta externt program. Du kommer att felsöka en separat instans av Visual Studio, så klicka på ellipsen (
) för att bläddra efter Visual Studio IDE. Namnet på den körbara filen är devenv.exeoch om du har installerat till standardplatsen är sökvägen %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.
Välj OK för att stänga dialogrutan.
Högerklicka på Projektet MarqueeControlLibrary och välj Ange som StartUp Project för att aktivera den här felsökningskonfigurationen.
Kontrollpunkt
Nu är du redo att felsöka designtidsbeteendet för din anpassade kontroll. När du har fastställt att felsökningsmiljön har konfigurerats korrekt testar du associationen mellan den anpassade kontrollen och den anpassade designern.
Testa felsökningsmiljön och designerassociationen
Öppna källfilen MarqueeControlRootDesigner i Code Editor och placera en brytpunkt på WriteLine-instruktionen.
Tryck på F5- för att starta felsökningssessionen.
En ny instans av Visual Studio skapas.
I den nya instansen av Visual Studio öppnar du Lösningen MarqueeControlTest. Du kan enkelt hitta lösningen genom att välja Senaste projekt från menyn Arkiv. Den MarqueeControlTest.sln lösningsfilen visas som den senast använda filen.
Öppna
DemoMarqueeControl
i designern.Felsökningsinstansen av Visual Studio får fokus och körningen stannar vid din brytpunkt. Tryck på F5 för att fortsätta felsökningssessionen.
Nu är allt på plats för att du ska kunna utveckla och felsöka din anpassade kontroll och dess associerade anpassade designer. Resten av den här artikeln fokuserar på information om hur du implementerar funktioner i kontrollen och designern.
Implementera anpassad kontroll
MarqueeControl
är en UserControl med lite anpassning. Den visar två metoder: Start
, som startar markeringsramens animering och Stop
, som stoppar animeringen. Eftersom MarqueeControl
innehåller underordnade kontroller som implementerar IMarqueeWidget
-gränssnittet Start
och Stop
räkna upp varje underordnad kontroll och anropa metoderna StartMarquee
respektive StopMarquee
på varje underordnad kontroll som implementerar IMarqueeWidget
.
Utseendet på kontrollerna MarqueeBorder
och MarqueeText
är beroende av layouten, så MarqueeControl
åsidosätter metoden OnLayout och anropar PerformLayout på underordnade kontroller av den här typen.
Det här är omfattningen av MarqueeControl
-anpassningarna. Körningsfunktionerna implementeras av kontrollerna MarqueeBorder
och MarqueeText
och designtidsfunktionerna implementeras av MarqueeBorderDesigner
- och MarqueeControlRootDesigner
-klasserna.
Implementera din anpassade kontroll
Öppna
MarqueeControl
-källfilen i Code Editor. Implementera metodernaStart
ochStop
.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
Åsidosätt metoden OnLayout.
protected override void OnLayout(LayoutEventArgs levent) { base.OnLayout (levent); // Repaint all IMarqueeWidget children if the layout // has changed. foreach( Control cntrl in this.Controls ) { if( cntrl is IMarqueeWidget ) { Control control = cntrl as Control; control.PerformLayout(); } } }
Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs) MyBase.OnLayout(levent) ' Repaint all IMarqueeWidget children if the layout ' has changed. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) cntrl.PerformLayout() End If Next cntrl End Sub
Skapa en barnkontroll för din egen anpassade kontroll
MarqueeControl
är värd för två typer av barnkontroll: MarqueeBorder
-kontroll och MarqueeText
-kontroll.
MarqueeBorder
: Den här kontrollen ritar en kant av "ljus" runt kanterna. Lamporna blinkar i följd, så de verkar röra sig runt kanten. Hastigheten med vilken lamporna blinkar styrs av en egenskap som kallasUpdatePeriod
. Flera andra anpassade egenskaper bestämmer andra aspekter av kontrollens utseende. Två metoder, som kallasStartMarquee
ochStopMarquee
, styr när animeringen startar och stoppas.MarqueeText
: Det här kontrollelementet renderar en blinkande sträng. Precis som denMarqueeBorder
kontrollen styrs den hastighet med vilken texten blinkar av egenskapenUpdatePeriod
.MarqueeText
-kontrollen har också deStartMarquee
- ochStopMarquee
metoder som är gemensamma förMarqueeBorder
kontroll.
Vid designtillfället gör MarqueeControlRootDesigner
att dessa två kontrolltyper kan läggas till i en MarqueeControl
i valfri kombination.
Vanliga funktioner i de två kontrollerna räknas in i ett gränssnitt som kallas IMarqueeWidget
. Detta gör det möjligt för MarqueeControl
att upptäcka alla Marquee-relaterade barnkontroller och ge dem särskild behandling.
Om du vill implementera funktionen periodisk animering använder du BackgroundWorker objekt från System.ComponentModel-namnområdet. Du kan använda Timer objekt, men när många IMarqueeWidget
objekt finns kan det hända att den enda användargränssnittstråden inte kan hänga med i animeringen.
Skapa en barnkontroll för din anpassade kontroll
Lägg till ett nytt klassobjekt i
MarqueeControlLibrary
-projektet. Ge den nya källfilen ett basnamn för "IMarqueeWidget".Öppna
IMarqueeWidget
-källfilen i Code Editor och ändra deklarationen frånclass
tillinterface
:// 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
Lägg till följande kod i
IMarqueeWidget
-gränssnittet för att exponera två metoder och en egenskap som ändrar markeringsramens animering:// This interface defines the contract for any class that is to // be used in constructing a MarqueeControl. public interface IMarqueeWidget { // This method starts the animation. If the control can // contain other classes that implement IMarqueeWidget as // children, the control should call StartMarquee on all // its IMarqueeWidget child controls. void StartMarquee(); // This method stops the animation. If the control can // contain other classes that implement IMarqueeWidget as // children, the control should call StopMarquee on all // its IMarqueeWidget child controls. void StopMarquee(); // This method specifies the refresh rate for the animation, // in milliseconds. int UpdatePeriod { get; set; } }
' This interface defines the contract for any class that is to ' be used in constructing a MarqueeControl. Public Interface IMarqueeWidget ' This method starts the animation. If the control can ' contain other classes that implement IMarqueeWidget as ' children, the control should call StartMarquee on all ' its IMarqueeWidget child controls. Sub StartMarquee() ' This method stops the animation. If the control can ' contain other classes that implement IMarqueeWidget as ' children, the control should call StopMarquee on all ' its IMarqueeWidget child controls. Sub StopMarquee() ' This method specifies the refresh rate for the animation, ' in milliseconds. Property UpdatePeriod() As Integer End Interface
Lägg till ett nytt anpassat objekt i
MarqueeControlLibrary
-projektet. Ge den nya källfilen ett basnamn för "MarqueeText".Dra en BackgroundWorker komponent från Toolbox- till din
MarqueeText
kontroll. Med den här komponenten kanMarqueeText
-kontrollen uppdateras asynkront.I fönstret Egenskaper anger du BackgroundWorker komponentens egenskaper för
WorkerReportsProgress
och WorkerSupportsCancellation till sant. Med de här inställningarna kan BackgroundWorker komponenten regelbundet generera händelsen ProgressChanged och avbryta asynkrona uppdateringar.Mer information finns i BackgroundWorker Component.
Öppna
MarqueeText
-källfilen i Code Editor. Importera följande namnområden överst i filen:using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Drawing; using System.Threading; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Drawing Imports System.Threading Imports System.Windows.Forms Imports System.Windows.Forms.Design
Ändra deklarationen av
MarqueeText
så att den ärver från Label och implementeraIMarqueeWidget
-gränssnittet:[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {
<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
Deklarera de instansvariabler som motsvarar de exponerade egenskaperna och initiera dem i konstruktorn. Fältet
isLit
avgör om texten ska målas i den färg som anges av egenskapenLightColor
.// 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
Implementera
IMarqueeWidget
-gränssnittet.Metoderna
StartMarquee
ochStopMarquee
anropar BackgroundWorker komponentens RunWorkerAsync- och CancelAsync metoder för att starta och stoppa animeringen.Attributen Category och Browsable tillämpas på egenskapen
UpdatePeriod
så att den visas i ett anpassat avsnitt i fönstret Egenskaper med namnet "Markeringsram".public virtual void StartMarquee() { // Start the updating thread and pass it the UpdatePeriod. this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod); } public virtual void StopMarquee() { // Stop the updating thread. this.backgroundWorker1.CancelAsync(); } [Category("Marquee")] [Browsable(true)] public int UpdatePeriod { get { return this.updatePeriodValue; } set { if (value > 0) { this.updatePeriodValue = value; } else { throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0"); } } }
Public Overridable Sub StartMarquee() _ Implements IMarqueeWidget.StartMarquee ' Start the updating thread and pass it the UpdatePeriod. Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod) End Sub Public Overridable Sub StopMarquee() _ Implements IMarqueeWidget.StopMarquee ' Stop the updating thread. Me.backgroundWorker1.CancelAsync() End Sub <Category("Marquee"), Browsable(True)> _ Public Property UpdatePeriod() As Integer _ Implements IMarqueeWidget.UpdatePeriod Get Return Me.updatePeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.updatePeriodValue = Value Else Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0") End If End Set End Property
Implementera egenskapsåtkomsterna. Du exponerar två egenskaper för klienter:
LightColor
ochDarkColor
. Attributen Category och Browsable tillämpas på dessa egenskaper, så egenskaperna visas i ett anpassat avsnitt i fönstret Egenskaper med namnet "Markeringsram".[Category("Marquee")] [Browsable(true)] public Color LightColor { get { return this.lightColorValue; } set { // The LightColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.lightColorValue.ToArgb() != value.ToArgb()) { this.lightColorValue = value; this.lightBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public Color DarkColor { get { return this.darkColorValue; } set { // The DarkColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.darkColorValue.ToArgb() != value.ToArgb()) { this.darkColorValue = value; this.darkBrush = new SolidBrush(value); } } }
<Category("Marquee"), Browsable(True)> _ Public Property LightColor() As Color Get Return Me.lightColorValue End Get Set(ByVal Value As Color) ' The LightColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then Me.lightColorValue = Value Me.lightBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property DarkColor() As Color Get Return Me.darkColorValue End Get Set(ByVal Value As Color) ' The DarkColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then Me.darkColorValue = Value Me.darkBrush = New SolidBrush(Value) End If End Set End Property
Implementera hanterare för BackgroundWorker komponentens DoWork- och ProgressChanged-händelser.
Händelsehanteraren DoWork väntar det antal millisekunder som specificeras av
UpdatePeriod
och genererar sedan händelsen ProgressChanged tills din kod stoppar animeringen genom att anropa CancelAsync.Händelsehanteraren ProgressChanged växlar texten mellan ljus och mörk för att ge intrycket av att blinka.
// This method is called in the worker thread's context, // so it must not make any calls into the MarqueeText control. // Instead, it communicates to the control using the // ProgressChanged event. // // The only work done in this event handler is // to sleep for the number of milliseconds specified // by UpdatePeriod, then raise the ProgressChanged event. private void backgroundWorker1_DoWork( object sender, System.ComponentModel.DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; // This event handler will run until the client cancels // the background task by calling CancelAsync. while (!worker.CancellationPending) { // The Argument property of the DoWorkEventArgs // object holds the value of UpdatePeriod, which // was passed as the argument to the RunWorkerAsync // method. Thread.Sleep((int)e.Argument); // The DoWork eventhandler does not actually report // progress; the ReportProgress event is used to // periodically alert the control to update its state. worker.ReportProgress(0); } } // The ProgressChanged event is raised by the DoWork method. // This event handler does work that is internal to the // control. In this case, the text is toggled between its // light and dark state, and the control is told to // repaint itself. private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) { this.isLit = !this.isLit; this.Refresh(); }
' This method is called in the worker thread's context, ' so it must not make any calls into the MarqueeText control. ' Instead, it communicates to the control using the ' ProgressChanged event. ' ' The only work done in this event handler is ' to sleep for the number of milliseconds specified ' by UpdatePeriod, then raise the ProgressChanged event. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles backgroundWorker1.DoWork Dim worker As BackgroundWorker = CType(sender, BackgroundWorker) ' This event handler will run until the client cancels ' the background task by calling CancelAsync. While Not worker.CancellationPending ' The Argument property of the DoWorkEventArgs ' object holds the value of UpdatePeriod, which ' was passed as the argument to the RunWorkerAsync ' method. Thread.Sleep(Fix(e.Argument)) ' The DoWork eventhandler does not actually report ' progress; the ReportProgress event is used to ' periodically alert the control to update its state. worker.ReportProgress(0) End While End Sub ' The ProgressChanged event is raised by the DoWork method. ' This event handler does work that is internal to the ' control. In this case, the text is toggled between its ' light and dark state, and the control is told to ' repaint itself. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.isLit = Not Me.isLit Me.Refresh() End Sub
Åsidosätt metoden OnPaint för att aktivera animeringen.
protected override void OnPaint(PaintEventArgs e) { // The text is painted in the light or dark color, // depending on the current value of isLit. this.ForeColor = this.isLit ? this.lightColorValue : this.darkColorValue; base.OnPaint(e); }
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) ' The text is painted in the light or dark color, ' depending on the current value of isLit. Me.ForeColor = IIf(Me.isLit, Me.lightColorValue, Me.darkColorValue) MyBase.OnPaint(e) End Sub
Tryck på F6 för att skapa lösningen.
Skapa barnkontrollen MarqueeBorder
Den MarqueeBorder
kontrollen är något mer avancerad än den MarqueeText
kontrollen. Den har fler egenskaper och animeringen i metoden OnPaint är mer involverad. I princip liknar det MarqueeText
-kontrollen.
Eftersom MarqueeBorder
-kontrollen kan ha underordnade kontroller måste den vara medveten om Layout-händelser.
Så här skapar du MarkisBorder-kontrollen
Lägg till ett nytt anpassat-objekt i
MarqueeControlLibrary
-projektet. Ge den nya källfilen ett basnamn för "MarqueeBorder".Dra en komponent BackgroundWorker från Toolbox- till din
MarqueeBorder
-kontroll. Med den här komponenten kanMarqueeBorder
-kontrollen uppdateras asynkront.I fönstret Egenskaper ställer du in BackgroundWorker komponentens egenskaperna
WorkerReportsProgress
och WorkerSupportsCancellation till sant. Med de här inställningarna kan BackgroundWorker komponenten regelbundet generera händelsen ProgressChanged och avbryta asynkrona uppdateringar. Mer information finns i BackgroundWorker Component.I fönstret Egenskaper väljer du knappen Händelser. Koppla hanterare för händelserna DoWork och ProgressChanged.
Öppna
MarqueeBorder
-källfilen i Code Editor. Importera följande namnområden överst i filen:using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Drawing; using System.Drawing.Design; using System.Threading; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Drawing Imports System.Drawing.Design Imports System.Threading Imports System.Windows.Forms Imports System.Windows.Forms.Design
Ändra deklarationen för
MarqueeBorder
till att ärva från Panel och implementeraIMarqueeWidget
-gränssnittet.[Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] public partial class MarqueeBorder : Panel, IMarqueeWidget {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeBorder Inherits Panel Implements IMarqueeWidget
Deklarera två uppräkningar för att hantera
MarqueeBorder
kontrolltillstånd:MarqueeSpinDirection
, som bestämmer i vilken riktning lamporna "snurrar" runt kantlinjen ochMarqueeLightShape
, som bestämmer ljusens form (kvadrat eller cirkel). Placera dessa deklarationer före klassdeklarationen förMarqueeBorder
.// 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
Deklarera de instansvariabler som motsvarar de exponerade egenskaperna och initiera dem i konstruktorn.
public static int MaxLightSize = 10; // These fields back the public properties. private int updatePeriodValue = 50; private int lightSizeValue = 5; private int lightPeriodValue = 3; private int lightSpacingValue = 1; private Color lightColorValue; private Color darkColorValue; private MarqueeSpinDirection spinDirectionValue = MarqueeSpinDirection.CW; private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square; // These brushes are used to paint the light and dark // colors of the marquee lights. private Brush lightBrush; private Brush darkBrush; // This field tracks the progress of the "first" light as it // "travels" around the marquee border. private int currentOffset = 0; // This component updates the control asynchronously. private System.ComponentModel.BackgroundWorker backgroundWorker1; public MarqueeBorder() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // Initialize light and dark colors // to the control's default values. this.lightColorValue = this.ForeColor; this.darkColorValue = this.BackColor; this.lightBrush = new SolidBrush(this.lightColorValue); this.darkBrush = new SolidBrush(this.darkColorValue); // The MarqueeBorder control manages its own padding, // because it requires that any contained controls do // not overlap any of the marquee lights. int pad = 2 * (this.lightSizeValue + this.lightSpacingValue); this.Padding = new Padding(pad, pad, pad, pad); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); }
Public Shared MaxLightSize As Integer = 10 ' These fields back the public properties. Private updatePeriodValue As Integer = 50 Private lightSizeValue As Integer = 5 Private lightPeriodValue As Integer = 3 Private lightSpacingValue As Integer = 1 Private lightColorValue As Color Private darkColorValue As Color Private spinDirectionValue As MarqueeSpinDirection = MarqueeSpinDirection.CW Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square ' These brushes are used to paint the light and dark ' colors of the marquee lights. Private lightBrush As Brush Private darkBrush As Brush ' This field tracks the progress of the "first" light as it ' "travels" around the marquee border. Private currentOffset As Integer = 0 ' This component updates the control asynchronously. Private WithEvents backgroundWorker1 As System.ComponentModel.BackgroundWorker Public Sub New() ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Initialize light and dark colors ' to the control's default values. Me.lightColorValue = Me.ForeColor Me.darkColorValue = Me.BackColor Me.lightBrush = New SolidBrush(Me.lightColorValue) Me.darkBrush = New SolidBrush(Me.darkColorValue) ' The MarqueeBorder control manages its own padding, ' because it requires that any contained controls do ' not overlap any of the marquee lights. Dim pad As Integer = 2 * (Me.lightSizeValue + Me.lightSpacingValue) Me.Padding = New Padding(pad, pad, pad, pad) SetStyle(ControlStyles.OptimizedDoubleBuffer, True) End Sub
Implementera
IMarqueeWidget
-gränssnittet.Metoderna
StartMarquee
ochStopMarquee
anropar BackgroundWorker komponentens RunWorkerAsync- och CancelAsync metoder för att starta och stoppa animeringen.Eftersom kontrollen
MarqueeBorder
kan innehålla underordnade kontroller, räknarStartMarquee
-metoden upp alla underordnade kontroller och anroparStartMarquee
för de som implementerarIMarqueeWidget
. MetodenStopMarquee
har en liknande implementering.public virtual void StartMarquee() { // The MarqueeBorder control may contain any number of // controls that implement IMarqueeWidget, so find // each IMarqueeWidget child and call its StartMarquee // method. foreach (Control cntrl in this.Controls) { if (cntrl is IMarqueeWidget) { IMarqueeWidget widget = cntrl as IMarqueeWidget; widget.StartMarquee(); } } // Start the updating thread and pass it the UpdatePeriod. this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod); } public virtual void StopMarquee() { // The MarqueeBorder control may contain any number of // controls that implement IMarqueeWidget, so find // each IMarqueeWidget child and call its StopMarquee // method. foreach (Control cntrl in this.Controls) { if (cntrl is IMarqueeWidget) { IMarqueeWidget widget = cntrl as IMarqueeWidget; widget.StopMarquee(); } } // Stop the updating thread. this.backgroundWorker1.CancelAsync(); } [Category("Marquee")] [Browsable(true)] public virtual int UpdatePeriod { get { return this.updatePeriodValue; } set { if (value > 0) { this.updatePeriodValue = value; } else { throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0"); } } }
Public Overridable Sub StartMarquee() _ Implements IMarqueeWidget.StartMarquee ' The MarqueeBorder control may contain any number of ' controls that implement IMarqueeWidget, so find ' each IMarqueeWidget child and call its StartMarquee ' method. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) widget.StartMarquee() End If Next cntrl ' Start the updating thread and pass it the UpdatePeriod. Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod) End Sub Public Overridable Sub StopMarquee() _ Implements IMarqueeWidget.StopMarquee ' The MarqueeBorder control may contain any number of ' controls that implement IMarqueeWidget, so find ' each IMarqueeWidget child and call its StopMarquee ' method. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) widget.StopMarquee() End If Next cntrl ' Stop the updating thread. Me.backgroundWorker1.CancelAsync() End Sub <Category("Marquee"), Browsable(True)> _ Public Overridable Property UpdatePeriod() As Integer _ Implements IMarqueeWidget.UpdatePeriod Get Return Me.updatePeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.updatePeriodValue = Value Else Throw New ArgumentOutOfRangeException("UpdatePeriod", _ "must be > 0") End If End Set End Property
Implementera egenskapsåtkomsterna. Kontrollen
MarqueeBorder
har flera egenskaper för att kontrollera dess utseende.[Category("Marquee")] [Browsable(true)] public int LightSize { get { return this.lightSizeValue; } set { if (value > 0 && value <= MaxLightSize) { this.lightSizeValue = value; this.DockPadding.All = 2 * value; } else { throw new ArgumentOutOfRangeException("LightSize", "must be > 0 and < MaxLightSize"); } } } [Category("Marquee")] [Browsable(true)] public int LightPeriod { get { return this.lightPeriodValue; } set { if (value > 0) { this.lightPeriodValue = value; } else { throw new ArgumentOutOfRangeException("LightPeriod", "must be > 0 "); } } } [Category("Marquee")] [Browsable(true)] public Color LightColor { get { return this.lightColorValue; } set { // The LightColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.lightColorValue.ToArgb() != value.ToArgb()) { this.lightColorValue = value; this.lightBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public Color DarkColor { get { return this.darkColorValue; } set { // The DarkColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.darkColorValue.ToArgb() != value.ToArgb()) { this.darkColorValue = value; this.darkBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public int LightSpacing { get { return this.lightSpacingValue; } set { if (value >= 0) { this.lightSpacingValue = value; } else { throw new ArgumentOutOfRangeException("LightSpacing", "must be >= 0"); } } } [Category("Marquee")] [Browsable(true)] [EditorAttribute(typeof(LightShapeEditor), typeof(System.Drawing.Design.UITypeEditor))] public MarqueeLightShape LightShape { get { return this.lightShapeValue; } set { this.lightShapeValue = value; } } [Category("Marquee")] [Browsable(true)] public MarqueeSpinDirection SpinDirection { get { return this.spinDirectionValue; } set { this.spinDirectionValue = value; } }
<Category("Marquee"), Browsable(True)> _ Public Property LightSize() As Integer Get Return Me.lightSizeValue End Get Set(ByVal Value As Integer) If Value > 0 AndAlso Value <= MaxLightSize Then Me.lightSizeValue = Value Me.DockPadding.All = 2 * Value Else Throw New ArgumentOutOfRangeException("LightSize", _ "must be > 0 and < MaxLightSize") End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightPeriod() As Integer Get Return Me.lightPeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.lightPeriodValue = Value Else Throw New ArgumentOutOfRangeException("LightPeriod", _ "must be > 0 ") End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightColor() As Color Get Return Me.lightColorValue End Get Set(ByVal Value As Color) ' The LightColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then Me.lightColorValue = Value Me.lightBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property DarkColor() As Color Get Return Me.darkColorValue End Get Set(ByVal Value As Color) ' The DarkColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then Me.darkColorValue = Value Me.darkBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightSpacing() As Integer Get Return Me.lightSpacingValue End Get Set(ByVal Value As Integer) If Value >= 0 Then Me.lightSpacingValue = Value Else Throw New ArgumentOutOfRangeException("LightSpacing", _ "must be >= 0") End If End Set End Property <Category("Marquee"), Browsable(True), _ EditorAttribute(GetType(LightShapeEditor), _ GetType(System.Drawing.Design.UITypeEditor))> _ Public Property LightShape() As MarqueeLightShape Get Return Me.lightShapeValue End Get Set(ByVal Value As MarqueeLightShape) Me.lightShapeValue = Value End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property SpinDirection() As MarqueeSpinDirection Get Return Me.spinDirectionValue End Get Set(ByVal Value As MarqueeSpinDirection) Me.spinDirectionValue = Value End Set End Property
Implementera hanterare för BackgroundWorker komponentens DoWork- och ProgressChanged-händelser.
Händelsehanteraren DoWork vilar under det antal millisekunder som anges av
UpdatePeriod
, och utlöser sedan händelsen ProgressChanged, tills din kod stoppar animeringen genom att anropa CancelAsync.Händelsehanteraren för ProgressChanged ökar positionen för "basljuset", från vilket de andra lampornas ljus/mörka tillstånd bestäms, och anropar metoden Refresh för att få kontrollen att måla om sig själv.
// This method is called in the worker thread's context, // so it must not make any calls into the MarqueeBorder // control. Instead, it communicates to the control using // the ProgressChanged event. // // The only work done in this event handler is // to sleep for the number of milliseconds specified // by UpdatePeriod, then raise the ProgressChanged event. private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; // This event handler will run until the client cancels // the background task by calling CancelAsync. while (!worker.CancellationPending) { // The Argument property of the DoWorkEventArgs // object holds the value of UpdatePeriod, which // was passed as the argument to the RunWorkerAsync // method. Thread.Sleep((int)e.Argument); // The DoWork eventhandler does not actually report // progress; the ReportProgress event is used to // periodically alert the control to update its state. worker.ReportProgress(0); } } // The ProgressChanged event is raised by the DoWork method. // This event handler does work that is internal to the // control. In this case, the currentOffset is incremented, // and the control is told to repaint itself. private void backgroundWorker1_ProgressChanged( object sender, System.ComponentModel.ProgressChangedEventArgs e) { this.currentOffset++; this.Refresh(); }
' This method is called in the worker thread's context, ' so it must not make any calls into the MarqueeBorder ' control. Instead, it communicates to the control using ' the ProgressChanged event. ' ' The only work done in this event handler is ' to sleep for the number of milliseconds specified ' by UpdatePeriod, then raise the ProgressChanged event. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles backgroundWorker1.DoWork Dim worker As BackgroundWorker = CType(sender, BackgroundWorker) ' This event handler will run until the client cancels ' the background task by calling CancelAsync. While Not worker.CancellationPending ' The Argument property of the DoWorkEventArgs ' object holds the value of UpdatePeriod, which ' was passed as the argument to the RunWorkerAsync ' method. Thread.Sleep(Fix(e.Argument)) ' The DoWork eventhandler does not actually report ' progress; the ReportProgress event is used to ' periodically alert the control to update its state. worker.ReportProgress(0) End While End Sub ' The ProgressChanged event is raised by the DoWork method. ' This event handler does work that is internal to the ' control. In this case, the currentOffset is incremented, ' and the control is told to repaint itself. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.currentOffset += 1 Me.Refresh() End Sub
Implementera hjälpmetoderna,
IsLit
ochDrawLight
.Metoden
IsLit
bestämmer färgen på ett ljus vid en viss position. Ljus som är "tända" ritas i den färg som ges av egenskapenLightColor
, och de som är "mörka" ritas i den färg som ges av egenskapenDarkColor
.Metoden
DrawLight
ritar ett ljus med lämplig färg, form och position.// This method determines if the marquee light at lightIndex // should be lit. The currentOffset field specifies where // the "first" light is located, and the "position" of the // light given by lightIndex is computed relative to this // offset. If this position modulo lightPeriodValue is zero, // the light is considered to be on, and it will be painted // with the control's lightBrush. protected virtual bool IsLit(int lightIndex) { int directionFactor = (this.spinDirectionValue == MarqueeSpinDirection.CW ? -1 : 1); return ( (lightIndex + directionFactor * this.currentOffset) % this.lightPeriodValue == 0 ); } protected virtual void DrawLight( Graphics g, Brush brush, int xPos, int yPos) { switch (this.lightShapeValue) { case MarqueeLightShape.Square: { g.FillRectangle(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue); break; } case MarqueeLightShape.Circle: { g.FillEllipse(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue); break; } default: { Trace.Assert(false, "Unknown value for light shape."); break; } } }
' This method determines if the marquee light at lightIndex ' should be lit. The currentOffset field specifies where ' the "first" light is located, and the "position" of the ' light given by lightIndex is computed relative to this ' offset. If this position modulo lightPeriodValue is zero, ' the light is considered to be on, and it will be painted ' with the control's lightBrush. Protected Overridable Function IsLit(ByVal lightIndex As Integer) As Boolean Dim directionFactor As Integer = _ IIf(Me.spinDirectionValue = MarqueeSpinDirection.CW, -1, 1) Return (lightIndex + directionFactor * Me.currentOffset) Mod Me.lightPeriodValue = 0 End Function Protected Overridable Sub DrawLight( _ ByVal g As Graphics, _ ByVal brush As Brush, _ ByVal xPos As Integer, _ ByVal yPos As Integer) Select Case Me.lightShapeValue Case MarqueeLightShape.Square g.FillRectangle( _ brush, _ xPos, _ yPos, _ Me.lightSizeValue, _ Me.lightSizeValue) Exit Select Case MarqueeLightShape.Circle g.FillEllipse( _ brush, _ xPos, _ yPos, _ Me.lightSizeValue, _ Me.lightSizeValue) Exit Select Case Else Trace.Assert(False, "Unknown value for light shape.") Exit Select End Select End Sub
Åsidosätt metoderna OnLayout och OnPaint.
Metoden OnPaint ritar lamporna längs kanterna på
MarqueeBorder
kontrollen.Eftersom OnPaint-metoden är beroende av dimensionerna för
MarqueeBorder
-kontrollen måste du anropa den när layouten ändras. För att uppnå detta åsidosätter du OnLayout och anropar Refresh.protected override void OnLayout(LayoutEventArgs levent) { base.OnLayout(levent); // Repaint when the layout has changed. this.Refresh(); } // This method paints the lights around the border of the // control. It paints the top row first, followed by the // right side, the bottom row, and the left side. The color // of each light is determined by the IsLit method and // depends on the light's position relative to the value // of currentOffset. protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.Clear(this.BackColor); base.OnPaint(e); // If the control is large enough, draw some lights. if (this.Width > MaxLightSize && this.Height > MaxLightSize) { // The position of the next light will be incremented // by this value, which is equal to the sum of the // light size and the space between two lights. int increment = this.lightSizeValue + this.lightSpacingValue; // Compute the number of lights to be drawn along the // horizontal edges of the control. int horizontalLights = (this.Width - increment) / increment; // Compute the number of lights to be drawn along the // vertical edges of the control. int verticalLights = (this.Height - increment) / increment; // These local variables will be used to position and // paint each light. int xPos = 0; int yPos = 0; int lightCounter = 0; Brush brush; // Draw the top row of lights. for (int i = 0; i < horizontalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); xPos += increment; lightCounter++; } // Draw the lights flush with the right edge of the control. xPos = this.Width - this.lightSizeValue; // Draw the right column of lights. for (int i = 0; i < verticalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); yPos += increment; lightCounter++; } // Draw the lights flush with the bottom edge of the control. yPos = this.Height - this.lightSizeValue; // Draw the bottom row of lights. for (int i = 0; i < horizontalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); xPos -= increment; lightCounter++; } // Draw the lights flush with the left edge of the control. xPos = 0; // Draw the left column of lights. for (int i = 0; i < verticalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); yPos -= increment; lightCounter++; } } }
Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs) MyBase.OnLayout(levent) ' Repaint when the layout has changed. Me.Refresh() End Sub ' This method paints the lights around the border of the ' control. It paints the top row first, followed by the ' right side, the bottom row, and the left side. The color ' of each light is determined by the IsLit method and ' depends on the light's position relative to the value ' of currentOffset. Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim g As Graphics = e.Graphics g.Clear(Me.BackColor) MyBase.OnPaint(e) ' If the control is large enough, draw some lights. If Me.Width > MaxLightSize AndAlso Me.Height > MaxLightSize Then ' The position of the next light will be incremented ' by this value, which is equal to the sum of the ' light size and the space between two lights. Dim increment As Integer = _ Me.lightSizeValue + Me.lightSpacingValue ' Compute the number of lights to be drawn along the ' horizontal edges of the control. Dim horizontalLights As Integer = _ (Me.Width - increment) / increment ' Compute the number of lights to be drawn along the ' vertical edges of the control. Dim verticalLights As Integer = _ (Me.Height - increment) / increment ' These local variables will be used to position and ' paint each light. Dim xPos As Integer = 0 Dim yPos As Integer = 0 Dim lightCounter As Integer = 0 Dim brush As Brush ' Draw the top row of lights. Dim i As Integer For i = 0 To horizontalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) xPos += increment lightCounter += 1 Next i ' Draw the lights flush with the right edge of the control. xPos = Me.Width - Me.lightSizeValue ' Draw the right column of lights. 'Dim i As Integer For i = 0 To verticalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) yPos += increment lightCounter += 1 Next i ' Draw the lights flush with the bottom edge of the control. yPos = Me.Height - Me.lightSizeValue ' Draw the bottom row of lights. 'Dim i As Integer For i = 0 To horizontalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) xPos -= increment lightCounter += 1 Next i ' Draw the lights flush with the left edge of the control. xPos = 0 ' Draw the left column of lights. 'Dim i As Integer For i = 0 To verticalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) yPos -= increment lightCounter += 1 Next i End If End Sub
Skapa en anpassad designer för skugg- och filteregenskaper
Klassen MarqueeControlRootDesigner
tillhandahåller implementeringen för rotdesignern. Förutom den här designern, som fungerar på MarqueeControl
, behöver du en anpassad designer som är specifikt associerad med MarqueeBorder
-kontrollen. Den här designern tillhandahåller ett anpassat beteende som är lämpligt i kontexten för den anpassade rotdesignern.
Mer specifikt kommer MarqueeBorderDesigner
att "skugga" och filtrera vissa egenskaper på MarqueeBorder
kontroll, vilket ändrar deras interaktion med designmiljön.
Att fånga upp anrop till en komponents egenskapsåtkomstor kallas "skuggning". Det gör att en designer kan spåra värdet som angetts av användaren och eventuellt skicka det värdet till komponenten som utformas.
I det här exemplet skuggas egenskaperna Visible och Enabled av MarqueeBorderDesigner
, vilket hindrar användaren från att göra MarqueeBorder
kontrollen osynlig eller inaktiverad under designtiden.
Designers kan också lägga till och ta bort egenskaper. I det här exemplet tas egenskapen Padding bort vid designtillfället, eftersom MarqueeBorder
-kontrollen programmatiskt anger utfyllnad baserat på storleken på de lampor som anges av egenskapen LightSize
.
Basklassen för MarqueeBorderDesigner
är ComponentDesigner, som har metoder som kan ändra attribut, egenskaper och händelser som exponeras av en kontroll vid designtillfället:
När du ändrar det offentliga gränssnittet för en komponent med hjälp av dessa metoder följer du dessa regler:
Lägg endast till eller ta bort objekt i
PreFilter
metoderÄndra endast befintliga objekt i
PostFilter
metoderAnropa alltid basimplementeringen först i
PreFilter
metoderAnropa alltid basimplementeringen sist i de
PostFilter
metoderna
Genom att följa dessa regler ser du till att alla designers i designmiljön har en konsekvent vy över alla komponenter som utformas.
Klassen ComponentDesigner innehåller en ordlista för att hantera värdena för skuggade egenskaper, vilket gör att du slipper skapa specifika instansvariabler.
Skapa en anpassad designer för att skugga och filtrera egenskaper
Högerklicka på mappen Design och lägg till en ny klass. Ge källfilen ett basnamn för MarqueeBorderDesigner.
Öppna källfilen MarqueeBorderDesigner i Code Editor. Importera följande namnområden överst i filen:
using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Windows.Forms Imports System.Windows.Forms.Design
Ändra deklarationen för
MarqueeBorderDesigner
så att den ärver från ParentControlDesigner.Eftersom kontrollen
MarqueeBorder
kan innehålla barnkontroller ärverMarqueeBorderDesigner
från ParentControlDesigner, som hanterar interaktionen mellan förälder och barn.namespace MarqueeControlLibrary.Design { public class MarqueeBorderDesigner : ParentControlDesigner {
Namespace MarqueeControlLibrary.Design Public Class MarqueeBorderDesigner Inherits ParentControlDesigner
Åsidosätt basimplementeringen av PreFilterProperties.
protected override void PreFilterProperties(IDictionary properties) { base.PreFilterProperties(properties); if (properties.Contains("Padding")) { properties.Remove("Padding"); } properties["Visible"] = TypeDescriptor.CreateProperty( typeof(MarqueeBorderDesigner), (PropertyDescriptor)properties["Visible"], new Attribute[0]); properties["Enabled"] = TypeDescriptor.CreateProperty( typeof(MarqueeBorderDesigner), (PropertyDescriptor)properties["Enabled"], new Attribute[0]); }
Protected Overrides Sub PreFilterProperties( _ ByVal properties As IDictionary) MyBase.PreFilterProperties(properties) If properties.Contains("Padding") Then properties.Remove("Padding") End If properties("Visible") = _ TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _ CType(properties("Visible"), PropertyDescriptor), _ New Attribute(-1) {}) properties("Enabled") = _ TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _ CType(properties("Enabled"), _ PropertyDescriptor), _ New Attribute(-1) {}) End Sub
Implementera egenskaperna Enabled och Visible. Dessa implementeringar skuggar kontrollens egenskaper.
public bool Visible { get { return (bool)ShadowProperties["Visible"]; } set { this.ShadowProperties["Visible"] = value; } } public bool Enabled { get { return (bool)ShadowProperties["Enabled"]; } set { this.ShadowProperties["Enabled"] = value; } }
Public Property Visible() As Boolean Get Return CBool(ShadowProperties("Visible")) End Get Set(ByVal Value As Boolean) Me.ShadowProperties("Visible") = Value End Set End Property Public Property Enabled() As Boolean Get Return CBool(ShadowProperties("Enabled")) End Get Set(ByVal Value As Boolean) Me.ShadowProperties("Enabled") = Value End Set End Property
Hantera komponentändringar
Klassen MarqueeControlRootDesigner
ger en anpassad designtidsupplevelse för dina MarqueeControl
-instanser. De flesta designtidsfunktionerna ärvs från klassen DocumentDesigner. Koden implementerar två specifika anpassningar: hantering av komponentändringar och tillägg av designerverb.
När användarna utformar sina MarqueeControl
instanser spårar rotdesignern ändringar i MarqueeControl
och dess underordnade kontroller. Designmiljön erbjuder en praktisk tjänst, IComponentChangeService, för att spåra ändringar i komponenttillståndet.
Du skaffar en referens till den här tjänsten genom att fråga systemmiljön med metoden GetService. Om frågan lyckas kan din designer koppla en hanterare för händelsen ComponentChanged och utföra de uppgifter som krävs för att upprätthålla ett konsekvent tillstånd under designstadiet.
När det gäller klassen MarqueeControlRootDesigner
anropar du metoden Refresh på varje IMarqueeWidget
objekt som finns i MarqueeControl
. Detta gör att IMarqueeWidget
-objektet målas om på rätt sätt när egenskaper som dess överordnade Size ändras.
Hantera komponentändringar
Öppna
MarqueeControlRootDesigner
-källfilen i Code Editor och åsidosätt metoden Initialize. Anropa basimplementeringen av Initialize och fråga efter IComponentChangeService.base.Initialize(component); IComponentChangeService cs = GetService(typeof(IComponentChangeService)) as IComponentChangeService; if (cs != null) { cs.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged); }
MyBase.Initialize(component) Dim cs As IComponentChangeService = _ CType(GetService(GetType(IComponentChangeService)), _ IComponentChangeService) If (cs IsNot Nothing) Then AddHandler cs.ComponentChanged, AddressOf OnComponentChanged End If
Implementera OnComponentChanged händelsehanterare. Testa den sändande komponentens typ och om det är en
IMarqueeWidget
anropar du dess Refresh-metod.private void OnComponentChanged( object sender, ComponentChangedEventArgs e) { if (e.Component is IMarqueeWidget) { this.Control.Refresh(); } }
Private Sub OnComponentChanged( _ ByVal sender As Object, _ ByVal e As ComponentChangedEventArgs) If TypeOf e.Component Is IMarqueeWidget Then Me.Control.Refresh() End If End Sub
Lägg till designerverb i din anpassade designer
Ett designerverb är ett menykommando som är länkat till en händelsehanterare. Designerverb läggs till i en komponents snabbmeny vid designtillfället. Mer information finns i DesignerVerb.
Du lägger till två designerverb till dina designers: Kör test och Stoppa test. Med hjälp av dessa verb kan du visa körningsbeteendet för MarqueeControl
under designfasen. Dessa verb läggs till i MarqueeControlRootDesigner
.
När Kör test anropas anropar verbhändelsehanteraren metoden StartMarquee
på MarqueeControl
. När Stop Test anropas anropar verbhändelsehanteraren metoden StopMarquee
på MarqueeControl
. Implementeringen av metoderna StartMarquee
och StopMarquee
anropar dessa metoder på inneslutna kontroller som implementerar IMarqueeWidget
, vilket gör att alla inneslutna IMarqueeWidget
-kontroller också kommer att delta i testet.
Så här lägger du till designerverb i dina anpassade designerverktyg
I klassen
MarqueeControlRootDesigner
lägger du till händelsehanterare med namnetOnVerbRunTest
ochOnVerbStopTest
.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
Anslut dessa händelsehanterare till motsvarande designerverb.
MarqueeControlRootDesigner
ärver en DesignerVerbCollection från basklassen. Du skapar två nya DesignerVerb objekt och lägger till dem i den här samlingen i metoden Initialize.this.Verbs.Add( new DesignerVerb("Run Test", new EventHandler(OnVerbRunTest)) ); this.Verbs.Add( new DesignerVerb("Stop Test", new EventHandler(OnVerbStopTest)) );
Me.Verbs.Add(New DesignerVerb("Run Test", _ New EventHandler(AddressOf OnVerbRunTest))) Me.Verbs.Add(New DesignerVerb("Stop Test", _ New EventHandler(AddressOf OnVerbStopTest)))
Skapa en anpassad användargränssnittsredigerare
När du skapar en anpassad designtidsupplevelse för användare är det ofta önskvärt att skapa en anpassad interaktion med fönstret Egenskaper. Du kan göra detta genom att skapa en UITypeEditor.
Kontrollen MarqueeBorder
exponerar flera egenskaper i fönstret Egenskaper. Två av dessa egenskaper, MarqueeSpinDirection
och MarqueeLightShape
representeras av uppräkningar. För att illustrera användningen av en redigerare av användargränssnittstyp har egenskapen MarqueeLightShape
en associerad UITypeEditor-klass.
Så här skapar du en redigerare av anpassad användargränssnittstyp
Öppna
MarqueeBorder
-källfilen i Code Editor.I definitionen av klassen
MarqueeBorder
deklarerar du en klass med namnetLightShapeEditor
som härleds från UITypeEditor.// This class demonstrates the use of a custom UITypeEditor. // It allows the MarqueeBorder control's LightShape property // to be changed at design time using a customized UI element // that is invoked by the Properties window. The UI is provided // by the LightShapeSelectionControl class. internal class LightShapeEditor : UITypeEditor {
' This class demonstrates the use of a custom UITypeEditor. ' It allows the MarqueeBorder control's LightShape property ' to be changed at design time using a customized UI element ' that is invoked by the Properties window. The UI is provided ' by the LightShapeSelectionControl class. Friend Class LightShapeEditor Inherits UITypeEditor
Deklarera en IWindowsFormsEditorService instansvariabel med namnet
editorService
.private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
Åsidosätt metoden GetEditStyle. Den här implementeringen returnerar DropDown, som talar om för designmiljön hur du visar
LightShapeEditor
.public override UITypeEditorEditStyle GetEditStyle( System.ComponentModel.ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; }
Public Overrides Function GetEditStyle( _ ByVal context As System.ComponentModel.ITypeDescriptorContext) _ As UITypeEditorEditStyle Return UITypeEditorEditStyle.DropDown End Function
Åsidosätt metoden EditValue. Den här implementeringen frågar designmiljön efter ett IWindowsFormsEditorService objekt. Om det lyckas skapar den en
LightShapeSelectionControl
. Metoden DropDownControl anropas för att startaLightShapeEditor
. Returvärdet från det här anropet returneras till designmiljön.public override object EditValue( ITypeDescriptorContext context, IServiceProvider provider, object value) { if (provider != null) { editorService = provider.GetService( typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService; } if (editorService != null) { LightShapeSelectionControl selectionControl = new LightShapeSelectionControl( (MarqueeLightShape)value, editorService); editorService.DropDownControl(selectionControl); value = selectionControl.LightShape; } return value; }
Public Overrides Function EditValue( _ ByVal context As ITypeDescriptorContext, _ ByVal provider As IServiceProvider, _ ByVal value As Object) As Object If (provider IsNot Nothing) Then editorService = _ CType(provider.GetService(GetType(IWindowsFormsEditorService)), _ IWindowsFormsEditorService) End If If (editorService IsNot Nothing) Then Dim selectionControl As _ New LightShapeSelectionControl( _ CType(value, MarqueeLightShape), _ editorService) editorService.DropDownControl(selectionControl) value = selectionControl.LightShape End If Return value End Function
Skapa en vykontroll för din anpassade UITypeEditor
Egenskapen MarqueeLightShape
stöder två typer av ljusformer: Square
och Circle
. Du skapar en anpassad kontroll som endast används för att grafiskt visa dessa värden i fönstret Egenskaper. Den här anpassade kontrollen används av din UITypeEditor för att interagera med fönstret Egenskaper.
Så här skapar du en vykontroll för din anpassade redigerare av användargränssnittstyp
Lägg till ett nytt UserControl objekt i
MarqueeControlLibrary
-projektet. Ge den nya källfilen ett basnamn för LightShapeSelectionControl.Dra två Panel-kontroller från -verktygslådan till
LightShapeSelectionControl
. Ge dem namnetsquarePanel
ochcirclePanel
. Ordna dem sida vid sida. Ange egenskapen Size för båda Panel kontrollerna till (60, 60). Ange egenskapen Location för kontrollensquarePanel
till (8, 10). Ange egenskapen Location för kontrollencirclePanel
till (80, 10). Ange slutligen egenskapen Size förLightShapeSelectionControl
till (150, 80).Öppna
LightShapeSelectionControl
-källfilen i Code Editor. Importera namnrymden System.Windows.Forms.Design överst i den filen.Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
Implementera Click händelsehanterare för kontrollerna
squarePanel
ochcirclePanel
. Dessa metoder anropar CloseDropDown för att avsluta den anpassade UITypeEditor redigeringssessionen.private void squarePanel_Click(object sender, EventArgs e) { this.lightShapeValue = MarqueeLightShape.Square; this.Invalidate( false ); this.editorService.CloseDropDown(); } private void circlePanel_Click(object sender, EventArgs e) { this.lightShapeValue = MarqueeLightShape.Circle; this.Invalidate( false ); this.editorService.CloseDropDown(); }
Private Sub squarePanel_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Me.lightShapeValue = MarqueeLightShape.Square Me.Invalidate(False) Me.editorService.CloseDropDown() End Sub Private Sub circlePanel_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Me.lightShapeValue = MarqueeLightShape.Circle Me.Invalidate(False) Me.editorService.CloseDropDown() End Sub
Deklarera en IWindowsFormsEditorService instansvariabel med namnet
editorService
.Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
Deklarera en
MarqueeLightShape
instansvariabel med namnetlightShapeValue
.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
I
LightShapeSelectionControl
konstruktorn kopplar du Click händelsehanterare tillsquarePanel
- ochcirclePanel
-kontrollernas Click-händelser. Definiera också en konstruktoröverladdning som tilldelarMarqueeLightShape
-värdet från designmiljön till fältetlightShapeValue
.// 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
I metoden Dispose kopplar du bort Click händelsehanterare.
protected override void Dispose( bool disposing ) { if( disposing ) { // Be sure to unhook event handlers // to prevent "lapsed listener" leaks. this.squarePanel.Click -= new EventHandler(squarePanel_Click); this.circlePanel.Click -= new EventHandler(circlePanel_Click); if(components != null) { components.Dispose(); } } base.Dispose( disposing ); }
Protected Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then ' Be sure to unhook event handlers ' to prevent "lapsed listener" leaks. RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click If (components IsNot Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub
I Solution Explorerklickar du på knappen Visa alla filer. Öppna filen LightShapeSelectionControl.Designer.cs eller LightShapeSelectionControl.Designer.vb och ta bort standarddefinitionen för metoden Dispose.
Implementera egenskapen
LightShape
.// LightShape is the property for which this control provides // a custom user interface in the Properties window. public MarqueeLightShape LightShape { get { return this.lightShapeValue; } set { if( this.lightShapeValue != value ) { this.lightShapeValue = value; } } }
' LightShape is the property for which this control provides ' a custom user interface in the Properties window. Public Property LightShape() As MarqueeLightShape Get Return Me.lightShapeValue End Get Set(ByVal Value As MarqueeLightShape) If Me.lightShapeValue <> Value Then Me.lightShapeValue = Value End If End Set End Property
Åsidosätt metoden OnPaint. Den här implementeringen ritar en fylld fyrkant och cirkel. Det markerar också det markerade värdet genom att rita en kantlinje runt den ena eller den andra formen.
protected override void OnPaint(PaintEventArgs e) { base.OnPaint (e); using( Graphics gSquare = this.squarePanel.CreateGraphics(), gCircle = this.circlePanel.CreateGraphics() ) { // Draw a filled square in the client area of // the squarePanel control. gSquare.FillRectangle( Brushes.Red, 0, 0, this.squarePanel.Width, this.squarePanel.Height ); // If the Square option has been selected, draw a // border inside the squarePanel. if( this.lightShapeValue == MarqueeLightShape.Square ) { gSquare.DrawRectangle( Pens.Black, 0, 0, this.squarePanel.Width-1, this.squarePanel.Height-1); } // Draw a filled circle in the client area of // the circlePanel control. gCircle.Clear( this.circlePanel.BackColor ); gCircle.FillEllipse( Brushes.Blue, 0, 0, this.circlePanel.Width, this.circlePanel.Height ); // If the Circle option has been selected, draw a // border inside the circlePanel. if( this.lightShapeValue == MarqueeLightShape.Circle ) { gCircle.DrawRectangle( Pens.Black, 0, 0, this.circlePanel.Width-1, this.circlePanel.Height-1); } } }
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) MyBase.OnPaint(e) Dim gCircle As Graphics = Me.circlePanel.CreateGraphics() Try Dim gSquare As Graphics = Me.squarePanel.CreateGraphics() Try ' Draw a filled square in the client area of ' the squarePanel control. gSquare.FillRectangle( _ Brushes.Red, _ 0, _ 0, _ Me.squarePanel.Width, _ Me.squarePanel.Height) ' If the Square option has been selected, draw a ' border inside the squarePanel. If Me.lightShapeValue = MarqueeLightShape.Square Then gSquare.DrawRectangle( _ Pens.Black, _ 0, _ 0, _ Me.squarePanel.Width - 1, _ Me.squarePanel.Height - 1) End If ' Draw a filled circle in the client area of ' the circlePanel control. gCircle.Clear(Me.circlePanel.BackColor) gCircle.FillEllipse( _ Brushes.Blue, _ 0, _ 0, _ Me.circlePanel.Width, _ Me.circlePanel.Height) ' If the Circle option has been selected, draw a ' border inside the circlePanel. If Me.lightShapeValue = MarqueeLightShape.Circle Then gCircle.DrawRectangle( _ Pens.Black, _ 0, _ 0, _ Me.circlePanel.Width - 1, _ Me.circlePanel.Height - 1) End If Finally gSquare.Dispose() End Try Finally gCircle.Dispose() End Try End Sub
Testa din anpassade kontroll i designern
Nu kan du skapa MarqueeControlLibrary
projektet. Testa implementeringen genom att skapa en kontroll som ärver från klassen MarqueeControl
och använda den i ett formulär.
Så här skapar du en anpassad MarqueeControl-implementering
Öppna
DemoMarqueeControl
i Windows Forms Designer. Detta skapar en instans avDemoMarqueeControl
typ och visar den i en instans avMarqueeControlRootDesigner
typ.Öppna fliken MarqueeControlLibrary Components i Toolbox. Du ser kontrollerna
MarqueeBorder
ochMarqueeText
tillgängliga för markering.Dra en instans av
MarqueeBorder
-kontrollen tillDemoMarqueeControl
-designområdet. Docka den härMarqueeBorder
-kontrollen till föräldrakontrollen.Dra en instans av
MarqueeText
-kontrollen till designytanDemoMarqueeControl
.Skapa lösningen.
Högerklicka på
DemoMarqueeControl
och välj alternativet Kör test på snabbmenyn för att starta animeringen. Klicka på Stoppa testet för att stoppa animationen.Öppna Form1 i designvyn.
Placera två Button kontroller i formuläret. Ge dem namnet
startButton
ochstopButton
och ändra egenskapsvärdena för Text till Start respektive Stop.Implementera Click händelsehanterare för båda Button kontroller.
Öppna fliken MarqueeControlTest Components i Toolbox. Du ser
DemoMarqueeControl
tillgängliga för val.Dra en instans av
DemoMarqueeControl
på designytan Form1.I Click händelsehanterare anropar du metoderna
Start
ochStop
på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(); }
Ange
MarqueeControlTest
projektet som startprojekt och kör det. Du kommer att se formuläret som visar dinDemoMarqueeControl
. Välj knappen Starta för att starta animeringen. Du bör se texten blinka och lamporna som rör sig runt kantlinjen.
Nästa steg
MarqueeControlLibrary
visar en enkel implementering av anpassade kontroller och tillhörande designers. Du kan göra det här exemplet mer avancerat på flera sätt:
Ändra egenskapsvärdena för
DemoMarqueeControl
i designern. Lägg till flerMarqueBorder
kontroller och docka dem i sina överordnade instanser för att skapa en kapslad effekt. Experimentera med olika inställningar förUpdatePeriod
och ljusrelaterade egenskaper.Skapa egna implementeringar av
IMarqueeWidget
. Du kan till exempel skapa en blinkande "neonskylt" eller en animerad skylt med flera bilder.Anpassa designtidsupplevelsen ytterligare. Du kan prova att skugga fler egenskaper än Enabled och Visible, och du kan lägga till nya egenskaper. Lägg till nya designerverb för att förenkla vanliga uppgifter som dockning av barnkontroller.
Bevilja licens för
MarqueeControl
.Kontrollera hur dina kontroller serialiseras och hur kod genereras för dem. Mer information finns i dynamisk källkodsgenerering och kompilering.
Se även
.NET Desktop feedback