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:

  • Creación del 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:

Posible organización de MarqueeControl

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 los valores de configuración o de edición activos. Para cambiar la configuración, elija la opción Importar y exportar configuraciones del menú Herramientas. Para obtener más información, vea Trabajar con valores de configuración.

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.

Creación del proyecto

El primer paso es crear el proyecto de aplicación. Este proyecto se utilizará para generar la aplicación que hospeda el control personalizado.

Para crear el proyecto

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

  1. Agregue un proyecto de Biblioteca de controles de Windows Forms a la solución. Dé al proyecto el nombre de "MarqueeControlLibrary".

  2. 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.

  3. Agregue un nuevo elemento UserControl al proyecto MarqueeControlLibrary. Dé el nombre base de "MarqueeControl" al nuevo archivo de código fuente.

  4. 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.

  5. Haga clic con el botón secundario del mouse en la carpeta Diseño y agregue una nueva clase. Dé el nombre base de "MarqueeControlRootDesigner" al archivo de código fuente.

  6. Necesitará utilizar los tipos del ensamblado System.Design, así que agregue esta referencia al proyecto MarqueeControlLibrary.

    Nota

    Para usar el ensamblado System.Design, el proyecto debe tener como destino la versión completa de .NET Framework, no .NET Framework Client Profile. Para cambiar la versión de .NET Framework de destino, vea Cómo: Elegir perfil o versión de destino de .NET Framework.

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

  1. 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;
    
  2. 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
        {
    
  3. 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;
    
  4. 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 el espacio de nombres "MarqueeControlLibrary.Design". Esta declaración coloca el 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
        {
    
  5. 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

  1. Agregue un nuevo elemento UserControl al proyecto MarqueeControlTest. Dé el nombre base de "DemoMarqueeControl" al nuevo archivo de código fuente.

  2. Abra el archivo DemoMarqueeControl en el Editor de código. En la parte superior del archivo, importe el espacio de nombres MarqueeControlLibrary:

Imports MarqueeControlLibrary
using MarqueeControlLibrary;
  1. Cambie la declaración de DemoMarqueeControl para heredar de la clase MarqueeControl.

  2. Compile el proyecto.

  3. Abra Form1 en el Diseñador de Windows Forms.

  4. Busque la ficha Componentes de MarqueeControlTest en el Cuadro de herramientas y ábrala. Arrastre un DemoMarqueeControl desde el Cuadro de herramientas al formulario.

  5. Compile el proyecto.

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

  1. Haga clic con el botón secundario del mouse en el proyecto MarqueeControlLibrary y seleccione Propiedades.

  2. En el cuadro de diálogo "Páginas de propiedades de MarqueeControlLibrary", seleccione la página Depurar.

  3. 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 (Captura de pantalla de VisualStudioEllipsesButton) 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, la ruta de acceso será %Archivos de programa%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.

  4. Haga clic en Aceptar para cerrar el cuadro de diálogo.

  5. 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

  1. 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.

  2. Presione F5 para iniciar la depuración. Observe que se crea una nueva instancia de Visual Studio.

  3. 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.

  4. 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

  1. Abra el archivo de código fuente MarqueeControl en el Editor de código. Implemente los métodos Start y Stop.

    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();
            }
        }
    }
    
  2. 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 hospedará 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 llamada UpdatePeriod. Varias propiedades personalizadas determinan otros aspectos de la apariencia del control. Dos métodos, llamados StartMarquee y StopMarquee, controlan cuándo se inicia o se detiene la animación.

  • MarqueeText: Este control representa una cadena parpadeante. Al igual que el control MarqueeBorder, la velocidad en la que el texto parpadea está controlada por la propiedad UpdatePeriod. El control MarqueeText también tiene en común los métodos StartMarquee y StopMarquee con el control MarqueeBorder.

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 detectar los controles secundarios relacionados con la marquesina y darles un tratamiento especial.

Para implementar la característica 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

  1. Agregue un nuevo elemento de clase al proyecto MarqueeControlLibrary. Dé el nombre base de "IMarqueeWidget" al nuevo archivo de código fuente.

  2. 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
    {
    
  3. 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;
        }
    }
    
  4. Agregue un nuevo elemento Control personalizado al proyecto MarqueeControlLibrary. Dé el nombre base de "MarqueeText" al nuevo archivo de código fuente.

  5. Arrastre un componente BackgroundWorker desde el Cuadro de herramientas hasta el control MarqueeText. Este componente permitirá al control MarqueeText actualizarse de forma asincrónica.

  6. 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).

  7. 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;
    
  8. Cambie la declaración de MarqueeText para heredar de Label e implementar la interfaz IMarqueeWidget:

    <ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeText
        Inherits Label
        Implements IMarqueeWidget
    
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    public partial class MarqueeText : Label, IMarqueeWidget
    {
    
  9. 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 propiedad LightColor.

    ' When isLit is true, the text is painted in the light color;
    ' When isLit is false, the text is painted in the dark color.
    ' This value changes whenever the BackgroundWorker component
    ' raises the ProgressChanged event.
    Private 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);
    }
    
  10. Implemente la interfaz IMarqueeWidget.

    Los métodos StartMarquee y StopMarquee 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");
            }
        }
    }
    
  11. Implemente los descriptores de acceso de propiedad. Expone dos propiedades a los clientes: LightColor y DarkColor. 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);
            }
        }
    }
    
  12. 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();
            }
    
    
  13. 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);
    }
    
  14. 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

  1. Agregue un nuevo elemento Control personalizado al proyecto MarqueeControlLibrary. Dé el nombre base de "MarqueeControl" al nuevo archivo de código fuente.

  2. Arrastre un componente BackgroundWorker desde el Cuadro de herramientas al control MarqueeBorder. Este componente permitirá al control MarqueeBorder actualizarse de forma asincrónica.

  3. 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).

  4. En la ventana Propiedades, haga clic en el botón Eventos. Asocie controladores para los eventos DoWork y ProgressChanged.

  5. 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;
    
  6. Cambie la declaración de MarqueeBorder para heredar de Panel e implementar la interfaz IMarqueeWidget:

    <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
    {
    
  7. 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, y MarqueeLightShape, que determina la forma de las luces (cuadradas o circulares). Coloque estas declaraciones antes de la declaración de clase MarqueeBorder.

    ' 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
    }
    
  8. 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);
    }
    
  9. Implemente la interfaz IMarqueeWidget.

    Los métodos StartMarquee y StopMarquee 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étodo StartMarquee enumera todos los controles secundarios y llama a StartMarquee en aquellos que implementa IMarqueeWidget. El método StopMarquee 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");
                    }
                }
            }
    
    
  10. 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;
                }
            }
    
    
  11. 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();
    }
    
  12. Implemente los métodos auxiliares IsLit y DrawLight.

    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 propiedad LightColor, y los que son "oscuros" se representan en el color proporcionado por la propiedad DarkColor.

    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;
                }
        }
    }
    
  13. 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 deshabilitar 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

  1. Haga clic con el botón secundario del mouse en la carpeta Diseño y agregue una nueva clase. Dé el nombre base de "MarqueeBorderDesigner" al archivo de código fuente.

  2. 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;
    
  3. 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
        {
    
  4. 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]);
    }
    
  5. 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 correcta, 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

  1. 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);
    }
    
  2. 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

  1. En la clase MarqueeControlRootDesigner, agregue los controladores de eventos denominados OnVerbRunTest y OnVerbStopTest.

    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();
    }
    
  2. Conecte estos controladores de eventos a los verbos de diseñador correspondientes. MarqueeControlRootDesigner hereda una colección 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

  1. Abra el archivo de código fuente MarqueeBorder en el Editor de código.

  2. En la definición de la clase MarqueeBorder, declare una clase llamada LightShapeEditor 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
    {
    
  3. Declare una variable de instancia IWindowsFormsEditorService denominada editorService.

    Private editorService As IWindowsFormsEditorService = Nothing
    
    private IWindowsFormsEditorService editorService = null;
    
  4. 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;
    }
    
  5. 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 iniciar LightShapeEditor. 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

  1. La propiedad MarqueeLightShape admite dos tipos de formas para la iluminación: Square y Circle. 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

  1. Agregue un nuevo elemento UserControl al proyecto MarqueeControlLibrary. Dé el nombre base de "LightShapeSelectionControl" al nuevo archivo de código fuente.

  2. Arrastre dos controles Panel desde el Cuadro de herramientas a LightShapeSelectionControl. Denomínelos squarePanel y circlePanel. Organícelos en paralelo. Establezca la propiedad Size de ambos controles Panel en (60, 60). Establezca la propiedad Location del control squarePanel en (8, 10). Establezca la propiedad Location del control circlePanel en (80, 10). Finalmente, establezca la propiedad Size de LightShapeSelectionControl en (150, 80).

  3. Abra el archivo de código fuente de 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;
  1. Implemente los controladores de eventos Click de los controles squarePanel y circlePanel. Estos métodos invocan CloseDropDown para finalizar la sesión de edición de UITypeEditor personalizada.

    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
    
            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();
            }
    
  2. Declare una variable de instancia IWindowsFormsEditorService denominada editorService.

Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
  1. Declare una variable de instancia MarqueeLightShape denominada lightShapeValue.

    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
  2. En el constructor LightShapeSelectionControl, asocie los controladores de eventos Click a los eventos Click de los controles squarePanel y circlePanel. Asimismo, defina una sobrecarga del constructor que asigne el valor de MarqueeLightShape del entorno de diseño al campo lightShapeValue.

    ' 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
    
            // 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);
            }
    
  3. En el método Dispose, desasocie los controladores de eventos Click.

    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
    
            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 );
            }
    
  4. 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.

  5. Implemente la propiedad LightShape.

    ' 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
    
            // 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;
                    }
                }
            }
    
  6. Invalide el método OnPaint. Esta implementación dibujará un cuadrado y un círculo rellenos. Resaltará también el valor seleccionado dibujando un borde alrededor de alguna de las dos formas.

    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
    
            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);
                    }
                }   
            }
    

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

  1. Abra DemoMarqueeControl en el Diseñador de Windows Forms. Esto creará una instancia del tipo DemoMarqueeControl y la mostrará en una instancia del tipo MarqueeControlRootDesigner.

  2. En el Cuadro de herramientas, abra la ficha Componentes de MarqueeControlLibrary. Verá los controles MarqueeBorder y MarqueeText disponibles para la selección.

  3. Arrastre una instancia del control MarqueeBorder a la superficie de diseño DemoMarqueeControl. Acople este control MarqueeBorder al control principal.

  4. Arrastre una instancia del control MarqueeText a la superficie de diseño DemoMarqueeControl.

  5. Genere la solución.

  6. 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.

  7. Abra Form1 en la vista de diseño.

  8. Coloque dos controles Button en el formulario. Asígneles los nombres startButton y stopButton, y cambia el valor de su propiedad Text a Iniciar y Detener, respectivamente.

  9. Implemente los controladores de eventos Click para ambos controles Button.

  10. En el Cuadro de herramientas, abra la ficha Componentes de MarqueeControlTest. Verá el DemoMarqueeControl disponible para la selección.

  11. Arrastre una instancia de DemoMarqueeControl a la superficie de diseño de Form1.

  12. En los controladores de eventos Click, invoque los métodos Start y Stop en DemoMarqueeControl.

Private Sub startButton_Click(sender As Object, e As System.EventArgs)
    Me.demoMarqueeControl1.Start()
End Sub 'startButton_Click

Private Sub stopButton_Click(sender As Object, e As System.EventArgs)
Me.demoMarqueeControl1.Stop()
End Sub 'stopButton_Click
private void startButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Start();
}

private void stopButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Stop();
}
  1. Establezca el proyecto MarqueeControlTest como el proyecto de inicio y ejecútelo. Verá que en el formulario se muestra DemoMarqueeControl. Haga clic en el botón Iniciar para iniciar la animación. Debe ver que el texto parpadea y que las luces se mueven por el borde.

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 controles MarqueBorder y acóplelos dentro de sus instancias primarias para crear un efecto anidado. Experimente con distintas configuraciones para UpdatePeriod 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

Cómo: Crear un control de formularios Windows Forms que aproveche las características en tiempo de diseño

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

Historial de cambios

Fecha

Historial

Motivo

Julio de 2010

Se ha agregado una nota sobre .NET Framework Client Profile.

Comentarios de los clientes.