Tutorial: Crear un control que aproveche las características en tiempo de diseño
La experiencia en tiempo de diseño de un control personalizado puede mejorarse con la creación de un diseñador personalizado que se asocie a este.
Precaución
Este contenido se ha escrito para .NET Framework. Si usa .NET 6 o una versión posterior, use este contenido con precaución. El sistema de diseñador ha cambiado para Windows Forms y es importante que revise el artículo Cambios del diseñador desde .NET Framework.
En este artículo se muestra cómo crear un diseñador personalizado para un control personalizado. Implementará un tipo MarqueeControl
y una clase de diseñador asociada denominada MarqueeControlRootDesigner
.
El tipo MarqueeControl
implementa una pantalla similar a una marquesina de teatro con luces animadas y texto parpadeante.
El diseñador de este control interactúa con el entorno de diseño para proporcionar una experiencia personalizada en tiempo de diseño. Con el diseñador personalizado, puede ensamblar una implementación MarqueeControl
personalizada con diversas combinaciones de luces animadas y texto parpadeante. Puede usar el control ensamblado en un formulario como cualquier otro control de Windows Forms.
Cuando haya terminado con este tutorial, el control personalizado tendrá un aspecto similar al siguiente:
Para ver la lista de códigos completa, consulte Cómo: Crear un control de Windows Forms que aproveche las características en tiempo de diseño.
Prerrequisitos
Para completar este tutorial, necesitará Visual Studio.
Crear el proyecto
El primer paso es crear el proyecto de la aplicación. Este proyecto se usará para compilar la aplicación que hospeda el control personalizado.
En Visual Studio, cree un proyecto de aplicación de Windows Forms y asígnele el nombre MarqueeControlTest.
Creación del proyecto de biblioteca de controles
Agregue a la solución un proyecto de biblioteca de controles de Windows Forms y asígnele el nombre MarqueeControlLibrary.
Elimine el control predeterminado del proyecto mediante el Explorador de soluciones; para ello, elimine el archivo de código fuente denominado "UserControl1.cs" o "UserControl1.vb", en función del lenguaje de su elección.
Agregue un elemento UserControl nuevo al proyecto
MarqueeControlLibrary
. Asigne al archivo de código fuente nuevo el nombre base MarqueeControl.Con el Explorador de soluciones, cree una carpeta en el proyecto
MarqueeControlLibrary
.Haga clic con el botón derecho en la carpeta Design y agregue una clase nueva. Asígnele el nombre MarqueeControlRootDesigner.
Deberá usar tipos del ensamblado System.Design, por lo que debe agregar esta referencia al proyecto
MarqueeControlLibrary
.
Referencia al proyecto del control personalizado
El proyecto MarqueeControlTest
se va a usar para probar el control personalizado. El proyecto de prueba reconocerá el control personalizado al agregar una referencia de proyecto al ensamblado MarqueeControlLibrary
.
Agregue una referencia de proyecto al ensamblado MarqueeControlLibrary
en el proyecto MarqueeControlTest
. Asegúrese de usar la pestaña Proyectos del cuadro de diálogo Agregar referencia en lugar de hacer referencia al ensamblado MarqueeControlLibrary
directamente.
Definición de un control personalizado y de su diseñador personalizado
El control personalizado se derivará de la clase UserControl. Esto permite que el control contenga otros controles y proporciona a este numerosas funcionalidades predeterminadas.
El control personalizado tendrá un diseñador personalizado asociado. Esto le permite crear una experiencia de diseño única adaptada a su control personalizado de forma específica.
El control se asocia a su diseñador mediante la clase DesignerAttribute. Puesto que está desarrollando el comportamiento en tiempo de diseño del control personalizado al completo, el diseñador personalizado implementará la interfaz IRootDesigner.
Para definir un control personalizado y su diseñador personalizado
Abra el archivo de código fuente
MarqueeControl
en el Editor de código. En la parte superior del archivo, importe los espacios de nombres siguientes: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
Agregue el atributo DesignerAttribute a la declaración de clase
MarqueeControl
. Esto asocia el control personalizado a su diseñador.[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : UserControl {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControl
Abra el archivo de código fuente
MarqueeControlRootDesigner
en el Editor de código. En la parte superior del archivo, importe los espacios de nombres siguientes: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
Cambie la declaración de
MarqueeControlRootDesigner
para heredar de la clase DocumentDesigner. Aplique el atributo ToolboxItemFilterAttribute para especificar la interacción del diseñador con el Cuadro de herramientas.Nota
La definición de la clase
MarqueeControlRootDesigner
se ha incluido en un espacio de nombres denominado MarqueeControlLibrary.Design. Esta declaración coloca el diseñador en un espacio de nombres especial reservado para tipos relacionados con el diseño.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
Defina el constructor de la clase
MarqueeControlRootDesigner
. Inserte una instrucción WriteLine en el cuerpo del constructor. Esto resultará útil para la depuración.public MarqueeControlRootDesigner() { Trace.WriteLine("MarqueeControlRootDesigner ctor"); }
Public Sub New() Trace.WriteLine("MarqueeControlRootDesigner ctor") End Sub
Creación de una instancia del control personalizado
Agregue un elemento UserControl nuevo al proyecto
MarqueeControlTest
. Asigne al archivo de código fuente nuevo el nombre base DemoMarqueeControl.Abra el archivo
DemoMarqueeControl
en el Editor de código. En la parte superior del archivo, importe el espacio de nombresMarqueeControlLibrary
:Imports MarqueeControlLibrary
using MarqueeControlLibrary;
Cambie la declaración de
DemoMarqueeControl
para heredar de la claseMarqueeControl
.Compile el proyecto.
Abra Form1 en el Diseñador de Windows Forms.
Busque la pestaña de Componentes MarqueeControlTest en el Cuadro de herramientas y ábrala. Arrastre un elemento
DemoMarqueeControl
del Cuadro de herramientas al formulario.Compile el proyecto.
Configuración del proyecto para la depuración en tiempo de diseño
Al desarrollar una experiencia personalizada en tiempo de diseño, deberá depurar los controles y los componentes. Existe una forma sencilla de configurar el proyecto para permitir la depuración en tiempo de diseño. Para obtener más información, consulte Tutorial: Depuración de controles personalizados de Windows Forms en tiempo de diseño.
Haga clic con el botón derecho en el proyecto
MarqueeControlLibrary
y seleccione Propiedades.En el cuadro de diálogo Páginas de propiedades de MarqueeControlLibrary, seleccione la página Depurar.
En la sección Acción de inicio, seleccione Iniciar programa externo. Va a depurar una instancia independiente de Visual Studio, por lo que debe hacer clic en el botón de puntos suspensivos () para buscar el IDE de Visual Studio. El nombre del archivo ejecutable es devenv.exe y, si ha realizado la instalación en la ubicación predeterminada, su ruta de acceso es %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edición>\Common7\IDE\devenv.exe.
Seleccione Aceptar para cerrar el cuadro de diálogo.
Haga clic con el botón derecho en el proyecto MarqueeControlLibrary y seleccione Establecer como proyecto de inicio para habilitar esta configuración de depuración.
Punto de control
Ahora está listo para depurar el comportamiento en tiempo de diseño del control personalizado. Una vez que haya determinado que el entorno de depuración se ha configurado correctamente, probará la asociación entre el control personalizado y el diseñador personalizado.
Para probar el entorno de depuración y la asociación del diseñador
Abra el archivo de código fuente MarqueeControlRootDesigner en el Editor de código y coloque un punto de interrupción en la instrucción WriteLine.
Presione F5 para iniciar la sesión de depuración.
Se crea una instancia de Visual Studio.
En esa nueva instancia de Visual Studio, abra la solución MarqueeControlTest. Si quiere encontrar la solución de forma fácil, seleccione Proyectos recientes en el menú Archivo. El archivo de solución MarqueeControlTest.sln se mostrará como el archivo usado más recientemente.
Abra
DemoMarqueeControl
en el diseñador.La instancia de depuración de Visual Studio obtiene el foco y la ejecución se detiene en el punto de interrupción. Presione F5 para continuar con la sesión de depuración.
En este punto, todo está listo para desarrollar y depurar el control personalizado y su diseñador personalizado asociado. El resto del artículo se centra en los detalles de las características de implementación del control y el diseñador.
Implementación del control personalizado
MarqueeControl
es una clase UserControl ligeramente personalizada. Expone dos métodos: Start
, que inicia la animación de la marquesina, y Stop
, que la detiene. Dado que MarqueeControl
contiene controles secundarios que implementan la interfaz IMarqueeWidget
, Start
y Stop
enumeran cada uno de los controles secundarios y llaman a los métodos StartMarquee
y StopMarquee
, respectivamente, en cada control secundario que implementa IMarqueeWidget
.
La apariencia de los controles MarqueeBorder
y MarqueeText
depende del diseño, por lo que MarqueeControl
invalida el método OnLayout y llama a PerformLayout en los controles secundarios de este tipo.
Esta es la extensión de las personalizaciones MarqueeControl
. Los controles MarqueeBorder
y MarqueeText
implementan las características en tiempo de ejecución y las clases MarqueeBorderDesigner
y MarqueeControlRootDesigner
implementan las características en tiempo de diseño.
Para implementar el control personalizado
Abra el archivo de código fuente
MarqueeControl
en el Editor de código. Implemente los métodosStart
yStop
.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
Invalide el método 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
Creación de un control secundario para el control personalizado
El elemento MarqueeControl
hospedará dos tipos de controles secundarios: los controles MarqueeBorder
y MarqueeText
.
MarqueeBorder
: este control dibuja un borde de "luces" alrededor del perímetro. Las luces parpadean en secuencia, por lo que parecen moverse alrededor del borde. La velocidad a la que parpadean las luces se controla mediante una propiedad llamadaUpdatePeriod
. Otros aspectos de la apariencia del control se determinan mediante otras propiedades personalizadas. Dos métodos, denominadosStartMarquee
yStopMarquee
, controlan cuándo se inicia y se detiene la animación.MarqueeText
: este control dibuja una cadena parpadeante. Al igual que en el controlMarqueeBorder
, la velocidad a la que el texto parpadea se controla mediante la propiedadUpdatePeriod
. El controlMarqueeText
también tiene los métodosStartMarquee
yStopMarquee
en común con el controlMarqueeBorder
.
En tiempo de diseño, MarqueeControlRootDesigner
permite que estos dos tipos de control se agreguen a MarqueeControl
combinados de cualquier forma.
Las características comunes de ambos controles se factorizan en una interfaz denominada IMarqueeWidget
. Esto permite que MarqueeControl
detecte cualquier control secundario relacionado con Marquee y le aplique un tratamiento especial.
Para implementar la característica de animación periódica, se usarán los objetos BackgroundWorker del espacio de nombres System.ComponentModel. Puede usar objetos Timer, pero si hay muchos objetos IMarqueeWidget
presentes, es posible que el subproceso de la interfaz de usuario simple no pueda hacer frente a la animación.
Para crear un control secundario del control personalizado
Agregue un nuevo elemento de clase al proyecto
MarqueeControlLibrary
. Asigne al nuevo archivo de código fuente el nombre base "IMarqueeWidget".Abra el archivo de código fuente
IMarqueeWidget
en el Editor de código y cambie la declaración declass
ainterface
:// 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
Agregue el código siguiente a la interfaz
IMarqueeWidget
para exponer dos métodos y una propiedad que manipule la animación de la marquesina:// 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
Agregue un elemento Control personalizado nuevo al proyecto
MarqueeControlLibrary
. Asigne al archivo de código fuente nuevo el nombre base "MarqueeText".Arrastre un componente BackgroundWorker del Cuadro de herramientas al control
MarqueeText
. Este componente permitirá que el controlMarqueeText
se actualice automáticamente de forma asincrónica.En la ventana Propiedades, establezca las propiedades
WorkerReportsProgress
y WorkerSupportsCancellation del componente BackgroundWorker en true. Esta configuración permite que el componente BackgroundWorker genere el evento ProgressChanged de forma periódica y cancele las actualizaciones asincrónicas.Para obtener más información, vea BackgroundWorker (Componente).
Abra el archivo de código fuente
MarqueeText
en el Editor de código. En la parte superior del archivo, importe los espacios de nombres siguientes: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
Cambie la declaración de
MarqueeText
para heredar de Label y para implementar la interfazIMarqueeWidget
:[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {
<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
Declare las variables de instancia que corresponden a las propiedades expuestas e inicialícelas en el constructor. El campo
isLit
determina si el texto se va a pintar del color indicado por la propiedadLightColor
.// 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
Implemente la interfaz
IMarqueeWidget
.Los métodos
StartMarquee
yStopMarquee
invocan a los métodos RunWorkerAsync y CancelAsync del componente BackgroundWorker para iniciar y detener la animación.Los atributos Category y Browsable se aplican a la propiedad
UpdatePeriod
para que aparezca en una sección personalizada de la ventana Propiedades denominada "Marquee".public virtual void StartMarquee() { // Start the updating thread and pass it the UpdatePeriod. this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod); } public virtual void StopMarquee() { // Stop the updating thread. this.backgroundWorker1.CancelAsync(); } [Category("Marquee")] [Browsable(true)] public int UpdatePeriod { get { return this.updatePeriodValue; } set { if (value > 0) { this.updatePeriodValue = value; } else { throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0"); } } }
Public Overridable Sub StartMarquee() _ Implements IMarqueeWidget.StartMarquee ' Start the updating thread and pass it the UpdatePeriod. Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod) End Sub Public Overridable Sub StopMarquee() _ Implements IMarqueeWidget.StopMarquee ' Stop the updating thread. Me.backgroundWorker1.CancelAsync() End Sub <Category("Marquee"), Browsable(True)> _ Public Property UpdatePeriod() As Integer _ Implements IMarqueeWidget.UpdatePeriod Get Return Me.updatePeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.updatePeriodValue = Value Else Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0") End If End Set End Property
Implemente los descriptores de acceso de propiedad. Expondrá dos propiedades a los clientes:
LightColor
yDarkColor
. Los atributos Category y Browsable se aplican a estas propiedades, de forma que estas aparecen en una sección personalizada de la ventana Propiedades denominada "Marquee".[Category("Marquee")] [Browsable(true)] public Color LightColor { get { return this.lightColorValue; } set { // The LightColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.lightColorValue.ToArgb() != value.ToArgb()) { this.lightColorValue = value; this.lightBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public Color DarkColor { get { return this.darkColorValue; } set { // The DarkColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.darkColorValue.ToArgb() != value.ToArgb()) { this.darkColorValue = value; this.darkBrush = new SolidBrush(value); } } }
<Category("Marquee"), Browsable(True)> _ Public Property LightColor() As Color Get Return Me.lightColorValue End Get Set(ByVal Value As Color) ' The LightColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then Me.lightColorValue = Value Me.lightBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property DarkColor() As Color Get Return Me.darkColorValue End Get Set(ByVal Value As Color) ' The DarkColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then Me.darkColorValue = Value Me.darkBrush = New SolidBrush(Value) End If End Set End Property
Implemente los controladores para los eventos DoWork y ProgressChanged del componente BackgroundWorker.
El controlador de eventos DoWork está en suspensión durante el número de milisegundos que especifique
UpdatePeriod
y, a continuación, genera el evento ProgressChanged, hasta que el código detiene la animación mediante la llamada a CancelAsync.El controlador de eventos ProgressChanged alterna el texto entre los estados claro y oscuro para dar la apariencia de parpadeo.
// 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
Invalide el método OnPaint para habilitar la animación.
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
Presione F6 para compilar la solución.
Creación del control secundario MarqueeBorder
El control MarqueeBorder
es algo más sofisticado que el control MarqueeText
. Tiene más propiedades y la animación del método OnPaint es más compleja. En principio, es bastante similar al control MarqueeText
.
Dado que el control MarqueeBorder
puede tener controles secundarios, debe tener en cuenta los eventos Layout.
Para crear el control MarqueeBorder
Agregue un elemento Control personalizado nuevo al proyecto
MarqueeControlLibrary
. Asigne al archivo de código fuente nuevo el nombre base "MarqueeBorder".Arrastre un componente BackgroundWorker del Cuadro de herramientas al control
MarqueeBorder
. Este componente permitirá que el controlMarqueeBorder
se actualice automáticamente de forma asincrónica.En la ventana Propiedades, establezca las propiedades
WorkerReportsProgress
y WorkerSupportsCancellation del componente BackgroundWorker en true. Esta configuración permite que el componente BackgroundWorker genere el evento ProgressChanged de forma periódica y cancele las actualizaciones asincrónicas. Para obtener más información, vea BackgroundWorker (Componente).En la ventana Propiedades, seleccione el botón Eventos. Asocie controladores para los eventos DoWork y ProgressChanged.
Abra el archivo de código fuente
MarqueeBorder
en el Editor de código. En la parte superior del archivo, importe los espacios de nombres siguientes: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
Cambie la declaración de
MarqueeBorder
para heredar de Panel y para implementar la interfazIMarqueeWidget
.[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
Declare dos enumeraciones para administrar el estado del control
MarqueeBorder
:MarqueeSpinDirection
, que determina la dirección en la que las luces "giran" alrededor del borde yMarqueeLightShape
, que determina la forma de las luces (cuadradas o circulares). Coloque estas declaraciones delante de la declaración de claseMarqueeBorder
.// 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
Declare las variables de instancia que corresponden a las propiedades expuestas e inicialícelas en el constructor.
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
Implemente la interfaz
IMarqueeWidget
.Los métodos
StartMarquee
yStopMarquee
invocan a los métodos RunWorkerAsync y CancelAsync del componente BackgroundWorker para iniciar y detener la animación.Dado que el control
MarqueeBorder
puede contener controles secundarios, el métodoStartMarquee
enumera todos los controles secundarios y llama aStartMarquee
en los que implementanIMarqueeWidget
. El métodoStopMarquee
tiene una implementación similar.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
Implemente los descriptores de acceso de propiedad. El control
MarqueeBorder
tiene varias propiedades para controlar su apariencia.[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
Implemente los controladores para los eventos DoWork y ProgressChanged del componente BackgroundWorker.
El controlador de eventos DoWork está en suspensión durante el número de milisegundos que especifique
UpdatePeriod
y, a continuación, genera el evento ProgressChanged, hasta que el código detiene la animación mediante la llamada a CancelAsync.El controlador de eventos ProgressChanged incrementa la posición de la luz "base", desde la que se determina el estado claro u oscuro del resto de luces, y llama al método Refresh para que el control vuelva a dibujarse.
// 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
Implemente los métodos auxiliares
IsLit
yDrawLight
.El método
IsLit
determina el color de una luz en una posición determinada. Las luces "encendidas" se dibujan en el color que indica la propiedadLightColor
y las que son "oscuras" se dibujan en el color que indica la propiedadDarkColor
.El método
DrawLight
dibuja una luz con el color, la forma y la posición adecuados.// 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
Invalide los métodos OnLayout y OnPaint.
El método OnPaint dibuja las luces a lo largo de los bordes del control
MarqueeBorder
.Dado que el método OnPaint depende de las dimensiones del control
MarqueeBorder
, debe llamarlo cada vez que cambie el diseño. Para ello, invalide OnLayout y llame a 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
Creación de un diseñador personalizado para sombrear y filtrar propiedades
La clase MarqueeControlRootDesigner
proporciona la implementación del diseñador raíz. Además de este diseñador, que funciona en MarqueeControl
, necesitará un diseñador personalizado que esté asociado específicamente con el control MarqueeBorder
. Este diseñador proporciona un comportamiento personalizado que resulta adecuado en el contexto del diseñador raíz personalizado.
En concreto, MarqueeBorderDesigner
"sombreará" y filtrará ciertas propiedades en el control MarqueeBorder
, lo que cambiará su interacción con el entorno de diseño.
La intercepción de llamadas al descriptor de acceso de propiedades de un componente se conoce como "sombreado". Permite a un diseñador realizar un seguimiento del valor establecido por el usuario y, opcionalmente, pasar ese valor al componente que se está diseñando.
En este ejemplo, MarqueeBorderDesigner
sombreará las propiedades Visible y Enabled, lo que impide que el usuario pueda hacer que el control MarqueeBorder
sea invisible o se deshabilite durante el tiempo de diseño.
Los diseñadores también pueden agregar y quitar propiedades. En este ejemplo, la propiedad Padding se quitará en tiempo de diseño, ya que el control MarqueeBorder
establece el relleno mediante programación, en función del tamaño de las luces que especifica la propiedad LightSize
.
La clase base para MarqueeBorderDesigner
es ComponentDesigner, que tiene métodos para cambiar los atributos, las propiedades y los eventos que expone un control en tiempo de diseño:
Al cambiar la interfaz pública de un componente mediante estos métodos, siga estas reglas:
Agregar o quitar elementos solamente en los métodos
PreFilter
Modificar los elementos existentes solamente en los métodos
PostFilter
Llamar siempre primero a la implementación base en los métodos
PreFilter
Llamar siempre en último lugar a la implementación base en los métodos
PostFilter
Cumplir estas reglas garantiza que todos los diseñadores del entorno en tiempo de diseño tengan una visión coherente de todos los componentes que se están diseñando.
La clase ComponentDesigner proporciona un diccionario para administrar los valores de las propiedades sombreadas, lo que elimina la necesidad de crear variables de instancias específicas.
Para crear un diseñador personalizado a fin de sombrear y filtrar propiedades
Haga clic con el botón derecho en la carpeta Design y agregue una clase nueva. Asigne al archivo de código fuente el nombre base MarqueeBorderDesigner.
Abra el archivo de código fuente MarqueeBorderDesigner en el Editor de código. En la parte superior del archivo, importe los espacios de nombres siguientes:
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
Cambie la declaración de
MarqueeBorderDesigner
para heredar de ParentControlDesigner.Dado que el control
MarqueeBorder
puede contener controles secundarios,MarqueeBorderDesigner
hereda de ParentControlDesigner, lo que controla la interacción de elementos primarios y secundarios.namespace MarqueeControlLibrary.Design { public class MarqueeBorderDesigner : ParentControlDesigner {
Namespace MarqueeControlLibrary.Design Public Class MarqueeBorderDesigner Inherits ParentControlDesigner
Reemplace la implementación base de 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
Implemente las propiedades Enabled y Visible. Estas implementaciones sombrean las propiedades del control.
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
Control de los cambios de componentes
La clase MarqueeControlRootDesigner
proporciona una experiencia personalizada en tiempo de diseño para las instancias de MarqueeControl
. La mayor parte de la funcionalidad en tiempo de diseño se hereda de la clase DocumentDesigner. El código implementará dos personalizaciones específicas: el control de los cambios de componentes y la adición de verbos del diseñador.
Cuando los usuarios diseñan sus instancias de MarqueeControl
, el diseñador raíz realizará un seguimiento de los cambios realizados en MarqueeControl
y en sus controles secundarios. El entorno en tiempo de diseño ofrece un servicio práctico, IComponentChangeService, para realizar un seguimiento de los cambios en el estado del componente.
Para obtener una referencia a este servicio, consulte el entorno con el método GetService. Si la consulta se realiza correctamente, el diseñador puede asociar un controlador para el evento ComponentChanged y realizar las tareas necesarias para mantener un estado coherente en tiempo de diseño.
En el caso de la clase MarqueeControlRootDesigner
, llamará al método Refresh en cada objeto IMarqueeWidget
que MarqueeControl
contenga. Esto hará que el objeto IMarqueeWidget
se vuelva a dibujar correctamente de forma automática cuando se cambien propiedades como Size en el elemento primario.
Para controlar los cambios de los componentes
Abra el archivo de código fuente
MarqueeControlRootDesigner
en el Editor de código y reemplace el método Initialize. Llame a la implementación base de Initialize y consulte 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
Implemente el controlador de eventos OnComponentChanged. Pruebe el tipo del componente de envío y, si es un elemento
IMarqueeWidget
, llame a su método 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
Adición de verbos de diseñador al diseñador personalizado
Un verbo de diseñador es un comando de menú vinculado a un controlador de eventos. Los verbos de diseñador se agregan al menú contextual de un componente en tiempo de diseño. Para obtener más información, vea DesignerVerb.
Va a agregar dos verbos de este tipo a sus diseñadores: Ejecutar prueba y Detener prueba. Estos verbos le permitirán ver el comportamiento en tiempo de ejecución de MarqueeControl
en tiempo de diseño y se agregarán a MarqueeControlRootDesigner
.
Cuando se invoca Ejecutar prueba, el controlador de eventos de verbo llamará al método StartMarquee
en MarqueeControl
. Cuando se invoca Detener prueba, el controlador de eventos de verbo llamará al método StopMarquee
en MarqueeControl
. La implementación de los métodos StartMarquee
y StopMarquee
llama a estos métodos en los controles contenidos que implementan IMarqueeWidget
, por lo que cualquier control IMarqueeWidget
contenido también participará en la prueba.
Para agregar verbos de diseñador a los diseñadores personalizados
En la clase
MarqueeControlRootDesigner
, agregue controladores de eventos denominadosOnVerbRunTest
yOnVerbStopTest
.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
Conecte estos controladores de eventos con sus verbos de diseñador correspondientes.
MarqueeControlRootDesigner
hereda un elemento DesignerVerbCollection de su clase base. Va a crear dos objetos DesignerVerb nuevos y a agregarlos a esta colección en el método 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)))
Creación de un elemento UITypeEditor personalizado
Al crear una experiencia personalizada en tiempo de diseño para los usuarios, a menudo es conveniente crear una interacción personalizada con la ventana Propiedades. Para lograrlo, cree un objeto UITypeEditor.
El control MarqueeBorder
expone varias propiedades en la ventana Propiedades. Dos de estas propiedades, MarqueeSpinDirection
y MarqueeLightShape
, se representan mediante enumeraciones. Para ilustrar el uso de un editor de tipos de interfaz de usuario, se asociará una clase UITypeEditor a la propiedad MarqueeLightShape
.
Para crear un editor de tipos de interfaz de usuario personalizado
Abra el archivo de código fuente
MarqueeBorder
en el Editor de código.En la definición de la clase
MarqueeBorder
, declare una clase denominadaLightShapeEditor
que se derive de 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
Declare una variable de instancia de IWindowsFormsEditorService llamada
editorService
.private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
Invalide el método GetEditStyle. Esta implementación devuelve DropDown, que indica al entorno de diseño cómo mostrar el editor
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
Invalide el método EditValue. Esta implementación consulta el entorno de diseño para un objeto IWindowsFormsEditorService. Si se realiza correctamente, crea un objeto
LightShapeSelectionControl
. El método DropDownControl se invoca para iniciarLightShapeEditor
. El valor devuelto de esta invocación se devuelve al entorno de diseño.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
Creación de un control de vista para el objeto UITypeEditor personalizado
La propiedad MarqueeLightShape
admite dos tipos de formas de luz: Square
y Circle
. Va a crear un control personalizado que se use exclusivamente para mostrar estos valores de forma gráfica en la ventana Propiedades. El objeto UITypeEditor usará este control personalizado para interactuar con la ventana Propiedades.
Para crear un control de vista para el editor de tipos de interfaz de usuario personalizado
Agregue un elemento UserControl nuevo al proyecto
MarqueeControlLibrary
. Asigne al archivo de código fuente nuevo el nombre base LightShapeSelectionControl.Arrastre dos controles Panel desde el Cuadro de herramientas a
LightShapeSelectionControl
. Asígneles los nombressquarePanel
ycirclePanel
. Organícelos uno al lado del otro. Establezca la propiedad Size de ambos controles Panel en (60, 60). Establezca la propiedad Location del controlsquarePanel
en (8, 10). Establezca la propiedad Location del controlcirclePanel
en (80, 10). Por último, establezca la propiedad Size deLightShapeSelectionControl
en (150, 80).Abra el archivo de código fuente
LightShapeSelectionControl
en el Editor de código. En la parte superior del archivo, importe el espacio de nombres System.Windows.Forms.Design:Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
Implemente controladores de eventos Click para los controles
squarePanel
ycirclePanel
. Estos métodos invocan a CloseDropDown para finalizar la sesión de edición de UITypeEditor personalizada.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
Declare una variable de instancia de IWindowsFormsEditorService llamada
editorService
.Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
Declare una variable de instancia de
MarqueeLightShape
llamadalightShapeValue
.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
En el constructor
LightShapeSelectionControl
, asocie los controladores de eventos Click a los eventos Click de los controlessquarePanel
ycirclePanel
. Asimismo, defina una sobrecarga de constructor que asigne el valorMarqueeLightShape
del entorno de diseño al campolightShapeValue
.// 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
En el método Dispose, desasocie los controladores de eventos Click.
protected override void Dispose( bool disposing ) { if( disposing ) { // Be sure to unhook event handlers // to prevent "lapsed listener" leaks. this.squarePanel.Click -= new EventHandler(squarePanel_Click); this.circlePanel.Click -= new EventHandler(circlePanel_Click); if(components != null) { components.Dispose(); } } base.Dispose( disposing ); }
Protected Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then ' Be sure to unhook event handlers ' to prevent "lapsed listener" leaks. RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click If (components IsNot Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub
En el Explorador de soluciones, haga clic en el botón Mostrar todos los archivos. Abra el archivo LightShapeSelectionControl.Designer.cs o LightShapeSelectionControl.Designer.vb y quite la definición predeterminada del método Dispose.
Implemente la propiedad
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
Invalide el método OnPaint. Esta implementación dibujará un cuadrado y un círculo rellenos. También dibujará un borde alrededor de una forma o la otra para resaltar el valor seleccionado.
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
Prueba del control personalizado en el diseñador
En este punto, puede compilar el proyecto MarqueeControlLibrary
. Cree un control que herede de la clase MarqueeControl
y úselo en un formulario para probar su implementación.
Para crear una implementación personalizada de MarqueeControl
Abra
DemoMarqueeControl
en el Diseñador de Windows Forms. Al hacerlo, se crea una instancia de tipoDemoMarqueeControl
y se muestra en una instancia de tipoMarqueeControlRootDesigner
.En el Cuadro de herramientas, abra la pestaña Componentes MarqueeControlLibrary. Verá los controles
MarqueeBorder
yMarqueeText
disponibles para su selección.Arrastre una instancia del control
MarqueeBorder
a la superficie de diseño deDemoMarqueeControl
. Acople este controlMarqueeBorder
al control primario.Arrastre una instancia del control
MarqueeText
a la superficie de diseño deDemoMarqueeControl
.Compile la solución.
Haga clic con el botón derecho en
DemoMarqueeControl
y, en el menú contextual, seleccione la opción Ejecutar prueba para iniciar la animación. Haga clic en Detener prueba para detener la animación.Abra Form1 en la vista Diseño.
Coloque dos controles Button en el formulario. Asígneles los nombres
startButton
ystopButton
y cambie los valores de la propiedad Text a Iniciar y Detener, respectivamente.Implemente controladores de eventos Click para ambos controles Button.
En el Cuadro de herramientas, abra la pestaña de Componentes MarqueeControlTest. Verá
DemoMarqueeControl
disponible para su selección.Arrastre una instancia de
DemoMarqueeControl
a la superficie de diseño de Form1.En los controladores de eventos Click, invoque los métodos
Start
yStop
enDemoMarqueeControl
.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(); }
Establezca el proyecto
MarqueeControlTest
como proyecto de inicio y ejecútelo. Verá que el formulario muestra suDemoMarqueeControl
. Seleccione el botón Iniciar para iniciar la animación. El texto debería aparecer parpadeando y las luces deberían moverse alrededor del borde.
Pasos siguientes
La biblioteca MarqueeControlLibrary
muestra una implementación sencilla de los controles personalizados y los diseñadores asociados. Existen diversas formas de hacer que este ejemplo sea más sofisticado:
Cambie los valores de las propiedades de
DemoMarqueeControl
en el diseñador. Agregue más controlesMarqueBorder
y acóplelos dentro de sus instancias primarias para crear un efecto anidado. Experimente con distintas configuraciones paraUpdatePeriod
y las propiedades relacionadas con la luz.Cree sus propias implementaciones de
IMarqueeWidget
. Por ejemplo, puede crear un "letrero de neón" parpadeante o un letrero animado con varias imágenes.Personalice aún más la experiencia en tiempo de diseño. Puede probar a sombrear más propiedades que Enabled y Visible y también agregar nuevas propiedades. Agregue nuevos verbos de diseñador para simplificar tareas comunes, como acoplar los controles secundarios.
Obtenga una licencia de
MarqueeControl
.Controle cómo se serializan los controles y cómo se genera el código para ellos. Para obtener más información, consulte Generación y compilación dinámicas de código fuente.
Consulte también
.NET Desktop feedback