Tutorial: Crear un control de formularios Windows Forms que aproveche las características en tiempo de diseño de Visual Studio
Se puede mejorar la experiencia en tiempo de diseño para un control personalizado creando un diseñador personalizado asociado.
En este tutorial se muestra cómo crear a 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 presentación similar a una marquesina de teatro, con luces animadas y el texto parpadeante.
El diseñador para este control interactúa con el entorno de diseño para proporcionar una experiencia en tiempo de diseño personalizada. Con el diseñador personalizado, se puede ensamblar una implementación de MarqueeControl
personalizada con luces animadas y texto parpadeante en muchas combinaciones. Se puede utilizar el control ensamblado en un formulario como cualquier otro control de formularios Windows Forms.
Las tareas ilustradas en este tutorial incluyen:
Crear el proyecto
Crear un proyecto de Biblioteca de controles
Hacer referencia al proyecto de Biblioteca de controles
Definir un control personalizado y su diseñador personalizado
Crear una instancia del control personalizado
Establecer el proyecto para depuración en tiempo de diseño
Implementar el control personalizado
Crear un control secundario para el control personalizado
Crear el control MarqueeBorder secundario
Crear un diseñador personalizado para sombrear y filtrar propiedades
Controlar los cambios de componente
Agregar verbos del diseñador al diseñador personalizado
Crear un UITypeEditor personalizado
Probar el control personalizado en el diseñador
Cuando termine, el control personalizado tendrá el siguiente aspecto:
Para obtener la lista de código completa, vea Cómo: Crear un control de formularios Windows Forms que aproveche las características en tiempo de diseño.
Nota
Los cuadros de diálogo y comandos de menú que se ven pueden diferir de los descritos en la Ayuda, en función de la configuración activa o la edición. Para cambiar la configuración, elija la opción Importar y exportar configuraciones en el menú Herramientas. Para obtener más información, vea Valores de configuración de Visual Studio.
Requisitos previos
Para poder completar este tutorial, necesitará:
- Permisos suficientes para poder crear y ejecutar proyectos de aplicación de Windows Forms en el equipo donde esté instalado Visual Studio.
Crear el proyecto
El primer paso es crear el proyecto de aplicación. Este proyecto se utilizará para generar la aplicación que aloja el control personalizado.
Para crear el proyecto
- Cree un proyecto de aplicación para Windows llamado a "MarqueeControlTest." Para obtener más información, vea Cómo: Crear un proyecto de aplicación para Windows.
Crear un proyecto de Biblioteca de controles
El paso siguiente es crear el proyecto de Biblioteca de controles. Creará a un nuevo control personalizado y su diseñador personalizado correspondiente.
Para crear el proyecto de Biblioteca de controles
Agregue un proyecto de Biblioteca de controles para Windows a la solución. Para obtener más información, vea Agregar nuevo proyecto (Cuadro de diálogo). Dé al proyecto el nombre de "MarqueeControlLibrary".
Con el Explorador de soluciones, elimine el control predeterminado del proyecto eliminando el archivo de código fuente denominado "UserControl1.cs" o "UserControl1.vb", dependiendo del lenguaje elegido. Para obtener más información, vea Cómo: Quitar, eliminar y excluir elementos.
Agregue un nuevo elemento UserControl al proyecto MarqueeControlLibrary. Dé el nombre base de "MarqueeControl" al nuevo archivo de código fuente.
Con el Explorador de soluciones, cree una nueva carpeta en el proyecto MarqueeControlLibrary. Para obtener más información, vea Cómo: Agregar nuevos elementos de proyecto. Denomine "Diseño" a la nueva carpeta.
Haga clic con el botón secundario del mouse (ratón) en la carpeta Diseño y agregue una nueva clase. Dé el nombre base de "MarqueeControlRootDesigner" al archivo de código fuente.
Necesitará utilizar los tipos del ensamblado System.Design, así que agregue esta referencia al proyecto MarqueeControlTest. Para obtener más información, vea Cómo: Agregar y quitar referencias en Visual Studio (C#, J#).
Hacer referencia al proyecto de Biblioteca de controles
Utilizará el proyecto MarqueeControlTest para probar el control personalizado. El proyecto de prueba se dará cuenta del control personalizado cuando agregue una referencia de proyecto al ensamblado MarqueeControlLibrary.
Para hacer referencia al proyecto de control personalizado
- En el proyecto MarqueeControlTest, agregue una referencia de proyecto al ensamblado MarqueeControlLibrary. Asegúrese de utilizar la ficha Proyectos en el cuadro de diálogo Agregar referencia en lugar de hacer referencia directamente al ensamblado MarqueeControlLibrary.
Definir un control personalizado y su diseñador personalizado
Su control personalizado derivará de la clase UserControl. Esta opción permite al control contener otros controles y da una gran funcionalidad predeterminada al control.
El control personalizado tendrá un diseñador personalizado asociado. Esto le permite crear una experiencia del diseño única, personalizada específicamente para su control personalizado.
Asocie el control a su diseñador utilizando la clase DesignerAttribute. Como está desarrollando el comportamiento en tiempo de diseño completo del control personalizado, 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:Imports System Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Drawing Imports System.Windows.Forms Imports System.Windows.Forms.Design
using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing; using System.Windows.Forms; using System.Windows.Forms.Design;
Agregue DesignerAttribute a la declaración de clase
MarqueeControl
. Esta opción asocia el control personalizado a su diseñador.<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControl
[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : 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:Imports System 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
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;
Cambie la declaración de
MarqueeControlRootDesigner
para heredar de la clase DocumentDesigner. Aplique 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 al diseñador en un espacio de nombres especial reservado para los tipos relacionados con el diseño.Namespace MarqueeControlLibrary.Design <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _ Public Class MarqueeControlRootDesigner Inherits DocumentDesigner
namespace MarqueeControlLibrary.Design { [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] public class MarqueeControlRootDesigner : DocumentDesigner {
Defina el constructor para la clase
MarqueeControlRootDesigner
. Inserte una instrucción WriteLine en el cuerpo del constructor. Esta opción es útil con fines de depuración.Public Sub New() Trace.WriteLine("MarqueeControlRootDesigner ctor") End Sub
public MarqueeControlRootDesigner() { Trace.WriteLine("MarqueeControlRootDesigner ctor"); }
Crear una instancia del control personalizado
Para observar el comportamiento en tiempo de diseño personalizado del control, colocará una instancia de su control en el formulario en el proyecto MarqueeControlTest.
Para crear una instancia de su control personalizado
Agregue un nuevo elemento UserControl al proyecto MarqueeControlTest. Dé el nombre base de "DemoMarqueeControl" al nuevo archivo de código fuente.
Abra el archivo
DemoMarqueeControl
en el Editor de código. En la parte superior del archivo, importe el espacio de nombres MarqueeControlLibrary siguiente:
Imports MarqueeControlLibrary
using MarqueeControlLibrary;
Establecer el proyecto para depuración en tiempo de diseño
Cuando está desarrollando una experiencia en tiempo de diseño personalizada, será necesario depurar los controles y componentes. Hay un modo muy sencillo de configurar el proyecto para permitir la depuración en tiempo de diseño. Para obtener más información, vea Tutorial: Depurar controles personalizados de formularios Windows Forms en tiempo de diseño.
Para configurar el proyecto para depuración en tiempo de diseño
Haga clic con el botón secundario del mouse en el proyecto MarqueeControlLibrary y seleccione Propiedades.
En el cuadro de diálogo "Páginas de propiedades de MarqueeControlLibrary", seleccione la página Propiedades de configuración.
En la sección Acción de inicio, seleccione Programa externo de inicio. Como está depurando una instancia independiente de Visual Studio, haga 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 lo ha instalado en la ubicación predeterminada, la ruta de acceso será "C:\Archivos de programa\Microsoft Visual Studio 8\Common7\IDE\devenv.exe".
Haga clic en Aceptar para cerrar el cuadro de diálogo.
Haga clic con el botón secundario del mouse 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 de su control personalizado. Una vez determinado que el entorno de depuración está 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 depuración. Observe que se crea una nueva instancia de Visual Studio.
En la nueva instancia de Visual Studio, abra la solución "MarqueeControlTest". Para encontrar con facilidad la solución, seleccione Proyectos recientes en el menú Archivo. El archivo de solución "MarqueeControlTest.sln" se mostrará como el último archivo utilizado.
Abra
DemoMarqueeControl
en el diseñador. Observe que la instancia de depuración de Visual Studio adquiere el foco y la ejecución se detiene en su punto de interrupción. Presione F5 para continuar 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 de este tutorial se concentrará en los detalles de implementación de las características del control y el diseñador.
Implementar el control personalizado
MarqueeControl
es un UserControl con algo de personalización. Expone dos métodos: Start
, que inicia la animación de la marquesina, y Stop
, que detiene la animación. Como MarqueeControl
contiene controles secundarios que implementan la interfaz IMarqueeWidget
, Start
y Stop
enumeran cada control secundario 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 tanto MarqueeControl
reemplaza el método OnLayout y llama al método PerformLayout en los controles secundarios de este tipo.
Ésta es la extensión de las personalizaciones de 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 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
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(); } } }
Reemplace el método OnLayout.
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
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(); } } }
Crear un control secundario para el control personalizado
MarqueeControl
alojará dos tipos de control secundario: el control MarqueeBorder
y el control MarqueeText
.
MarqueeBorder
: Este control representa un borde de "luces" alrededor de los bordes. Las luces parpadean en forma secuencial, por lo que parece que se mueven alrededor del borde. La velocidad de parpadeo de las luces está controlada por una propiedad llamadaUpdatePeriod
. Varias propiedades personalizadas determinan otros aspectos de la apariencia del control. Dos métodos, llamadosStartMarquee
yStopMarquee
, controlan cuándo se inicia o se detiene la animación.MarqueeText
: Este control representa una cadena parpadeante. Al igual que el controlMarqueeBorder
, la velocidad en la que el texto parpadea está controlada por la propiedadUpdatePeriod
. El controlMarqueeText
también tiene en común los métodosStartMarquee
yStopMarquee
con el controlMarqueeBorder
.
En tiempo de diseño, MarqueeControlRootDesigner
permite agregar estos dos tipos de control a MarqueeControl
en cualquier combinación.
Las características comunes de los dos controles se factorizan en una interfaz llamada IMarqueeWidget
. Esto permite a MarqueeControl
descubrir los controles secundarios relacionados con la marquesina y darles un tratamiento especial.
Para implementar la función de animación periódica, utilizará los objetos BackgroundWorker del espacio de nombres System.ComponentModel. Podría utilizar los objetos Timer, pero cuando hay presentes varios objetos IMarqueeWidget
, es posible que el único subproceso de la interfaz de usuario no pueda mantener la animación.
Para crear un control secundario para su control personalizado
Agregue un nuevo elemento de clase al proyecto MarqueeControlLibrary. Dé el nombre base de "IMarqueeWidget" al nuevo archivo de código fuente.
Abra el archivo de código fuente
IMarqueeWidget
en el Editor de código y cambie la declaración de class a interface:' This interface defines the contract for any class that is to ' be used in constructing a MarqueeControl. Public Interface IMarqueeWidget
// This interface defines the contract for any class that is to // be used in constructing a MarqueeControl. public interface IMarqueeWidget {
Agregue el código siguiente a la interfaz
IMarqueeWidget
para exponer dos métodos y una propiedad que manipulan 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. 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
// 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; } }
Agregue un nuevo elemento Control personalizado al proyecto MarqueeControlLibrary. Dé el nombre base de "MarqueeText" al nuevo archivo de código fuente.
Arrastre un componente BackgroundWorker desde el Cuadro de herramientas hasta el control
MarqueeText
. Este componente permitirá al controlMarqueeText
actualizarse de forma asincrónica.En la ventana Propiedades, establezca las propiedades WorkerReportsProgess y WorkerSupportsCancellation del componente BackgroundWorker en true. Esta configuración permite al componente BackgroundWorker provocar periódicamente el evento ProgressChanged y cancelar 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:Imports System 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
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;
Cambie la declaración de
MarqueeText
para heredar de Label e implementar la interfazIMarqueeWidget
:<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, 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 representa en el color proporcionado 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 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 'New
// 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); }
Implemente la interfaz
IMarqueeWidget
.Los métodos
StartMarquee
yStopMarquee
invocan los métodos RunWorkerAsync y CancelAsync del componente BackgroundWorker para iniciar y detener la aplicación.Los atributos Category y Browsable se aplican a la propiedad
UpdatePeriod
, por lo que aparece en una sección personalizada de la ventana Propiedades llamada "Marquesina".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
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"); } } }
Implemente los descriptores de acceso de propiedad. Expone dos propiedades a los clientes:
LightColor
yDarkColor
. Los atributos Category y Browsable se aplican a estas propiedades, por lo que aparece en una sección personalizada de la ventana Propiedades llamada "Marquesina".<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 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); } } }
Implemente los controladores para los eventos DoWork y ProgressChanged del componente BackgroundWorker.
El controlador de eventos DoWork permanece desactivado durante la cantidad de milisegundos especificada en
UpdatePeriod
y luego provoca el evento ProgressChanged, hasta que el código detiene la aplicación llamando al método CancelAsync.El controlador de eventos ProgressChanged alterna el estado de iluminación y apagado del texto para proporcionar 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 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
// 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(); }
Reemplace el método OnPaint para habilitar la animación.
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
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); }
Presione F6 para generar la solución.
Crear el control MarqueeBorder secundario
El control MarqueeBorder
es ligeramente más complejo más que el control MarqueeText
. Tiene más propiedades y el método OnPaint presenta más animación. En principio, es bastante similar al control MarqueeText
.
Dado que el control MarqueeBorder
puede tener controles secundarios, necesita darse cuenta de los eventos Layout.
Para crear el control MarqueeBorder
Agregue un nuevo elemento Control personalizado al proyecto MarqueeControlLibrary. Dé el nombre base de "MarqueeControl" al nuevo archivo de código fuente.
Arrastre un componente BackgroundWorker desde el Cuadro de herramientas al control
MarqueeBorder
. Este componente permitirá al controlMarqueeBorder
actualizarse de forma asincrónica.En la ventana Propiedades, establezca las propiedades WorkerReportsProgess y WorkerSupportsCancellation del componente BackgroundWorker en true. Esta configuración permite al componente BackgroundWorker provocar periódicamente el evento ProgressChanged y cancelar las actualizaciones asincrónicas. Para obtener más información, vea BackgroundWorker (Componente).
En la ventana Propiedades, haga clic en 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:Imports System 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
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;
Cambie la declaración de
MarqueeBorder
para heredar de Panel e implementar la interfazIMarqueeWidget
:<Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeBorder Inherits Panel Implements IMarqueeWidget
[Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] public partial class MarqueeBorder : Panel, IMarqueeWidget {
Declare dos enumeraciones para administrar el estado del control
MarqueeBorder
:MarqueeSpinDirection
, que determina la dirección en la que la luz "gira" alrededor del borde, yMarqueeLightShape
, que determina la forma de las luces (cuadradas o circulares). Coloque estas declaraciones antes de la declaración de claseMarqueeBorder
.' 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
// 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 }
Declare las variables de instancia que corresponden a las propiedades expuestas e inicialícelas en el constructor.
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
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); }
Implemente la interfaz
IMarqueeWidget
.Los métodos
StartMarquee
yStopMarquee
invocan los métodos RunWorkerAsync y CancelAsync del componente BackgroundWorker para iniciar y detener la aplicación.Como el control
MarqueeBorder
puede contener controles secundarios, el métodoStartMarquee
enumera todos los controles secundarios y llama aStartMarquee
en aquellos que implementaIMarqueeWidget
. El métodoStopMarquee
tiene una implementación similar.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
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"); } } }
Implemente los descriptores de acceso de propiedad. El control
MarqueeBorder
dispone de varias propiedades para controlar su apariencia.<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
[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; } }
Implemente los controladores para los eventos DoWork y ProgressChanged del componente BackgroundWorker.
El controlador de eventos DoWork permanece desactivado durante la cantidad de milisegundos especificada en
UpdatePeriod
y luego provoca el evento ProgressChanged, hasta que el código detiene la aplicación llamando al método CancelAsync.El controlador de eventos ProgressChanged aumenta la posición de la luz "base", desde la cual se determina el estado iluminado/oscuro de las otras luces, y llama al método Refresh para hacer que el control se pinte a sí mismo.
' 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
// 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(); }
Implemente los métodos auxiliares
IsLit
yDrawLight
.El método
IsLit
determina el color de una luz en una posición determinada. Las luces que se "encienden" se representan en el color proporcionado por la propiedadLightColor
, y los que son "oscuros" se representan en el color proporcionado por la propiedadDarkColor
.El método
DrawLight
dibuja una luz utilizando 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 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
// 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; } } }
Utilice 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
, necesita llamarlo cada vez que el diseño cambia. Para ello, reemplace las llamadas a los métodos OnLayout y Refresh.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
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++; } } }
Crear un diseñador personalizado para sombrear y filtrar propiedades
La clase MarqueeControlRootDesigner
proporciona la implementación para el diseñador raíz. Además de este diseñador, que funciona en MarqueeControl
, necesitará a un diseñador personalizado que se asocia específicamente al control MarqueeBorder
. Este diseñador proporciona comportamiento personalizado que es adecuado en el contexto del diseñador raíz personalizado.
Específicamente, MarqueeBorderDesigner
sombreará y filtrará ciertas propiedades en el control MarqueeBorder
, cambiando su interacción con el entorno de diseño.
Las llamadas que interceptan al descriptor de acceso de propiedad de un componente se conocen como "sombreado". Permite a un diseñador realizar el seguimiento del valor establecido por el usuario y opcionalmente pasa ese valor al componente que se está diseñando.
En este ejemplo, las propiedades Visible y Enabled se sombrearán por MarqueeBorderDesigner
, que impide al usuario hacer invisible o desactivar el control MarqueeBorder
durante el tiempo de diseño.
Los diseñadores también pueden agregar y quitar propiedades. En este ejemplo, se quitará la propiedad Padding en tiempo de diseño, porque el control MarqueeBorder
establece mediante programación el relleno en función del tamaño de las luces especificado en la propiedad LightSize
.
La clase base para MarqueeBorderDesigner
es ComponentDesigner, que tiene métodos que pueden cambiar los atributos, propiedades y eventos expuestos por un control en tiempo de diseño:
Al cambiar la interfaz pública de un componente utilizando estos métodos, debe seguir las reglas siguientes:
Agregue o quite elementos únicamente en los métodos PreFilter
Modifique los elementos existentes únicamente en los métodos PostFilter
Siempre llame primero a la implementación base de los métodos PreFilter
Siempre llame primero a la implementación base de los métodos PostFilter
El cumplimiento de estas reglas garantiza que todos los diseñadores en entorno en tiempo de diseño poseen una vista coherente de todos los componentes que se están diseñando.
La clase ComponentDesigner proporciona un diccionario para administrar los valores de propiedades sombreadas, que libera de la necesidad de crear variables de instancia específicas.
Para crear a un diseñador personalizado para sombrear y filtrar propiedades
Haga clic con el botón secundario del mouse (ratón) en la carpeta Diseño y agregue una nueva clase. Dé el nombre base de "MarqueeBorderDesigner" al archivo de código fuente.
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:Imports System Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Windows.Forms Imports System.Windows.Forms.Design
using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Windows.Forms; using 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, que controla la interacción primaria-secundaria.Namespace MarqueeControlLibrary.Design <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _ Public Class MarqueeBorderDesigner Inherits ParentControlDesigner
namespace MarqueeControlLibrary.Design { [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] public class MarqueeBorderDesigner : ParentControlDesigner {
Reemplace la implementación base de PreFilterProperties.
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
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]); }
Implemente las propiedades Enabled y Visible. Estas implementaciones sombrean las propiedades del control.
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
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; } }
Controlar los cambios de componente
La clase MarqueeControlRootDesigner
proporciona experiencia en tiempo de diseño personalizada 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 determinadas: la administración de los cambios de componentes y la adición de verbos del diseñador.
Cuando los usuarios diseñan las instancias de MarqueeControl
, el diseñador raíz realizará el seguimiento de los cambios en MarqueeControl
y sus controles secundarios. El entorno en tiempo de diseño ofrece un práctico servicio, IComponentChangeService, para realizar el seguimiento de los cambios al estado de componente.
Adquiere una referencia a este servicio consultando el entorno con el método GetService. Si la consulta es satisfactoria, el diseñador puede adjuntar un controlador al evento ComponentChanged y realizar las tareas necesarias para conservar un estado coherente en tiempo de diseño.
En el caso de la clase MarqueeControlRootDesigner
, llame al método Refresh en cada objeto IMarqueeWidget
contenido por MarqueeControl
. Esto hará que el objeto IMarqueeWidget
se vuelva a representar correctamente cuando se cambien propiedades como la propiedad Size del elemento primario.
Para controlar los cambios de componente
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 del método Initialize y consulte IComponentChangeService.MyBase.Initialize(component) Dim cs As IComponentChangeService = _ CType(GetService(GetType(IComponentChangeService)), _ IComponentChangeService) If (cs IsNot Nothing) Then AddHandler cs.ComponentChanged, AddressOf OnComponentChanged End If
base.Initialize(component); IComponentChangeService cs = GetService(typeof(IComponentChangeService)) as IComponentChangeService; if (cs != null) { cs.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged); }
Implemente el controlador de eventos OnComponentChanged. Pruebe el tipo del componente enviado y si es
IMarqueeWidget
, llame al método 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
private void OnComponentChanged( object sender, ComponentChangedEventArgs e) { if (e.Component is IMarqueeWidget) { this.Control.Refresh(); } }
Agregar verbos del diseñador al diseñador personalizado
Un verbo del diseñador es un comando de menú vinculado a un controlador de eventos. Los verbos del diseñador se agregan en tiempo de diseño al menú contextual de un componente. Para obtener más información, vea DesignerVerb.
Agregará dos verbos del diseñador a los diseñadores: Ejecutar prueba y Detener prueba. Estos verbos le permitirán ver en tiempo de diseño el comportamiento en tiempo de ejecución de MarqueeControl
. Estos verbos se agregarán a MarqueeControlRootDesigner
.
Cuando se invoca Ejecutar prueba, el controlador de eventos del verbo llamará al método StartMarquee
en MarqueeControl
. Cuando se invoca Detener prueba, el controlador de eventos del verbo llamará al método StopMarquee
en MarqueeControl
. La implementación de los métodos StartMarquee
y StopMarquee
llama a estos métodos en controles contenidos que implementan IMarqueeWidget
, por tanto también participará en la prueba cualquier control IMarqueeWidget
contenido.
Para agregar verbos del diseñador a los diseñadores personalizados
En la clase
MarqueeControlRootDesigner
, agregue los controladores de eventos denominadosOnVerbRunTest
yOnVerbStopTest
.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
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(); }
Conecte estos controladores de eventos a sus verbos del diseñador correspondientes.
MarqueeControlRootDesigner
hereda un DesignerVerbCollection de su clase base. Creará dos nuevos objetos DesignerVerb y los agregará a esta colección en el método Initialize.Me.Verbs.Add(New DesignerVerb("Run Test", _ New EventHandler(AddressOf OnVerbRunTest))) Me.Verbs.Add(New DesignerVerb("Stop Test", _ New EventHandler(AddressOf OnVerbStopTest)))
this.Verbs.Add( new DesignerVerb("Run Test", new EventHandler(OnVerbRunTest)) ); this.Verbs.Add( new DesignerVerb("Stop Test", new EventHandler(OnVerbStopTest)) );
Crear un UITypeEditor personalizado
Cuando crea una experiencia en tiempo de diseño personalizada para los usuarios, se aconseja crear una interacción personalizada con la ventana Propiedades. Puede llevarlo a cabo creando un UITypeEditor. Para obtener más información, vea Cómo: Crear un editor de tipos de interfaz de usuario.
El control MarqueeBorder
expone varias propiedades en la ventana Propiedades. Dos de estas propiedades, MarqueeSpinDirection
y MarqueeLightShape
, se representan por enumeraciones. Para explicar el uso de un editor de tipos de la interfaz de usuario, la propiedad MarqueeLightShape
tendrá una clase UITypeEditor asociada.
Para crear un editor de tipos de la interfaz de usuario personalizada
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 llamadaLightShapeEditor
que deriva 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. Friend Class LightShapeEditor Inherits 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 {
Declare una variable de instancia IWindowsFormsEditorService denominada
editorService
.Private editorService As IWindowsFormsEditorService = Nothing
private IWindowsFormsEditorService editorService = null;
Reemplace el método GetEditStyle. Esta implementación devuelve DropDown, que indica al entorno de diseño cómo mostrar
LightShapeEditor
.Public Overrides Function GetEditStyle( _ ByVal context As System.ComponentModel.ITypeDescriptorContext) _ As UITypeEditorEditStyle Return UITypeEditorEditStyle.DropDown End Function
public override UITypeEditorEditStyle GetEditStyle( System.ComponentModel.ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; }
Reemplace el método EditValue. Esta implementación consulta en el entorno de diseño un objeto IWindowsFormsEditorService. Si es correcto, crea un
LightShapeSelectionControl
. El método DropDownControl se invoca para iniciarLightShapeEditor
. El valor devuelto de esta invocación se devuelve al entorno de diseño.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
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; }
Crear un control de vista para UITypeEditor personalizado
- La propiedad
MarqueeLightShape
admite dos tipos de formas para la iluminación:Square
yCircle
. Creará un control personalizado que se utilizará únicamente para mostrar gráficamente estos valores en la ventana Propiedades. UITypeEditor utilizará este control personalizado para interactuar con la ventana Propiedades.
Para crear un control de vista para el editor de tipos de la interfaz de usuario personalizado
Agregue un nuevo elemento UserControl al proyecto MarqueeControlLibrary. Dé el nombre base de "LightShapeSelectionControl" al nuevo archivo de código fuente.
Arrastre dos controles Panel desde el Cuadro de herramientas a
LightShapeSelectionControl
. DenomínelossquarePanel
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). Finalmente, 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 siguiente:
Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
Probar el control personalizado en el diseñador
Llegado a este punto, puede generar el proyecto MarqueeControlLibrary. Pruebe la implementación; para ello, cree un control que herede de la clase MarqueeControl
y utilícelo en un formulario.
Para crear una implementación de MarqueeControl personalizada
Abra
DemoMarqueeControl
en el Diseñador de Windows Forms. Esto creará una instancia del tipoDemoMarqueeControl
y la mostrará en una instancia del tipoMarqueeControlRootDesigner
.En el Cuadro de herramientas, abra la ficha Componentes de MarqueeControlLibrary. Verá los controles
MarqueeBorder
yMarqueeText
disponibles para la selección.Arrastre una instancia del control
MarqueeBorder
a la superficie de diseñoDemoMarqueeControl
. Acople este controlMarqueeBorder
al control principal.Arrastre una instancia del control
MarqueeText
a la superficie de diseñoDemoMarqueeControl
.Genere la solución.
Haga clic con el botón secundario en
DemoMarqueeControl
y seleccione en el menú contextual 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 de diseño.
Coloque dos controles Button en el formulario. Denomínelos
startButton
ystopButton
, y cambie los valores de la propiedad Text a Iniciar y Detener, respectivamente.Implemente los controladores de eventos Click para ambos controles Button.
En el Cuadro de herramientas, abra la ficha Componentes de MarqueeControlTest. Verá el
DemoMarqueeControl
disponible para la 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();
}
Pasos siguientes
MarqueeControlLibrary muestra una implementación simple de controles personalizados y de los diseñadores asociados. Hay varias formas de realizar este ejemplo más complejo:
Cambie los valores de la propiedad para
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 iluminación.Cree sus propias implementaciones de
IMarqueeWidget
. Por ejemplo, podría crear un "signo de neón" parpadeante o un signo animado con varias imágenes.Siga personalizando la experiencia en tiempo de diseño. Otra posibilidad es sombrear otras propiedades que no sean Enabled y Visible y agregar las nuevas propiedades. Agregue nuevos verbos del diseñador para simplificar las tareas comunes como el acoplamiento de controles secundarios.
Otorgue licencias
MarqueeControl
. Para obtener más información, vea Cómo: Obtener licencia para componentes y controles.Controle cómo se serializan los controles y cómo se genera el código para ellos. Para obtener más información, vea Generación y compilación dinámicas de código fuente.
Vea también
Tareas
Referencia
UserControl
ParentControlDesigner
DocumentDesigner
IRootDesigner
DesignerVerb
UITypeEditor
BackgroundWorker
Otros recursos
Ampliar compatibilidad en tiempo de diseño
Diseñadores personalizados
.NET Shape Library: A Sample Designer