Compartir a través de


Tutorial: Crear un control que aproveche las características en tiempo de diseño

La experiencia en tiempo de diseño de un control personalizado se puede mejorar mediante la creación de un diseñador personalizado asociado.

Precaución

Este contenido se escribió para .NET Framework. Si usa .NET 6 o una versión posterior, use este contenido con precaución. El sistema de diseñador ha cambiado para Windows Forms y es importante que revise los cambios del diseñador desde el artículo de .NET Framework .

En este artículo se muestra cómo crear un diseñador personalizado para un control personalizado. Implementará un MarqueeControl tipo y una clase de diseñador asociada denominada MarqueeControlRootDesigner.

El MarqueeControl tipo implementa una pantalla similar a una marquesina de teatro con luces animadas y texto parpadeante.

El diseñador de este control interactúa con el entorno de diseño para proporcionar una experiencia personalizada en tiempo de diseño. Con el diseñador personalizado, puede ensamblar una implementación personalizada MarqueeControl con luces animadas y texto parpadeante en muchas combinaciones. Puede usar el control ensamblado en un formulario como cualquier otro control de Windows Forms.

Cuando haya terminado con este tutorial, el control personalizado tendrá un aspecto similar al siguiente:

La aplicación muestra una marca que dice Texto y los botones Iniciar y Detener.

Para obtener la lista de código completa, vea Cómo crear un control de Windows Forms que aproveche las características de Design-Time.

Prerrequisitos

Para completar este tutorial, necesitará Visual Studio.

Creación del proyecto

El primer paso es crear el proyecto de aplicación. Usará este proyecto para compilar la aplicación que hospeda el control personalizado.

En Visual Studio, cree un nuevo proyecto de aplicación de Windows Forms y asígneles el nombre MarqueeControlTest.

Creación del proyecto de biblioteca de controles

  1. Agregue un proyecto de biblioteca de controles de Windows Forms a la solución. Asigne al proyecto el nombre 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", en función del idioma que prefiera.

  3. Agregue un nuevo UserControl elemento al MarqueeControlLibrary proyecto. Asigne al nuevo archivo de origen un nombre base de MarqueeControl.

  4. Con el Explorador de soluciones, cree una carpeta en el MarqueeControlLibrary proyecto.

  5. Haga clic con el botón derecho en la carpeta Diseño y agregue una nueva clase. Asígne el nombre MarqueeControlRootDesigner.

  6. Tendrá que usar tipos del ensamblado de System.Design, por lo que debe agregar esta referencia al MarqueeControlLibrary proyecto.

Hacer referencia al proyecto de control personalizado

Usará el MarqueeControlTest proyecto para probar el control personalizado. El proyecto de prueba reconocerá el control personalizado al agregar una referencia de proyecto al MarqueeControlLibrary ensamblaje.

En el proyecto MarqueeControlTest, agregue una referencia de proyecto al ensamblaje MarqueeControlLibrary. Asegúrese de usar la pestaña Proyectos en el cuadro de diálogo Agregar referencia en lugar de hacer referencia al MarqueeControlLibrary ensamblado directamente.

Definir un control personalizado y su diseñador personalizado

El control personalizado se derivará de la UserControl clase . Esto permite que el control contenga otros controles y proporciona a su control una gran cantidad de funcionalidad predeterminada.

El control personalizado tendrá un diseñador personalizado asociado. Esto le permite crear una experiencia de diseño única adaptada específicamente para su control personalizado.

El control se asocia con su diseñador mediante la DesignerAttribute clase . Dado que está desarrollando todo el comportamiento durante el tiempo de diseño de su control personalizado, el diseñador del control personalizado implementará la interfaz IRootDesigner.

Para definir un control personalizado y su diseñador personalizado

  1. Abra el MarqueeControl archivo de código fuente en el Editor de código. En la parte superior del fichero, importe los siguientes namespaces:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  2. Agregue DesignerAttribute a la declaración de clase MarqueeControl. Esto asocia el control personalizado a su diseñador.

    [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
    public class MarqueeControl : UserControl
    {
    
    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
  3. Abra el MarqueeControlRootDesigner archivo de código fuente en el Editor de código. En la parte superior del fichero, importe los siguientes namespaces:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing.Design;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing.Design
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  4. Cambie la declaración de MarqueeControlRootDesigner para que herede de la clase DocumentDesigner. Utilice el ToolboxItemFilterAttribute para especificar la interacción del diseñador con el Cuadro de herramientas.

    Nota:

    La definición de la MarqueeControlRootDesigner clase se ha incluido en un espacio de nombres denominado MarqueeControlLibrary.Design. Esta declaración coloca el diseñador en un espacio de nombres especial reservado para tipos relacionados con el diseño.

    namespace MarqueeControlLibrary.Design
    {
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
        public class MarqueeControlRootDesigner : DocumentDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
        ToolboxItemFilterType.Require), _
        ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
        ToolboxItemFilterType.Require)> _
        Public Class MarqueeControlRootDesigner
            Inherits DocumentDesigner
    
  5. Defina el constructor para la MarqueeControlRootDesigner clase . Inserte una WriteLine instrucción en el cuerpo del constructor. Esto será útil para la depuración.

    public MarqueeControlRootDesigner()
    {
        Trace.WriteLine("MarqueeControlRootDesigner ctor");
    }
    
    Public Sub New()
        Trace.WriteLine("MarqueeControlRootDesigner ctor")
    End Sub
    

Creación de una instancia del control personalizado

  1. Agregue un nuevo UserControl elemento al MarqueeControlTest proyecto. Asigne al nuevo archivo de origen un nombre base de DemoMarqueeControl.

  2. Abra el DemoMarqueeControl archivo en el Editor de código. En la parte superior del archivo, importa el MarqueeControlLibrary namespace.

    Imports MarqueeControlLibrary
    
    using MarqueeControlLibrary;
    
  3. Cambie la declaración de DemoMarqueeControl para que herede de la clase MarqueeControl.

  4. Compile el proyecto.

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

  6. Busque la pestaña MarqueeControlTest Components (Componentes de MarqueeControlTest ) en el Cuadro de herramientas y ábralo. Arrastre un DemoMarqueeControl elemento desde el Cuadro de herramientas hasta el formulario.

  7. Compile el proyecto.

Configurar el proyecto para la depuración de Design-Time

Al desarrollar una experiencia personalizada en tiempo de diseño, será necesario depurar los controles y los componentes. Hay una manera sencilla de configurar su proyecto para permitir la depuración durante el tiempo de diseño. Para obtener más información, vea Tutorial: Depuración de controles personalizados de Formularios Windows Forms en tiempo de diseño.

  1. Haga clic con el botón derecho en el MarqueeControlLibrary proyecto 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 Iniciar acción , seleccione Iniciar programa externo. Va a depurar una instancia independiente de Visual Studio, por lo que debe hacer clic en los puntos suspensivos (el botón de puntos suspensivos (...) en la ventana de propiedades de Visual Studio) para buscar el IDE de Visual Studio. El nombre del archivo ejecutable es devenv.exey, si instaló en la ubicación predeterminada, su ruta de acceso es %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.

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

  5. Haga clic con el botón derecho en el proyecto MarqueeControlLibrary y seleccione Establecer como proyecto de inicio para habilitar esta configuración de depuración.

Punto de control

Ya estás listo para depurar el comportamiento durante el diseño de tu control personalizado. Una vez que haya verificado que el entorno de depuración está correctamente configurado, procederá a probar la vinculación entre el control personalizado y el diseñador personalizado.

Para probar el entorno de depuración y la colaboració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 WriteLine instrucción .

  2. Presione F5 para iniciar la sesión de depuración.

    Se crea una nueva instancia de Visual Studio.

  3. En la nueva instancia de Visual Studio, abra la solución MarqueeControlTest. Para encontrar fácilmente la solución, seleccione Proyectos recientes en el menú Archivo . El archivo de solución MarqueeControlTest.sln se mostrará como el archivo usado más recientemente.

  4. Abra el DemoMarqueeControl en el diseñador.

    La instancia de depuración de Visual Studio adquiere el enfoque y la ejecución se detiene en el punto de interrupción. Presione F5 para continuar con la sesión de depuración.

En este punto, todo está preparado para que desarrolles y depures tu control personalizado y su diseñador personalizado asociado. El resto de este artículo se centra en los detalles de la implementación de las características del control y el diseñador.

Implementar el control personalizado

MarqueeControl es un UserControl con un poco de personalización. Expone dos métodos: Start, que inicia la animación de marca y Stop, que detiene la animación. Dado que 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 lo que MarqueeControl invalida el método OnLayout y llama a PerformLayout en los controles secundarios de este tipo.

Este es el alcance de las MarqueeControl personalizaciones. Las características en tiempo de ejecución se implementan mediante los MarqueeBorder controles y MarqueeText , y las características en tiempo de diseño se implementan mediante las MarqueeBorderDesigner clases y MarqueeControlRootDesigner .

Para implementar el control personalizado

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

    public void Start()
    {
        // The MarqueeControl may contain any number of
        // controls that implement IMarqueeWidget, so
        // find each IMarqueeWidget child and call its
        // StartMarquee method.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StartMarquee();
            }
        }
    }
    
    public void Stop()
    {
        // The MarqueeControl may contain any number of
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StopMarquee
        // method.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StopMarquee();
            }
        }
    }
    
    Public Sub Start()
        ' The MarqueeControl may contain any number of 
        ' controls that implement IMarqueeWidget, so 
        ' find each IMarqueeWidget child and call its
        ' StartMarquee method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StartMarquee()
            End If
        Next cntrl
    End Sub
    
    
    Public Sub [Stop]()
        ' The MarqueeControl may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StopMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StopMarquee()
            End If
        Next cntrl
    End Sub
    
  2. Invalide el OnLayout método .

    protected override void OnLayout(LayoutEventArgs levent)
    {
        base.OnLayout (levent);
    
        // Repaint all IMarqueeWidget children if the layout
        // has changed.
        foreach( Control cntrl in this.Controls )
        {
            if( cntrl is IMarqueeWidget )
            {
                Control control = cntrl as Control;
    
                control.PerformLayout();
            }
        }
    }
    
    Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs)
        MyBase.OnLayout(levent)
    
        ' Repaint all IMarqueeWidget children if the layout 
        ' has changed.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                cntrl.PerformLayout()
            End If
        Next cntrl
    End Sub
    

Crear un control secundario para el control personalizado

El MarqueeControl alojará dos tipos de controles secundarios: el control MarqueeBorder y el control MarqueeText.

  • MarqueeBorder: este control pinta un borde de "luces" alrededor de sus bordes. Las luces parpadean en secuencia, por lo que parecen estar moviéndose alrededor del borde. La velocidad a la que las luces parpadean se controla mediante una propiedad denominada UpdatePeriod. Otras propiedades personalizadas determinan otros aspectos de la apariencia del control. Dos métodos, llamados StartMarquee y StopMarquee, controlan cuando la animación se inicia y se detiene.

  • MarqueeText: este control pinta una cadena parpadeante. Al igual que el MarqueeBorder control , la velocidad a la que el texto parpadea se controla mediante la UpdatePeriod propiedad . El control MarqueeText también tiene los métodos StartMarquee y StopMarquee en común con el control MarqueeBorder.

En tiempo de diseño, MarqueeControlRootDesigner permite que estos dos tipos de control se agreguen a una MarqueeControl en cualquier combinación.

Las características comunes de los dos controles se factorizarán en una interfaz denominada IMarqueeWidget. Esto permite al MarqueeControl detectar cualquier control secundario relacionado con el elemento 'Marquee' y ofrecerles un tratamiento especial.

Para implementar la característica de animación periódica, utilizará BackgroundWorker objetos del espacio de nombres System.ComponentModel. Puede usar Timer objetos, pero cuando hay muchos IMarqueeWidget objetos presentes, es posible que el subproceso de interfaz de usuario único no pueda mantenerse al día con la animación.

Para crear un control secundario para el control personalizado

  1. Agregue un nuevo elemento de clase al MarqueeControlLibrary proyecto. Asigne al nuevo archivo de origen un nombre base de "IMarqueeWidget".

  2. Abra el IMarqueeWidget archivo de código fuente 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 IMarqueeWidget interfaz para exponer dos métodos y una propiedad que manipule la animación de marquesina:

    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
        // This method starts the animation. If the control can
        // contain other classes that implement IMarqueeWidget as
        // children, the control should call StartMarquee on all
        // its IMarqueeWidget child controls.
        void StartMarquee();
    
        // This method stops the animation. If the control can
        // contain other classes that implement IMarqueeWidget as
        // children, the control should call StopMarquee on all
        // its IMarqueeWidget child controls.
        void StopMarquee();
    
        // This method specifies the refresh rate for the animation,
        // in milliseconds.
        int UpdatePeriod
        {
            get;
            set;
        }
    }
    
    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
       ' This method starts the animation. If the control can 
       ' contain other classes that implement IMarqueeWidget as
       ' children, the control should call StartMarquee on all
       ' its IMarqueeWidget child controls.
       Sub StartMarquee()
       
       ' This method stops the animation. If the control can 
       ' contain other classes that implement IMarqueeWidget as
       ' children, the control should call StopMarquee on all
       ' its IMarqueeWidget child controls.
       Sub StopMarquee()
       
       ' This method specifies the refresh rate for the animation,
       ' in milliseconds.
       Property UpdatePeriod() As Integer
    
    End Interface
    
  4. Agregue un nuevo elemento Control Personalizado al proyecto MarqueeControlLibrary. Asigne al nuevo archivo de origen un nombre base de "MarqueeText".

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

  6. En la ventana Propiedades, establezca las propiedades BackgroundWorker y WorkerReportsProgress del componente WorkerSupportsCancellation en verdadero. Esta configuración permite que el BackgroundWorker componente genere periódicamente el ProgressChanged evento y cancele las actualizaciones asincrónicas.

    Para obtener más información, vea BackgroundWorker Component.

  7. Abra el MarqueeText archivo de código fuente en el Editor de código. En la parte superior del fichero, importe los siguientes namespaces:

    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  8. Cambie la declaración para que herede de MarqueeText e implemente la interfaz Label.

    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    public partial class MarqueeText : Label, IMarqueeWidget
    {
    
    <ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeText
        Inherits Label
        Implements IMarqueeWidget
    
  9. Declare las variables de instancia que corresponden a las propiedades expuestas e inicialícelas en el constructor. El isLit campo determina si el texto se va a pintar en el color proporcionado por la LightColor propiedad .

    // When isLit is true, the text is painted in the light color;
    // When isLit is false, the text is painted in the dark color.
    // This value changes whenever the BackgroundWorker component
    // raises the ProgressChanged event.
    private bool isLit = true;
    
    // These fields back the public properties.
    private int updatePeriodValue = 50;
    private Color lightColorValue;
    private Color darkColorValue;
    
    // These brushes are used to paint the light and dark
    // colors of the text.
    private Brush lightBrush;
    private Brush darkBrush;
    
    // This component updates the control asynchronously.
    private BackgroundWorker backgroundWorker1;
    
    public MarqueeText()
    {
        // This call is required by the Windows.Forms Form Designer.
        InitializeComponent();
    
        // Initialize light and dark colors
        // to the control's default values.
        this.lightColorValue = this.ForeColor;
        this.darkColorValue = this.BackColor;
        this.lightBrush = new SolidBrush(this.lightColorValue);
        this.darkBrush = new SolidBrush(this.darkColorValue);
    }
    
    ' When isLit is true, the text is painted in the light color;
    ' When isLit is false, the text is painted in the dark color.
    ' This value changes whenever the BackgroundWorker component
    ' raises the ProgressChanged event.
    Private isLit As Boolean = True
    
    ' These fields back the public properties.
    Private updatePeriodValue As Integer = 50
    Private lightColorValue As Color
    Private darkColorValue As Color
    
    ' These brushes are used to paint the light and dark
    ' colors of the text.
    Private lightBrush As Brush
    Private darkBrush As Brush
    
    ' This component updates the control asynchronously.
    Private WithEvents backgroundWorker1 As BackgroundWorker
    
    
    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()
    
        ' Initialize light and dark colors 
        ' to the control's default values.
        Me.lightColorValue = Me.ForeColor
        Me.darkColorValue = Me.BackColor
        Me.lightBrush = New SolidBrush(Me.lightColorValue)
        Me.darkBrush = New SolidBrush(Me.darkColorValue)
    End Sub
    
  10. Implemente la IMarqueeWidget interfaz.

    Los métodos StartMarquee y StopMarquee invocan los métodos BackgroundWorker y RunWorkerAsync del componente CancelAsync para iniciar y detener la animación.

    Los Category atributos y Browsable se aplican a la UpdatePeriod propiedad, por lo que aparece en una sección personalizada de la ventana Propiedades denominada "Marquee".

    public virtual void StartMarquee()
    {
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' Start the updating thread and pass it the UpdatePeriod.
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub
    
    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' Stop the updating thread.
        Me.backgroundWorker1.CancelAsync()
    End Sub
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod
    
        Get
            Return Me.updatePeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.updatePeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0")
            End If
        End Set
    
    End Property
    
  11. Implemente los descriptores de acceso de propiedad. Expondrá dos propiedades a los clientes: LightColor y DarkColor. Los Category atributos y Browsable se aplican a estas propiedades, por lo que las propiedades aparecen en una sección personalizada de la ventana Propiedades denominada "Marquee".

    [Category("Marquee")]
    [Browsable(true)]
    public Color LightColor
    {
        get
        {
            return this.lightColorValue;
        }
        set
        {
            // The LightColor property is only changed if the
            // client provides a different value. Comparing values
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.lightColorValue.ToArgb() != value.ToArgb())
            {
                this.lightColorValue = value;
                this.lightBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color DarkColor
    {
        get
        {
            return this.darkColorValue;
        }
        set
        {
            // The DarkColor property is only changed if the
            // client provides a different value. Comparing values
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.darkColorValue.ToArgb() != value.ToArgb())
            {
                this.darkColorValue = value;
                this.darkBrush = new SolidBrush(value);
            }
        }
    }
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color
    
        Get
            Return Me.lightColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If
        End Set
    
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color
    
        Get
            Return Me.darkColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If
        End Set
    
    End Property
    
  12. Implemente los controladores para los eventos BackgroundWorker y DoWork del componente ProgressChanged.

    El DoWork controlador de eventos espera durante el número de milisegundos especificados por UpdatePeriod y luego genera el evento ProgressChanged, hasta que tu código detenga la animación llamando a CancelAsync.

    El ProgressChanged controlador de eventos alterna el texto entre su estado claro y oscuro para dar la apariencia de parpadear.

    // This method is called in the worker thread's context,
    // so it must not make any calls into the MarqueeText control.
    // Instead, it communicates to the control using the
    // ProgressChanged event.
    //
    // The only work done in this event handler is
    // to sleep for the number of milliseconds specified
    // by UpdatePeriod, then raise the ProgressChanged event.
    private void backgroundWorker1_DoWork(
        object sender,
        System.ComponentModel.DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
    
        // This event handler will run until the client cancels
        // the background task by calling CancelAsync.
        while (!worker.CancellationPending)
        {
            // The Argument property of the DoWorkEventArgs
            // object holds the value of UpdatePeriod, which
            // was passed as the argument to the RunWorkerAsync
            // method.
            Thread.Sleep((int)e.Argument);
    
            // The DoWork eventhandler does not actually report
            // progress; the ReportProgress event is used to
            // periodically alert the control to update its state.
            worker.ReportProgress(0);
        }
    }
    
    // The ProgressChanged event is raised by the DoWork method.
    // This event handler does work that is internal to the
    // control. In this case, the text is toggled between its
    // light and dark state, and the control is told to
    // repaint itself.
    private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        this.isLit = !this.isLit;
        this.Refresh();
    }
    
    
    ' This method is called in the worker thread's context, 
    ' so it must not make any calls into the MarqueeText control.
    ' Instead, it communicates to the control using the 
    ' ProgressChanged event.
    '
    ' The only work done in this event handler is
    ' to sleep for the number of milliseconds specified 
    ' by UpdatePeriod, then raise the ProgressChanged event.
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
        ' This event handler will run until the client cancels
        ' the background task by calling CancelAsync.
        While Not worker.CancellationPending
            ' The Argument property of the DoWorkEventArgs
            ' object holds the value of UpdatePeriod, which 
            ' was passed as the argument to the RunWorkerAsync
            ' method. 
            Thread.Sleep(Fix(e.Argument))
    
            ' The DoWork eventhandler does not actually report
            ' progress; the ReportProgress event is used to 
            ' periodically alert the control to update its state.
            worker.ReportProgress(0)
        End While
    End Sub
    
    
    ' The ProgressChanged event is raised by the DoWork method.
    ' This event handler does work that is internal to the
    ' control. In this case, the text is toggled between its
    ' light and dark state, and the control is told to 
    ' repaint itself.
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
        Me.isLit = Not Me.isLit
        Me.Refresh()
    End Sub
    
  13. Invalide el OnPaint método para habilitar la animación.

    protected override void OnPaint(PaintEventArgs e)
    {
        // The text is painted in the light or dark color,
        // depending on the current value of isLit.
        this.ForeColor =
            this.isLit ? this.lightColorValue : this.darkColorValue;
    
        base.OnPaint(e);
    }
    
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        ' The text is painted in the light or dark color,
        ' depending on the current value of isLit.
        Me.ForeColor = IIf(Me.isLit, Me.lightColorValue, Me.darkColorValue)
    
        MyBase.OnPaint(e)
    End Sub
    
  14. Presione F6 para compilar la solución.

Creación del control secundario MarqueeBorder

El MarqueeBorder control es ligeramente más sofisticado que el MarqueeText control. Tiene más propiedades y la animación en el OnPaint método está más implicada. En principio, es bastante similar al MarqueeText control.

Dado que el MarqueeBorder control puede tener controles secundarios, debe ser consciente de los eventos de Layout.

Para crear el control MarqueeBorder

  1. Agregue un nuevo elemento Control Personalizado al proyecto MarqueeControlLibrary. Asigne al nuevo archivo de origen un nombre base de "MarqueeBorder".

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

  3. En la ventana Propiedades, establezca las propiedades BackgroundWorker y WorkerReportsProgress del componente WorkerSupportsCancellation en verdadero. Esta configuración permite que el BackgroundWorker componente genere periódicamente el ProgressChanged evento y cancele las actualizaciones asincrónicas. Para obtener más información, vea BackgroundWorker Component.

  4. En la ventana Propiedades , seleccione el botón Eventos . Adjunte controladores para los eventos DoWork y ProgressChanged.

  5. Abra el MarqueeBorder archivo de código fuente en el Editor de código. En la parte superior del fichero, importe los siguientes namespaces:

    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Drawing.Design
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  6. Cambie la declaración de MarqueeBorder para heredar de Panel e implementar la interfaz IMarqueeWidget.

    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {
    
    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _
    ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeBorder
        Inherits Panel
        Implements IMarqueeWidget
    
  7. Declare dos enumeraciones para administrar el MarqueeBorder estado del control: MarqueeSpinDirection, que determina la dirección en la que las luces "giran" 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
    }
    
    // This defines the possible values for the MarqueeBorder
    // control's LightShape property.
    public enum MarqueeLightShape
    {
        Square,
        Circle
    }
    
    ' This defines the possible values for the MarqueeBorder
    ' control's SpinDirection property.
    Public Enum MarqueeSpinDirection
       CW
       CCW
    End Enum
    
    ' This defines the possible values for the MarqueeBorder
    ' control's LightShape property.
    Public Enum MarqueeLightShape
        Square
        Circle
    End Enum
    
  8. Declare las variables de instancia que corresponden a las propiedades expuestas e inicialícelas en el constructor.

    public static int MaxLightSize = 10;
    
    // These fields back the public properties.
    private int updatePeriodValue = 50;
    private int lightSizeValue = 5;
    private int lightPeriodValue = 3;
    private int lightSpacingValue = 1;
    private Color lightColorValue;
    private Color darkColorValue;
    private MarqueeSpinDirection spinDirectionValue = MarqueeSpinDirection.CW;
    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    // These brushes are used to paint the light and dark
    // colors of the marquee lights.
    private Brush lightBrush;
    private Brush darkBrush;
    
    // This field tracks the progress of the "first" light as it
    // "travels" around the marquee border.
    private int currentOffset = 0;
    
    // This component updates the control asynchronously.
    private System.ComponentModel.BackgroundWorker backgroundWorker1;
    
    public MarqueeBorder()
    {
        // This call is required by the Windows.Forms Form Designer.
        InitializeComponent();
    
        // Initialize light and dark colors
        // to the control's default values.
        this.lightColorValue = this.ForeColor;
        this.darkColorValue = this.BackColor;
        this.lightBrush = new SolidBrush(this.lightColorValue);
        this.darkBrush = new SolidBrush(this.darkColorValue);
    
        // The MarqueeBorder control manages its own padding,
        // because it requires that any contained controls do
        // not overlap any of the marquee lights.
        int pad = 2 * (this.lightSizeValue + this.lightSpacingValue);
        this.Padding = new Padding(pad, pad, pad, pad);
    
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    }
    
    Public Shared MaxLightSize As Integer = 10
    
    ' These fields back the public properties.
    Private updatePeriodValue As Integer = 50
    Private lightSizeValue As Integer = 5
    Private lightPeriodValue As Integer = 3
    Private lightSpacingValue As Integer = 1
    Private lightColorValue As Color
    Private darkColorValue As Color
    Private spinDirectionValue As MarqueeSpinDirection = MarqueeSpinDirection.CW
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
    ' These brushes are used to paint the light and dark
    ' colors of the marquee lights.
    Private lightBrush As Brush
    Private darkBrush As Brush
    
    ' This field tracks the progress of the "first" light as it
    ' "travels" around the marquee border.
    Private currentOffset As Integer = 0
    
    ' This component updates the control asynchronously.
    Private WithEvents backgroundWorker1 As System.ComponentModel.BackgroundWorker
    
    
    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()
    
        ' Initialize light and dark colors 
        ' to the control's default values.
        Me.lightColorValue = Me.ForeColor
        Me.darkColorValue = Me.BackColor
        Me.lightBrush = New SolidBrush(Me.lightColorValue)
        Me.darkBrush = New SolidBrush(Me.darkColorValue)
    
        ' The MarqueeBorder control manages its own padding,
        ' because it requires that any contained controls do
        ' not overlap any of the marquee lights.
        Dim pad As Integer = 2 * (Me.lightSizeValue + Me.lightSpacingValue)
        Me.Padding = New Padding(pad, pad, pad, pad)
    
        SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
    End Sub
    
  9. Implemente la IMarqueeWidget interfaz.

    Los métodos StartMarquee y StopMarquee invocan los métodos BackgroundWorker y RunWorkerAsync del componente CancelAsync para iniciar y detener la animación.

    Dado que el MarqueeBorder control puede contener controles secundarios, el StartMarquee método enumera todos los controles secundarios y llama a StartMarquee en los que implementan IMarqueeWidget. El StopMarquee método tiene una implementación similar.

    public virtual void StartMarquee()
    {
        // The MarqueeBorder control may contain any number of
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StartMarquee
        // method.
        foreach (Control cntrl in this.Controls)
        {
            if (cntrl is IMarqueeWidget)
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StartMarquee();
            }
        }
    
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // The MarqueeBorder control may contain any number of
        // controls that implement IMarqueeWidget, so find
        // each IMarqueeWidget child and call its StopMarquee
        // method.
        foreach (Control cntrl in this.Controls)
        {
            if (cntrl is IMarqueeWidget)
            {
                IMarqueeWidget widget = cntrl as IMarqueeWidget;
                widget.StopMarquee();
            }
        }
    
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public virtual int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
    
    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' The MarqueeBorder control may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StartMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StartMarquee()
            End If
        Next cntrl
    
        ' Start the updating thread and pass it the UpdatePeriod.
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub
    
    
    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' The MarqueeBorder control may contain any number of 
        ' controls that implement IMarqueeWidget, so find
        ' each IMarqueeWidget child and call its StopMarquee
        ' method.
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)
    
                widget.StopMarquee()
            End If
        Next cntrl
    
        ' Stop the updating thread.
        Me.backgroundWorker1.CancelAsync()
    End Sub
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Overridable Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod
    
        Get
            Return Me.updatePeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.updatePeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("UpdatePeriod", _
                "must be > 0")
            End If
        End Set
    
    End Property
    
  10. Implemente los descriptores de acceso de propiedad. El MarqueeBorder control tiene varias propiedades para controlar su apariencia.

    [Category("Marquee")]
    [Browsable(true)]
    public int LightSize
    {
        get
        {
            return this.lightSizeValue;
        }
    
        set
        {
            if (value > 0 && value <= MaxLightSize)
            {
                this.lightSizeValue = value;
                this.DockPadding.All = 2 * value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightSize", "must be > 0 and < MaxLightSize");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int LightPeriod
    {
        get
        {
            return this.lightPeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.lightPeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightPeriod", "must be > 0 ");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color LightColor
    {
        get
        {
            return this.lightColorValue;
        }
    
        set
        {
            // The LightColor property is only changed if the
            // client provides a different value. Comparing values
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.lightColorValue.ToArgb() != value.ToArgb())
            {
                this.lightColorValue = value;
                this.lightBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public Color DarkColor
    {
        get
        {
            return this.darkColorValue;
        }
    
        set
        {
            // The DarkColor property is only changed if the
            // client provides a different value. Comparing values
            // from the ToArgb method is the recommended test for
            // equality between Color structs.
            if (this.darkColorValue.ToArgb() != value.ToArgb())
            {
                this.darkColorValue = value;
                this.darkBrush = new SolidBrush(value);
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int LightSpacing
    {
        get
        {
            return this.lightSpacingValue;
        }
    
        set
        {
            if (value >= 0)
            {
                this.lightSpacingValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("LightSpacing", "must be >= 0");
            }
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    [EditorAttribute(typeof(LightShapeEditor),
         typeof(System.Drawing.Design.UITypeEditor))]
    public MarqueeLightShape LightShape
    {
        get
        {
            return this.lightShapeValue;
        }
    
        set
        {
            this.lightShapeValue = value;
        }
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public MarqueeSpinDirection SpinDirection
    {
        get
        {
            return this.spinDirectionValue;
        }
    
        set
        {
            this.spinDirectionValue = value;
        }
    }
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightSize() As Integer
        Get
            Return Me.lightSizeValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 AndAlso Value <= MaxLightSize Then
                Me.lightSizeValue = Value
                Me.DockPadding.All = 2 * Value
            Else
                Throw New ArgumentOutOfRangeException("LightSize", _
                "must be > 0 and < MaxLightSize")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightPeriod() As Integer
        Get
            Return Me.lightPeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.lightPeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("LightPeriod", _
                "must be > 0 ")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color
        Get
            Return Me.lightColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color
        Get
            Return Me.darkColorValue
        End Get
    
        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the 
            ' client provides a different value. Comparing values 
            ' from the ToArgb method is the recommended test for
            ' equality between Color structs.
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property LightSpacing() As Integer
        Get
            Return Me.lightSpacingValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value >= 0 Then
                Me.lightSpacingValue = Value
            Else
                Throw New ArgumentOutOfRangeException("LightSpacing", _
                "must be >= 0")
            End If
        End Set
    End Property
    
    
    <Category("Marquee"), Browsable(True), _
    EditorAttribute(GetType(LightShapeEditor), _
    GetType(System.Drawing.Design.UITypeEditor))> _
    Public Property LightShape() As MarqueeLightShape
    
        Get
            Return Me.lightShapeValue
        End Get
    
        Set(ByVal Value As MarqueeLightShape)
            Me.lightShapeValue = Value
        End Set
    
    End Property
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property SpinDirection() As MarqueeSpinDirection
    
        Get
            Return Me.spinDirectionValue
        End Get
    
        Set(ByVal Value As MarqueeSpinDirection)
            Me.spinDirectionValue = Value
        End Set
    
    End Property
    
  11. Implemente los controladores para los eventos BackgroundWorker y DoWork del componente ProgressChanged.

    El DoWork controlador de eventos espera durante el número de milisegundos especificados por UpdatePeriod y luego genera el evento ProgressChanged, hasta que tu código detenga la animación llamando a CancelAsync.

    El ProgressChanged controlador de eventos incrementa la posición de la luz "base", desde la que se determina el estado claro/oscuro de las otras luces y llama al Refresh método para que el control se vuelva a pintar.

    // This method is called in the worker thread's context,
    // so it must not make any calls into the MarqueeBorder
    // control. Instead, it communicates to the control using
    // the ProgressChanged event.
    //
    // The only work done in this event handler is
    // to sleep for the number of milliseconds specified
    // by UpdatePeriod, then raise the ProgressChanged event.
    private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
    
        // This event handler will run until the client cancels
        // the background task by calling CancelAsync.
        while (!worker.CancellationPending)
        {
            // The Argument property of the DoWorkEventArgs
            // object holds the value of UpdatePeriod, which
            // was passed as the argument to the RunWorkerAsync
            // method.
            Thread.Sleep((int)e.Argument);
    
            // The DoWork eventhandler does not actually report
            // progress; the ReportProgress event is used to
            // periodically alert the control to update its state.
            worker.ReportProgress(0);
        }
    }
    
    // The ProgressChanged event is raised by the DoWork method.
    // This event handler does work that is internal to the
    // control. In this case, the currentOffset is incremented,
    // and the control is told to repaint itself.
    private void backgroundWorker1_ProgressChanged(
        object sender,
        System.ComponentModel.ProgressChangedEventArgs e)
    {
        this.currentOffset++;
        this.Refresh();
    }
    
    ' This method is called in the worker thread's context, 
    ' so it must not make any calls into the MarqueeBorder
    ' control. Instead, it communicates to the control using 
    ' the ProgressChanged event.
    '
    ' The only work done in this event handler is
    ' to sleep for the number of milliseconds specified 
    ' by UpdatePeriod, then raise the ProgressChanged event.
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
        ' This event handler will run until the client cancels
        ' the background task by calling CancelAsync.
        While Not worker.CancellationPending
            ' The Argument property of the DoWorkEventArgs
            ' object holds the value of UpdatePeriod, which 
            ' was passed as the argument to the RunWorkerAsync
            ' method. 
            Thread.Sleep(Fix(e.Argument))
    
            ' The DoWork eventhandler does not actually report
            ' progress; the ReportProgress event is used to 
            ' periodically alert the control to update its state.
            worker.ReportProgress(0)
        End While
    End Sub
    
    
    ' The ProgressChanged event is raised by the DoWork method.
    ' This event handler does work that is internal to the
    ' control. In this case, the currentOffset is incremented,
    ' and the control is told to repaint itself.
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
        Me.currentOffset += 1
        Me.Refresh()
    End Sub
    
  12. Implemente los métodos auxiliares IsLit y DrawLight.

    El IsLit método determina el color de una luz en una posición determinada. Las luces que se "iluminan" se dibujan en el color proporcionado por la LightColor propiedad y las que son "oscuras" se dibujan en el color proporcionado por la DarkColor propiedad .

    El DrawLight método 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 virtual bool IsLit(int lightIndex)
    {
        int directionFactor =
            (this.spinDirectionValue == MarqueeSpinDirection.CW ? -1 : 1);
    
        return (
            (lightIndex + directionFactor * this.currentOffset) % this.lightPeriodValue == 0
            );
    }
    
    protected virtual void DrawLight(
        Graphics g,
        Brush brush,
        int xPos,
        int yPos)
    {
        switch (this.lightShapeValue)
        {
            case MarqueeLightShape.Square:
                {
                    g.FillRectangle(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue);
                    break;
                }
            case MarqueeLightShape.Circle:
                {
                    g.FillEllipse(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue);
                    break;
                }
            default:
                {
                    Trace.Assert(false, "Unknown value for light shape.");
                    break;
                }
        }
    }
    
    ' This method determines if the marquee light at lightIndex
    ' should be lit. The currentOffset field specifies where
    ' the "first" light is located, and the "position" of the
    ' light given by lightIndex is computed relative to this 
    ' offset. If this position modulo lightPeriodValue is zero,
    ' the light is considered to be on, and it will be painted
    ' with the control's lightBrush. 
    Protected Overridable Function IsLit(ByVal lightIndex As Integer) As Boolean
        Dim directionFactor As Integer = _
        IIf(Me.spinDirectionValue = MarqueeSpinDirection.CW, -1, 1)
    
        Return (lightIndex + directionFactor * Me.currentOffset) Mod Me.lightPeriodValue = 0
    End Function
    
    
    Protected Overridable Sub DrawLight( _
    ByVal g As Graphics, _
    ByVal brush As Brush, _
    ByVal xPos As Integer, _
    ByVal yPos As Integer)
    
        Select Case Me.lightShapeValue
            Case MarqueeLightShape.Square
                g.FillRectangle( _
                brush, _
                xPos, _
                yPos, _
                Me.lightSizeValue, _
                Me.lightSizeValue)
                Exit Select
            Case MarqueeLightShape.Circle
                g.FillEllipse( _
                brush, _
                xPos, _
                yPos, _
                Me.lightSizeValue, _
                Me.lightSizeValue)
                Exit Select
            Case Else
                Trace.Assert(False, "Unknown value for light shape.")
                Exit Select
        End Select
    
    End Sub
    
  13. Invalide los métodos OnLayout y OnPaint.

    El OnPaint método dibuja las luces a lo largo de los bordes del MarqueeBorder control.

    Dado que el OnPaint método depende de las dimensiones del MarqueeBorder control, debe llamarlo cada vez que cambie el diseño. Para lograrlo, invalide OnLayout y llame a Refresh.

    protected override void OnLayout(LayoutEventArgs levent)
    {
        base.OnLayout(levent);
    
        // Repaint when the layout has changed.
        this.Refresh();
    }
    
    // This method paints the lights around the border of the
    // control. It paints the top row first, followed by the
    // right side, the bottom row, and the left side. The color
    // of each light is determined by the IsLit method and
    // depends on the light's position relative to the value
    // of currentOffset.
    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        g.Clear(this.BackColor);
    
        base.OnPaint(e);
    
        // If the control is large enough, draw some lights.
        if (this.Width > MaxLightSize &&
            this.Height > MaxLightSize)
        {
            // The position of the next light will be incremented
            // by this value, which is equal to the sum of the
            // light size and the space between two lights.
            int increment =
                this.lightSizeValue + this.lightSpacingValue;
    
            // Compute the number of lights to be drawn along the
            // horizontal edges of the control.
            int horizontalLights =
                (this.Width - increment) / increment;
    
            // Compute the number of lights to be drawn along the
            // vertical edges of the control.
            int verticalLights =
                (this.Height - increment) / increment;
    
            // These local variables will be used to position and
            // paint each light.
            int xPos = 0;
            int yPos = 0;
            int lightCounter = 0;
            Brush brush;
    
            // Draw the top row of lights.
            for (int i = 0; i < horizontalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                xPos += increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the right edge of the control.
            xPos = this.Width - this.lightSizeValue;
    
            // Draw the right column of lights.
            for (int i = 0; i < verticalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                yPos += increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the bottom edge of the control.
            yPos = this.Height - this.lightSizeValue;
    
            // Draw the bottom row of lights.
            for (int i = 0; i < horizontalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                xPos -= increment;
                lightCounter++;
            }
    
            // Draw the lights flush with the left edge of the control.
            xPos = 0;
    
            // Draw the left column of lights.
            for (int i = 0; i < verticalLights; i++)
            {
                brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;
    
                DrawLight(g, brush, xPos, yPos);
    
                yPos -= increment;
                lightCounter++;
            }
        }
    }
    
    Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs)
        MyBase.OnLayout(levent)
    
        ' Repaint when the layout has changed.
        Me.Refresh()
    End Sub
    
    
    ' This method paints the lights around the border of the 
    ' control. It paints the top row first, followed by the
    ' right side, the bottom row, and the left side. The color
    ' of each light is determined by the IsLit method and
    ' depends on the light's position relative to the value
    ' of currentOffset.
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim g As Graphics = e.Graphics
        g.Clear(Me.BackColor)
    
        MyBase.OnPaint(e)
    
        ' If the control is large enough, draw some lights.
        If Me.Width > MaxLightSize AndAlso Me.Height > MaxLightSize Then
            ' The position of the next light will be incremented 
            ' by this value, which is equal to the sum of the
            ' light size and the space between two lights.
            Dim increment As Integer = _
            Me.lightSizeValue + Me.lightSpacingValue
    
            ' Compute the number of lights to be drawn along the
            ' horizontal edges of the control.
            Dim horizontalLights As Integer = _
            (Me.Width - increment) / increment
    
            ' Compute the number of lights to be drawn along the
            ' vertical edges of the control.
            Dim verticalLights As Integer = _
            (Me.Height - increment) / increment
    
            ' These local variables will be used to position and
            ' paint each light.
            Dim xPos As Integer = 0
            Dim yPos As Integer = 0
            Dim lightCounter As Integer = 0
            Dim brush As Brush
    
            ' Draw the top row of lights.
            Dim i As Integer
            For i = 0 To horizontalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                xPos += increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the right edge of the control.
            xPos = Me.Width - Me.lightSizeValue
    
            ' Draw the right column of lights.
            'Dim i As Integer
            For i = 0 To verticalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                yPos += increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the bottom edge of the control.
            yPos = Me.Height - Me.lightSizeValue
    
            ' Draw the bottom row of lights.
            'Dim i As Integer
            For i = 0 To horizontalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                xPos -= increment
                lightCounter += 1
            Next i
    
            ' Draw the lights flush with the left edge of the control.
            xPos = 0
    
            ' Draw the left column of lights.
            'Dim i As Integer
            For i = 0 To verticalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)
    
                DrawLight(g, brush, xPos, yPos)
    
                yPos -= increment
                lightCounter += 1
            Next i
        End If
    End Sub
    

Crear un diseñador personalizado para sombras y filtrar propiedades

La MarqueeControlRootDesigner clase proporciona la implementación para el diseñador raíz. Además de este diseñador, que funciona en MarqueeControl, necesitará un diseñador personalizado que esté específicamente asociado al control MarqueeBorder. Este diseñador proporciona un comportamiento personalizado adecuado en el contexto del diseñador raíz personalizado.

En concreto, el MarqueeBorderDesigner replicará y filtrará determinadas propiedades del control MarqueeBorder, cambiando su interacción con el entorno de diseño.

La interceptación de llamadas al descriptor de acceso de propiedad de un componente se conoce como "sombreado". Permite que un diseñador realice un seguimiento del valor establecido por el usuario y, opcionalmente, pase ese valor al componente que se está diseñando.

En este ejemplo, las propiedades Visible y Enabled serán ocultadas por MarqueeBorderDesigner, lo que impide que el usuario pueda hacer invisible o deshabilitar el control durante el tiempo de diseño MarqueeBorder.

Los diseñadores también pueden agregar y quitar propiedades. En este ejemplo, la Padding propiedad se eliminará en tiempo de diseño, ya que el MarqueeBorder control configura programáticamente el espaciado en función del tamaño de las luces especificadas por la LightSize propiedad.

La clase base de MarqueeBorderDesigner es ComponentDesigner, que tiene métodos que pueden cambiar los atributos, las propiedades y los eventos expuestos por un control en tiempo de diseño:

Al cambiar la interfaz pública de un componente mediante estos métodos, siga estas reglas:

  • Agregar o quitar elementos solo en los PreFilter métodos

  • Modificación de elementos existentes solo en los PostFilter métodos

  • Llame siempre a la implementación base primero en los métodos.PreFilter

  • Llame siempre a la implementación base por última vez en los métodos.PostFilter

Cumplir estas reglas garantiza que todos los diseñadores del entorno en tiempo de diseño tengan una vista coherente de todos los componentes que se están diseñando.

La ComponentDesigner clase proporciona un diccionario para administrar los valores de las propiedades sombreadas, lo que le alivia la necesidad de crear variables de instancia específicas.

Para crear un diseñador personalizado para sombrear y filtrar propiedades

  1. Haga clic con el botón derecho en la carpeta Diseño y agregue una nueva clase. Asigne al archivo de origen un nombre base de MarqueeBorderDesigner.

  2. Abra el archivo de código fuente MarqueeBorderDesigner en el Editor de código. En la parte superior del fichero, importe los siguientes namespaces:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  3. Cambie la declaración de MarqueeBorderDesigner para que herede de ParentControlDesigner.

    Dado que MarqueeBorder puede contener controles secundarios, MarqueeBorderDesigner hereda de ParentControlDesigner, que maneja la interacción entre controles primarios y secundarios.

    namespace MarqueeControlLibrary.Design
    {
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
  4. Invalide la implementación base de PreFilterProperties.

    protected override void PreFilterProperties(IDictionary properties)
    {
        base.PreFilterProperties(properties);
    
        if (properties.Contains("Padding"))
        {
            properties.Remove("Padding");
        }
    
        properties["Visible"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Visible"],
            new Attribute[0]);
    
        properties["Enabled"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Enabled"],
            new Attribute[0]);
    }
    
    Protected Overrides Sub PreFilterProperties( _
    ByVal properties As IDictionary)
    
        MyBase.PreFilterProperties(properties)
    
        If properties.Contains("Padding") Then
            properties.Remove("Padding")
        End If
    
        properties("Visible") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Visible"), PropertyDescriptor), _
        New Attribute(-1) {})
    
        properties("Enabled") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Enabled"), _
        PropertyDescriptor), _
        New Attribute(-1) {})
    
    End Sub
    
  5. Implemente las Enabled propiedades y Visible . Estas implementaciones sombrean las propiedades del control.

    public bool Visible
    {
        get
        {
            return (bool)ShadowProperties["Visible"];
        }
        set
        {
            this.ShadowProperties["Visible"] = value;
        }
    }
    
    public bool Enabled
    {
        get
        {
            return (bool)ShadowProperties["Enabled"];
        }
        set
        {
            this.ShadowProperties["Enabled"] = value;
        }
    }
    
    Public Property Visible() As Boolean
        Get
            Return CBool(ShadowProperties("Visible"))
        End Get
        Set(ByVal Value As Boolean)
            Me.ShadowProperties("Visible") = Value
        End Set
    End Property
    
    
    Public Property Enabled() As Boolean
        Get
            Return CBool(ShadowProperties("Enabled"))
        End Get
        Set(ByVal Value As Boolean)
            Me.ShadowProperties("Enabled") = Value
        End Set
    End Property
    

Controlar los cambios del componente

La MarqueeControlRootDesigner clase proporciona una experiencia de personalización en tiempo de diseño para sus MarqueeControl instancias. La mayoría de la funcionalidad en tiempo de diseño se hereda de la clase DocumentDesigner. El código implementará dos personalizaciones específicas: controlar los cambios de componente y agregar verbos del diseñador.

A medida que los usuarios diseñan sus MarqueeControl instancias, el diseñador raíz realizará un seguimiento de los cambios al MarqueeControl y sus controles secundarios. El entorno en tiempo de diseño ofrece un servicio práctico, IComponentChangeService, para seguir los cambios en el estado del componente.

Para adquirir una referencia a este servicio, consulte el entorno con el método GetService. Si la consulta se realiza correctamente, el diseñador puede adjuntar un controlador para el ComponentChanged evento y realizar las tareas necesarias para mantener un estado coherente en tiempo de diseño.

En el caso de la MarqueeControlRootDesigner clase, llamará al Refresh método en cada IMarqueeWidget objeto contenido por el MarqueeControl. Esto hará que el IMarqueeWidget objeto se repinte correctamente cuando se cambien propiedades como las de su padre Size.

Para gestionar los cambios de los componentes

  1. Abra el MarqueeControlRootDesigner archivo de código fuente en el Editor de código e invalide el Initialize método . Llame a la implementación base de Initialize y solicite el IComponentChangeService.

    base.Initialize(component);
    
    IComponentChangeService cs =
        GetService(typeof(IComponentChangeService))
        as IComponentChangeService;
    
    if (cs != null)
    {
        cs.ComponentChanged +=
            new ComponentChangedEventHandler(OnComponentChanged);
    }
    
    MyBase.Initialize(component)
    
    Dim cs As IComponentChangeService = _
    CType(GetService(GetType(IComponentChangeService)), _
    IComponentChangeService)
    
    If (cs IsNot Nothing) Then
        AddHandler cs.ComponentChanged, AddressOf OnComponentChanged
    End If
    
  2. Implemente el controlador de OnComponentChanged eventos. Pruebe el tipo del componente de envío y, si es un IMarqueeWidget, llame a su método Refresh.

    private void OnComponentChanged(
        object sender,
        ComponentChangedEventArgs e)
    {
        if (e.Component is IMarqueeWidget)
        {
            this.Control.Refresh();
        }
    }
    
    Private Sub OnComponentChanged( _
    ByVal sender As Object, _
    ByVal e As ComponentChangedEventArgs)
        If TypeOf e.Component Is IMarqueeWidget Then
            Me.Control.Refresh()
        End If
    End Sub
    

Añadir Verbos del Diseñador a su 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 al menú contextual de un componente en tiempo de diseño. Para obtener más información, consulte DesignerVerb.

Agregará dos verbos de diseñador a los diseñadores: Ejecutar prueba y detener prueba. Estos verbos le permitirán ver el comportamiento en tiempo de ejecución de MarqueeControl en tiempo de diseño. Estos verbos se agregarán a MarqueeControlRootDesigner.

Cuando se invoca Run Test, el controlador de eventos del verbo llamará al método StartMarquee en el MarqueeControl. Cuando se invoca Stop Test, el manejador del evento de verbo llamará al método StopMarquee en MarqueeControl. La implementación de los métodos StartMarquee y StopMarquee llama a estos métodos en controles contenidos que implementan IMarqueeWidget, de manera que cualquier control contenido IMarqueeWidget también participará en la prueba.

Para agregar verbos de diseño a tus diseñadores personalizados

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

    private void OnVerbRunTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Start();
    }
    
    private void OnVerbStopTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Stop();
    }
    
    Private Sub OnVerbRunTest( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
        c.Start()
    
    End Sub
    
    Private Sub OnVerbStopTest( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
        c.Stop()
    
    End Sub
    
  2. Conecte estos controladores de eventos a sus verbos de diseñador correspondientes. MarqueeControlRootDesigner hereda un DesignerVerbCollection elemento de su clase base. Creará dos nuevos objetos DesignerVerb y los agregará a esta colección en el método Initialize.

    this.Verbs.Add(
        new DesignerVerb("Run Test",
        new EventHandler(OnVerbRunTest))
        );
    
    this.Verbs.Add(
        new DesignerVerb("Stop Test",
        new EventHandler(OnVerbStopTest))
        );
    
    Me.Verbs.Add(New DesignerVerb("Run Test", _
    New EventHandler(AddressOf OnVerbRunTest)))
    
    Me.Verbs.Add(New DesignerVerb("Stop Test", _
    New EventHandler(AddressOf OnVerbStopTest)))
    

Creación de un uiTypeEditor personalizado

Al crear una experiencia personalizada en tiempo de diseño para los usuarios, a menudo es conveniente crear una interacción personalizada con la ventana Propiedades. Puede lograr esto creando un UITypeEditor.

El MarqueeBorder control expone varias propiedades en la ventana Propiedades. Dos de estas propiedades MarqueeSpinDirection y MarqueeLightShape se representan mediante enumeraciones. Para ilustrar el uso de un editor de tipos de interfaz de usuario, la MarqueeLightShape propiedad tendrá una clase asociada UITypeEditor .

Para crear un editor de tipos de interfaz de usuario personalizado

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

  2. En la definición de la MarqueeBorder clase , declare una clase denominada 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.
    internal class LightShapeEditor : UITypeEditor
    {
    
    ' This class demonstrates the use of a custom UITypeEditor. 
    ' It allows the MarqueeBorder control's LightShape property
    ' to be changed at design time using a customized UI element
    ' that is invoked by the Properties window. The UI is provided
    ' by the LightShapeSelectionControl class.
    Friend Class LightShapeEditor
        Inherits UITypeEditor
    
  3. Declare una IWindowsFormsEditorService variable de instancia denominada editorService.

    private IWindowsFormsEditorService editorService = null;
    
    Private editorService As IWindowsFormsEditorService = Nothing
    
  4. Invalide el GetEditStyle método . Esta implementación devuelve DropDown, que indica al entorno de diseño cómo mostrar el LightShapeEditor.

    public override UITypeEditorEditStyle GetEditStyle(
    System.ComponentModel.ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    
    Public Overrides Function GetEditStyle( _
    ByVal context As System.ComponentModel.ITypeDescriptorContext) _
    As UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function
    
    
  5. Invalide el EditValue método . Esta implementación consulta el entorno de diseño de un IWindowsFormsEditorService objeto . Si se ejecuta correctamente, 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 override object EditValue(
        ITypeDescriptorContext context,
        IServiceProvider provider,
        object value)
    {
        if (provider != null)
        {
            editorService =
                provider.GetService(
                typeof(IWindowsFormsEditorService))
                as IWindowsFormsEditorService;
        }
    
        if (editorService != null)
        {
            LightShapeSelectionControl selectionControl =
                new LightShapeSelectionControl(
                (MarqueeLightShape)value,
                editorService);
    
            editorService.DropDownControl(selectionControl);
    
            value = selectionControl.LightShape;
        }
    
        return value;
    }
    
    Public Overrides Function EditValue( _
    ByVal context As ITypeDescriptorContext, _
    ByVal provider As IServiceProvider, _
    ByVal value As Object) As Object
        If (provider IsNot Nothing) Then
            editorService = _
            CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
            IWindowsFormsEditorService)
        End If
    
        If (editorService IsNot Nothing) Then
            Dim selectionControl As _
            New LightShapeSelectionControl( _
            CType(value, MarqueeLightShape), _
            editorService)
    
            editorService.DropDownControl(selectionControl)
    
            value = selectionControl.LightShape
        End If
    
        Return value
    End Function
    

Crear un control de vista para el uiTypeEditor personalizado

La MarqueeLightShape propiedad admite dos tipos de formas ligeras: Square y Circle. Creará un control personalizado utilizado únicamente para mostrar gráficamente estos valores en la ventana Propiedades. El usuario usará UITypeEditor este control personalizado para interactuar con la ventana Propiedades.

Para crear un control de vista para el editor de tipos de interfaz de usuario personalizado

  1. Agregue un nuevo UserControl elemento al MarqueeControlLibrary proyecto. Asigne al nuevo archivo de origen un nombre base de LightShapeSelectionControl.

  2. Arrastre dos Panel controles desde el Cuadro de herramientas a .LightShapeSelectionControl Nómbralos squarePanel y circlePanel. Organícelas en paralelo. Establezca la Size propiedad de ambos Panel controles en (60, 60) . Establezca la Location propiedad del squarePanel control en (8, 10). Establezca la Location propiedad del circlePanel control en (80, 10). Por último, establezca la propiedad Size de LightShapeSelectionControl en (150, 80).

  3. Abra el LightShapeSelectionControl archivo de código fuente en el Editor de código. En la parte superior del archivo, importa el System.Windows.Forms.Design namespace.

    Imports System.Windows.Forms.Design
    
    using System.Windows.Forms.Design;
    
  4. Implemente controladores de eventos para los controles Click y squarePanel. Estos métodos invocan CloseDropDown para finalizar la sesión de edición personalizada UITypeEditor .

    private void squarePanel_Click(object sender, EventArgs e)
    {
        this.lightShapeValue = MarqueeLightShape.Square;
        
        this.Invalidate( false );
    
        this.editorService.CloseDropDown();
    }
    
    private void circlePanel_Click(object sender, EventArgs e)
    {
        this.lightShapeValue = MarqueeLightShape.Circle;
    
        this.Invalidate( false );
    
        this.editorService.CloseDropDown();
    }
    
    Private Sub squarePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Me.lightShapeValue = MarqueeLightShape.Square
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()
    
    End Sub
    
    
    Private Sub circlePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Me.lightShapeValue = MarqueeLightShape.Circle
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()
    
    End Sub
    
  5. Declare una IWindowsFormsEditorService variable de instancia denominada editorService.

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

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
  7. En el LightShapeSelectionControl constructor, adjunte los Click controladores de eventos a los eventos de los controles squarePanel y circlePanelClick. Además, defina una sobrecarga de constructor que asigne el MarqueeLightShape valor del entorno de diseño al lightShapeValue campo.

    // This constructor takes a MarqueeLightShape value from the
    // design-time environment, which will be used to display
    // the initial state.
    public LightShapeSelectionControl(
        MarqueeLightShape lightShape,
        IWindowsFormsEditorService editorService )
    {
        // This call is required by the designer.
        InitializeComponent();
    
        // Cache the light shape value provided by the
        // design-time environment.
        this.lightShapeValue = lightShape;
    
        // Cache the reference to the editor service.
        this.editorService = editorService;
    
        // Handle the Click event for the two panels.
        this.squarePanel.Click += new EventHandler(squarePanel_Click);
        this.circlePanel.Click += new EventHandler(circlePanel_Click);
    }
    
    ' This constructor takes a MarqueeLightShape value from the
    ' design-time environment, which will be used to display
    ' the initial state.
     Public Sub New( _
     ByVal lightShape As MarqueeLightShape, _
     ByVal editorService As IWindowsFormsEditorService)
         ' This call is required by the Windows.Forms Form Designer.
         InitializeComponent()
    
         ' Cache the light shape value provided by the 
         ' design-time environment.
         Me.lightShapeValue = lightShape
    
         ' Cache the reference to the editor service.
         Me.editorService = editorService
    
         ' Handle the Click event for the two panels. 
         AddHandler Me.squarePanel.Click, AddressOf squarePanel_Click
         AddHandler Me.circlePanel.Click, AddressOf circlePanel_Click
     End Sub
    
  8. En el Dispose método, desasocie los Click controladores de eventos.

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            // Be sure to unhook event handlers
            // to prevent "lapsed listener" leaks.
            this.squarePanel.Click -=
                new EventHandler(squarePanel_Click);
            this.circlePanel.Click -=
                new EventHandler(circlePanel_Click);
    
            if(components != null)
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }
    
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
    
            ' Be sure to unhook event handlers
            ' to prevent "lapsed listener" leaks.
            RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click
            RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click
    
            If (components IsNot Nothing) Then
                components.Dispose()
            End If
    
        End If
        MyBase.Dispose(disposing)
    End Sub
    
  9. 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 Dispose método .

  10. Implemente la LightShape propiedad .

    // LightShape is the property for which this control provides
    // a custom user interface in the Properties window.
    public MarqueeLightShape LightShape
    {
        get
        {
            return this.lightShapeValue;
        }
        
        set
        {
            if( this.lightShapeValue != value )
            {
                this.lightShapeValue = value;
            }
        }
    }
    
    ' LightShape is the property for which this control provides
    ' a custom user interface in the Properties window.
    Public Property LightShape() As MarqueeLightShape
    
        Get
            Return Me.lightShapeValue
        End Get
    
        Set(ByVal Value As MarqueeLightShape)
            If Me.lightShapeValue <> Value Then
                Me.lightShapeValue = Value
            End If
        End Set
    
    End Property
    
  11. Invalide el OnPaint método . Esta implementación dibujará un cuadrado y un círculo rellenos. También resaltará el valor seleccionado dibujando un borde alrededor de una forma u otra.

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint (e);
    
        using(
            Graphics gSquare = this.squarePanel.CreateGraphics(),
            gCircle = this.circlePanel.CreateGraphics() )
        {	
            // Draw a filled square in the client area of
            // the squarePanel control.
            gSquare.FillRectangle(
                Brushes.Red,
                0,
                0,
                this.squarePanel.Width,
                this.squarePanel.Height
                );
    
            // If the Square option has been selected, draw a
            // border inside the squarePanel.
            if( this.lightShapeValue == MarqueeLightShape.Square )
            {
                gSquare.DrawRectangle(
                    Pens.Black,
                    0,
                    0,
                    this.squarePanel.Width-1,
                    this.squarePanel.Height-1);
            }
    
            // Draw a filled circle in the client area of
            // the circlePanel control.
            gCircle.Clear( this.circlePanel.BackColor );
            gCircle.FillEllipse(
                Brushes.Blue,
                0,
                0,
                this.circlePanel.Width,
                this.circlePanel.Height
                );
    
            // If the Circle option has been selected, draw a
            // border inside the circlePanel.
            if( this.lightShapeValue == MarqueeLightShape.Circle )
            {
                gCircle.DrawRectangle(
                    Pens.Black,
                    0,
                    0,
                    this.circlePanel.Width-1,
                    this.circlePanel.Height-1);
            }
        }	
    }
    
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        MyBase.OnPaint(e)
    
        Dim gCircle As Graphics = Me.circlePanel.CreateGraphics()
        Try
            Dim gSquare As Graphics = Me.squarePanel.CreateGraphics()
            Try
                ' Draw a filled square in the client area of
                ' the squarePanel control.
                gSquare.FillRectangle( _
                Brushes.Red, _
                0, _
                0, _
                Me.squarePanel.Width, _
                Me.squarePanel.Height)
    
                ' If the Square option has been selected, draw a 
                ' border inside the squarePanel.
                If Me.lightShapeValue = MarqueeLightShape.Square Then
                    gSquare.DrawRectangle( _
                    Pens.Black, _
                    0, _
                    0, _
                    Me.squarePanel.Width - 1, _
                    Me.squarePanel.Height - 1)
                End If
    
                ' Draw a filled circle in the client area of
                ' the circlePanel control.
                gCircle.Clear(Me.circlePanel.BackColor)
                gCircle.FillEllipse( _
                Brushes.Blue, _
                0, _
                0, _
                Me.circlePanel.Width, _
                Me.circlePanel.Height)
    
                ' If the Circle option has been selected, draw a 
                ' border inside the circlePanel.
                If Me.lightShapeValue = MarqueeLightShape.Circle Then
                    gCircle.DrawRectangle( _
                    Pens.Black, _
                    0, _
                    0, _
                    Me.circlePanel.Width - 1, _
                    Me.circlePanel.Height - 1)
                End If
            Finally
                gSquare.Dispose()
            End Try
        Finally
            gCircle.Dispose()
        End Try
    End Sub
    

Probar el control personalizado en el diseñador

En este momento, puede compilar el proyecto MarqueeControlLibrary. Pruebe la implementación mediante la creación de un control que herede de la MarqueeControl clase y su uso en un formulario.

Para crear una implementación personalizada de MarqueeControl

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

  2. En el Cuadro de herramientas, abra la pestaña MarqueeControlLibrary Components (Componentes de MarqueeControlLibrary). Verá los MarqueeBorder controles y MarqueeText disponibles para la selección.

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

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

  5. Compile la solución.

  6. Haga clic con el DemoMarqueeControl botón derecho en y, en el menú contextual, seleccione la opción Ejecutar prueba para iniciar la animación. Haga clic en Detener prueba para detener la animación.

  7. Abra Form1 en la vista Diseño.

  8. Coloque dos Button controles en el formulario. Asígneles los nombres startButton y stopButton, y cambie los valores de las propiedades Text a Start y Stop, respectivamente.

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

  10. En el Cuadro de herramientas, abra la pestaña MarqueeControlTest Components (Componentes de MarqueeControlTest). Verá la DemoMarqueeControl opción disponible para la selección.

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

  12. En los Click controladores de eventos, invoque los métodos Start y Stop en el 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();
    }
    
  13. Establezca el MarqueeControlTest proyecto como proyecto de inicio y ejecútelo. Verá el formulario que muestra su DemoMarqueeControl. Seleccione el botón Iniciar para iniciar la animación. Debería ver el texto parpadeando y las luces moviéndose alrededor del borde.

Pasos siguientes

MarqueeControlLibrary muestra una implementación sencilla de controles personalizados y diseñadores asociados. Puede hacer que este ejemplo sea más sofisticado de varias maneras:

  • Cambie los valores de propiedad del DemoMarqueeControl en el diseñador. Agregue más MarqueBorder controles y acóplelos dentro de sus instancias de origen para crear un efecto anidado. Experimente con diferentes configuraciones para la UpdatePeriod y las propiedades relacionadas con la luz.

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

  • Personalice aún más la experiencia en el momento del diseño. Puede intentar sombrear más propiedades que Enabled y Visible, y podría agregar nuevas propiedades. Agregue nuevos verbos de diseñador para simplificar tareas comunes, como acoplar controles secundarios.

  • Conceda la licencia a MarqueeControl.

  • Controlar cómo se serializan los controles y cómo se genera código para ellos. Para obtener más información, consulte Generación y compilación dinámicas de código fuente.

Consulte también