Tutorial: Crear un control de formularios Windows Forms que aproveche las características en tiempo de diseño de Visual StudioWalkthrough: Creating a Windows Forms Control That Takes Advantage of Visual Studio Design-Time Features

Puede mejorar la experiencia en tiempo de diseño para un control personalizado mediante la creación de un diseñador personalizado asociado.The design-time experience for a custom control can be enhanced by authoring an associated custom designer.

Este tutorial muestra cómo crear un diseñador personalizado para un control personalizado.This walkthrough illustrates how to create a custom designer for a custom control. Implementará un MarqueeControl tipo y una clase de diseñador asociada, llamado MarqueeControlRootDesigner.You will implement a MarqueeControl type and an associated designer class, called MarqueeControlRootDesigner.

El MarqueeControl tipo implementa una presentación similar a una marquesina de teatro con luces animadas y texto parpadeante.The MarqueeControl type implements a display similar to a theater marquee, with animated lights and flashing text.

El diseñador para este control interactúa con el entorno de diseño para proporcionar una experiencia personalizada en tiempo de diseño.The designer for this control interacts with the design environment to provide a custom design-time experience. Con el diseñador personalizado, puede ensamblar una personalizada MarqueeControl implementación con luces animadas y texto parpadeante en muchas combinaciones.With the custom designer, you can assemble a custom MarqueeControl implementation with animated lights and flashing text in many combinations. Puede usar el control ensamblado en un formulario como cualquier otro control de Windows Forms.You can use the assembled control on a form like any other Windows Forms control.

Las tareas ilustradas en este tutorial incluyen:Tasks illustrated in this walkthrough include:

  • Crear el proyectoCreating the Project

  • Crear un proyecto de biblioteca de controlesCreating a Control Library Project

  • Referencia del proyecto de Control personalizadoReferencing the Custom Control Project

  • Definir un Control personalizado y su diseñador personalizadoDefining a Custom Control and Its Custom Designer

  • Creación de una instancia del Control personalizadoCreating an Instance of Your Custom Control

  • Configurar el proyecto para la depuración de tiempo de diseñoSetting Up the Project for Design-Time Debugging

  • Implementar el Control personalizadoImplementing Your Custom Control

  • Creación de un Control secundario para el Control personalizadoCreating a Child Control for Your Custom Control

  • Crear el Control MarqueeBorder secundarioCreate the MarqueeBorder Child Control

  • Crear un diseñador personalizado para instantáneas y las propiedades de filtroCreating a Custom Designer to Shadow and Filter Properties

  • Gestionar los cambios de componenteHandling Component Changes

  • Agregar verbos de diseñador a un diseñador personalizadoAdding Designer Verbs to your Custom Designer

  • Crear un UITypeEditor personalizadoCreating a Custom UITypeEditor

  • Probar el Control personalizado en el diseñadorTesting your Custom Control in the Designer

Cuando haya terminado, el control personalizado tendrá un aspecto similar al siguiente:When you are finished, your custom control will look something like the following:

La aplicación que muestra un recuadro que indica que el texto y un botones de inicio y detención.

Para obtener el código completo, vea Cómo: Crear un Control de Windows Forms que aproveche las características de tiempo de diseño de.For the complete code listing, see How to: Create a Windows Forms Control That Takes Advantage of Design-Time Features.

Nota

Los cuadros de diálogo y comandos de menú que se ven pueden diferir de los descritos en la Ayuda, en función de los valores de configuración o de edición activos.The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. Para cambiar la configuración, elija la opción Importar y exportar configuraciones del menú Herramientas .To change your settings, choose Import and Export Settings on the Tools menu. Para más información, vea Personalizar el IDE de Visual Studio.For more information, see Personalize the Visual Studio IDE.

Requisitos previosPrerequisites

Para completar este tutorial, necesitará Visual Studio.In order to complete this walkthrough, you'll need Visual Studio.

Crear el proyectoCreating the Project

El primer paso es crear el proyecto de aplicación.The first step is to create the application project. Este proyecto se utilizará para compilar la aplicación que hospeda el control personalizado.You will use this project to build the application that hosts the custom control.

Abra Visual Studio y cree un proyecto de aplicación de Windows Forms denominado "MarqueeControlTest" (archivo > New > proyecto > Visual C# o Visual Basic > escritorio clásico de > deaplicacióndeformulariosdeWindows).Open Visual Studio and create a Windows Forms Application project called "MarqueeControlTest" (File > New > Project > Visual C# or Visual Basic > Classic Desktop > Windows Forms Application).

Crear un proyecto de biblioteca de controlesCreating a Control Library Project

El siguiente paso es crear el proyecto de biblioteca de controles.The next step is to create the control library project. Se creará un nuevo control personalizado y su diseñador personalizado correspondiente.You will create a new custom control and its corresponding custom designer.

Para crear el proyecto de biblioteca de controlTo create the control library project

  1. Agregue un proyecto de biblioteca de controles de Windows Forms a la solución.Add a Windows Forms Control Library project to the solution. Denomine el proyecto "MarqueeControlLibrary".Name the project "MarqueeControlLibrary."

  2. Uso de el Explorador de soluciones, elimine el control predeterminado del proyecto eliminando el archivo de origen denominado "UserControl1.cs" o "UserControl1.vb", dependiendo del lenguaje que prefiera.Using Solution Explorer, delete the project's default control by deleting the source file named "UserControl1.cs" or "UserControl1.vb", depending on your language of choice. Para obtener más información, vea Cómo: Quitar, eliminar y excluir elementos.For more information, see How to: Remove, Delete, and Exclude Items.

  3. Agregue un nuevo UserControl de elemento para el MarqueeControlLibrary proyecto.Add a new UserControl item to the MarqueeControlLibrary project. Asigne al archivo de origen nuevo nombre base de "MarqueeControl".Give the new source file a base name of "MarqueeControl."

  4. Uso de el Explorador de soluciones, cree una nueva carpeta en el MarqueeControlLibrary proyecto.Using Solution Explorer, create a new folder in the MarqueeControlLibrary project. Para obtener más información, vea Cómo: Agregar nuevos elementos de proyecto.For more information, see How to: Add New Project Items. Nombre de la nueva carpeta "Diseño".Name the new folder "Design."

  5. Haga clic en el diseño carpeta y agregue una nueva clase.Right-click the Design folder and add a new class. Asigne al archivo de origen nombre base de "MarqueeControlRootDesigner".Give the source file a base name of "MarqueeControlRootDesigner."

  6. Deberá utilizar los tipos del ensamblado System.Design, así que agregue esta referencia a la MarqueeControlLibrary proyecto.You will need to use types from the System.Design assembly, so add this reference to the MarqueeControlLibrary project.

    Nota

    Para usar el ensamblado System.Design, el proyecto debe tener como destino la versión completa de .NET Framework, no el .NET Framework Client Profile.To use the System.Design assembly, your project must target the full version of the .NET Framework, not the .NET Framework Client Profile. Para cambiar la plataforma de destino, vea Cómo: usar una versión de .NET Framework como destino.To change the target framework, see How to: Target a Version of the .NET Framework.

Referencia del proyecto de Control personalizadoReferencing the Custom Control Project

Usará el MarqueeControlTest proyecto para probar el control personalizado.You will use the MarqueeControlTest project to test the custom control. El proyecto de prueba conocerán la existencia del control personalizado cuando se agrega una referencia al proyecto el MarqueeControlLibrary ensamblado.The test project will become aware of the custom control when you add a project reference to the MarqueeControlLibrary assembly.

Para hacer referencia al proyecto de control personalizadoTo reference the custom control project

  • En el MarqueeControlTest proyecto, agregue una referencia al proyecto el MarqueeControlLibrary ensamblado.In the MarqueeControlTest project, add a project reference to the MarqueeControlLibrary assembly. Asegúrese de usar el proyectos pestaña en el Agregar referencia cuadro de diálogo en lugar de hacer referencia a la MarqueeControlLibrary ensamblado directamente.Be sure to use the Projects tab in the Add Reference dialog box instead of referencing the MarqueeControlLibrary assembly directly.

Definir un Control personalizado y su diseñador personalizadoDefining a Custom Control and Its Custom Designer

El control personalizado se derivará de la UserControl clase.Your custom control will derive from the UserControl class. Esto permite que el control contener otros controles, y proporciona un amplio abanico de funcionalidades de forma predeterminada el control.This allows your control to contain other controls, and it gives your control a great deal of default functionality.

El control personalizado tendrá un diseñador personalizado asociado.Your custom control will have an associated custom designer. Esto le permite crear una experiencia de diseño exclusivo diseñada específicamente para su control personalizado.This allows you to create a unique design experience tailored specifically for your custom control.

Asociar el control a su diseñador utilizando el DesignerAttribute clase.You associate the control with its designer by using the DesignerAttribute class. Dado que está desarrollando el comportamiento en tiempo de diseño completo del control personalizado, el diseñador personalizado implementará la IRootDesigner interfaz.Because you are developing the entire design-time behavior of your custom control, the custom designer will implement the IRootDesigner interface.

Para definir un control personalizado y su diseñador personalizadoTo define a custom control and its custom designer

  1. Abra el MarqueeControl archivo de código fuente en el Editor de código.Open the MarqueeControl source file in the Code Editor. En la parte superior del archivo, importe los espacios de nombres siguientes:At the top of the file, import the following 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
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  2. Agregar el DesignerAttribute a la MarqueeControl declaración de clase.Add the DesignerAttribute to the MarqueeControl class declaration. Esto asocia el control personalizado a su diseñador.This associates the custom control with its designer.

    [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.Open the MarqueeControlRootDesigner source file in the Code Editor. En la parte superior del archivo, importe los espacios de nombres siguientes:At the top of the file, import the following 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
    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 va a heredar la DocumentDesigner clase.Change the declaration of MarqueeControlRootDesigner to inherit from the DocumentDesigner class. Aplicar el ToolboxItemFilterAttribute para especificar la interacción del diseñador con el cuadro de herramientas.Apply the ToolboxItemFilterAttribute to specify the designer interaction with the Toolbox.

    Tenga en cuenta la definición de la MarqueeControlRootDesigner clase se ha incluido en un espacio de nombres denominado "MarqueeControlLibrary.Design".Note The definition for the MarqueeControlRootDesigner class has been enclosed in a namespace called "MarqueeControlLibrary.Design." Esta declaración coloca el diseñador en un espacio de nombres especial reservado para los tipos relacionados con el diseño.This declaration places the designer in a special namespace reserved for design-related types.

    namespace MarqueeControlLibrary.Design
    {
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeControlRootDesigner : DocumentDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
        ToolboxItemFilterType.Require), _
        ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
        ToolboxItemFilterType.Require)> _
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeControlRootDesigner
            Inherits DocumentDesigner
    
  5. Defina el constructor para la MarqueeControlRootDesigner clase.Define the constructor for the MarqueeControlRootDesigner class. Insertar un WriteLine instrucción en el cuerpo del constructor.Insert a WriteLine statement in the constructor body. Esto será útil para fines de depuración.This will be useful for debugging purposes.

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

Creación de una instancia del Control personalizadoCreating an Instance of Your Custom Control

Para observar el comportamiento personalizado en tiempo de diseño del control, colocará una instancia del control en el formulario en MarqueeControlTest proyecto.To observe the custom design-time behavior of your control, you will place an instance of your control on the form in MarqueeControlTest project.

Para crear una instancia del control personalizadoTo create an instance of your custom control

  1. Agregue un nuevo UserControl de elemento para el MarqueeControlTest proyecto.Add a new UserControl item to the MarqueeControlTest project. Asigne el nuevo archivo de origen en un nombre base de "DemoMarqueeControl".Give the new source file a base name of "DemoMarqueeControl."

  2. Abra el DemoMarqueeControl de archivos en el Editor de código.Open the DemoMarqueeControl file in the Code Editor. En la parte superior del archivo, importe el MarqueeControlLibrary espacio de nombres:At the top of the file, import the MarqueeControlLibrary namespace:

Imports MarqueeControlLibrary
using MarqueeControlLibrary;
  1. Cambie la declaración de DemoMarqueeControl va a heredar la MarqueeControl clase.Change the declaration of DemoMarqueeControl to inherit from the MarqueeControl class.

  2. Compile el proyecto.Build the project.

  3. Abra Form1 en el Diseñador de Windows Forms.Open Form1 in the Windows Forms Designer.

  4. Buscar el MarqueeControlTest componentes pestaña en el cuadro de herramientas y ábralo.Find the MarqueeControlTest Components tab in the Toolbox and open it. Arrastre un DemoMarqueeControl desde el cuadro de herramientas hasta su formulario.Drag a DemoMarqueeControl from the Toolbox onto your form.

  5. Compile el proyecto.Build the project.

Configurar el proyecto para la depuración de tiempo de diseñoSetting Up the Project for Design-Time Debugging

Cuando está desarrollando una experiencia personalizada en tiempo de diseño, será necesario depurar los controles y componentes.When you are developing a custom design-time experience, it will be necessary to debug your controls and components. Hay una manera sencilla de configurar el proyecto para permitir la depuración en tiempo de diseño.There is a simple way to set up your project to allow debugging at design time. Para obtener más información, vea Tutorial: Controles de depuración personalizado Windows Forms en tiempo de diseño.For more information, see Walkthrough: Debugging Custom Windows Forms Controls at Design Time.

Para configurar el proyecto para la depuración de tiempo de diseñoTo set up the project for design-time debugging

  1. Haga clic en el MarqueeControlLibrary del proyecto y seleccione propiedades.Right-click the MarqueeControlLibrary project and select Properties.

  2. En el cuadro de diálogo "Páginas de propiedades de MarqueeControlLibrary", seleccione el depurar página.In the "MarqueeControlLibrary Property Pages" dialog box, select the Debug page.

  3. En el acción de inicio sección, seleccione iniciar programa externo.In the Start Action section, select Start External Program. Puede depurar una instancia independiente de Visual Studio, haga clic en el botón de puntos suspensivos (los puntos suspensivos (...) en la ventana Propiedades de Visual Studio.) botón para buscar el IDE de Visual Studio.You will be debugging a separate instance of Visual Studio, so click the ellipsis (The Ellipsis button (...) in the Properties window of Visual Studio.) button to browse for the Visual Studio IDE. El nombre del archivo ejecutable es devenv.exe, y si ha instalado en la ubicación predeterminada, su ruta de acceso es %programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.The name of the executable file is devenv.exe, and if you installed to the default location, its path is %programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.

  4. Haga clic en Aceptar para cerrar el cuadro de diálogo.Click OK to close the dialog box.

  5. Haga clic en el MarqueeControlLibrary del proyecto y seleccione "Establecer como proyecto de inicio" para habilitar esta configuración de depuración.Right-click the MarqueeControlLibrary project and select "Set as StartUp Project" to enable this debugging configuration.

Punto de controlCheckpoint

Ahora está listo para depurar el comportamiento en tiempo de diseño del control personalizado.You are now ready to debug the design-time behavior of your custom control. Cuando haya determinado que el entorno de depuración se ha configurado correctamente, probará la asociación entre el control personalizado y el diseñador personalizado.Once you have determined that the debugging environment is set up correctly, you will test the association between the custom control and the custom designer.

Para probar el entorno de depuración y la asociación del diseñadorTo test the debugging environment and the designer association

  1. Abra el MarqueeControlRootDesigner archivo de código fuente en el Editor de código y coloque un punto de interrupción en el WriteLine instrucción.Open the MarqueeControlRootDesigner source file in the Code Editor and place a breakpoint on the WriteLine statement.

  2. Presione F5 para iniciar la sesión de depuración.Press F5 to start the debugging session. Tenga en cuenta que se crea una nueva instancia de Visual Studio.Note that a new instance of Visual Studio is created.

  3. En la nueva instancia de Visual Studio, abra la solución "MarqueeControlTest".In the new instance of Visual Studio, open the "MarqueeControlTest" solution. Puede encontrar fácilmente la solución seleccionando proyectos recientes desde el archivo menú.You can easily find the solution by selecting Recent Projects from the File menu. El archivo de solución "MarqueeControlTest.sln" se mostrará como los archivos usados recientemente.The "MarqueeControlTest.sln" solution file will be listed as the most recently used file.

  4. Abra el DemoMarqueeControl en el diseñador.Open the DemoMarqueeControl in the designer. Tenga en cuenta que la instancia de depuración de Visual Studio adquiere el foco y la ejecución se detiene en el punto de interrupción.Note that the debugging instance of Visual Studio acquires focus and execution stops at your breakpoint. Presione F5 para continuar la sesión de depuración.Press F5 to continue the debugging session.

En este momento, todo está listo para desarrollar y depurar el control personalizado y su diseñador personalizado asociado.At this point, everything is in place for you to develop and debug your custom control and its associated custom designer. El resto de este tutorial se centrará en los detalles de implementación de las características del control y el diseñador.The remainder of this walkthrough will concentrate on the details of implementing features of the control and the designer.

Implementar el Control personalizadoImplementing Your Custom Control

El MarqueeControl es un UserControl con un poco de personalización.The MarqueeControl is a UserControl with a little bit of customization. Expone dos métodos: Start, que inicia la animación de marquesina, y Stop, lo que detiene la animación.It exposes two methods: Start, which starts the marquee animation, and Stop, which stops the animation. Dado que el MarqueeControl contiene controles secundarios que implementan la IMarqueeWidget interfaz, Start y Stop enumerar cada control secundario y la llamada la StartMarquee y StopMarquee métodos, respectivamente, en cada control secundario que implementa IMarqueeWidget.Because the MarqueeControl contains child controls that implement the IMarqueeWidget interface, Start and Stop enumerate each child control and call the StartMarquee and StopMarquee methods, respectively, on each child control that implements IMarqueeWidget.

La apariencia de la MarqueeBorder y MarqueeText depende de los controles en el diseño, por lo que MarqueeControl invalida la OnLayout método y llama a PerformLayout en los controles secundarios de este tipo.The appearance of the MarqueeBorder and MarqueeText controls is dependent on the layout, so MarqueeControl overrides the OnLayout method and calls PerformLayout on child controls of this type.

Se trata de la extensión de la MarqueeControl personalizaciones.This is the extent of the MarqueeControl customizations. Las características de tiempo de ejecución se implementan mediante la MarqueeBorder y MarqueeText controles y las características de tiempo de diseño se implementan mediante la MarqueeBorderDesigner y MarqueeControlRootDesigner clases.The run-time features are implemented by the MarqueeBorder and MarqueeText controls, and the design-time features are implemented by the MarqueeBorderDesigner and MarqueeControlRootDesigner classes.

Para implementar el control personalizadoTo implement your custom control

  1. Abra el MarqueeControl archivo de código fuente en el Editor de código.Open the MarqueeControl source file in the Code Editor. Implemente el Start y Stop métodos.Implement the Start and Stop methods.

    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 método OnLayout .Override the OnLayout method.

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

Creación de un Control secundario para el Control personalizadoCreating a Child Control for Your Custom Control

El MarqueeControl hospedará los dos tipos de control secundario: el MarqueeBorder control y el MarqueeText control.The MarqueeControl will host two kinds of child control: the MarqueeBorder control and the MarqueeText control.

  • MarqueeBorder: Este control dibuja un borde de "luces" alrededor de los bordes.MarqueeBorder: This control paints a border of "lights" around its edges. Las luces de flash en secuencia, por lo que parecen que se mueven alrededor del borde.The lights flash in sequence, so they appear to be moving around the border. La velocidad a la que las luces flash se controla mediante una propiedad denominada UpdatePeriod.The speed at which the lights flash is controlled by a property called UpdatePeriod. Varias propiedades personalizadas determinan otros aspectos de la apariencia del control.Several other custom properties determine other aspects of the control's appearance. Dos métodos, denominados StartMarquee y StopMarquee, controlar cuando se inicia y detiene la animación.Two methods, called StartMarquee and StopMarquee, control when the animation starts and stops.

  • MarqueeText: Este control representa una cadena parpadeante.MarqueeText: This control paints a flashing string. Al igual que el MarqueeBorder (control), la velocidad a la que parpadea el texto se controla mediante el UpdatePeriod propiedad.Like the MarqueeBorder control, the speed at which the text flashes is controlled by the UpdatePeriod property. El MarqueeText control también tiene la StartMarquee y StopMarquee métodos en común con el MarqueeBorder control.The MarqueeText control also has the StartMarquee and StopMarquee methods in common with the MarqueeBorder control.

En tiempo de diseño, el MarqueeControlRootDesigner permite que estos dos tipos de control que se agregarán a un MarqueeControl en cualquier combinación.At design time, the MarqueeControlRootDesigner allows these two control types to be added to a MarqueeControl in any combination.

Características comunes de los dos controles se factorizan en una interfaz denominada IMarqueeWidget.Common features of the two controls are factored into an interface called IMarqueeWidget. Esto permite la MarqueeControl para detectar todos los controles secundarios relacionados con la marquesina y darles un tratamiento especial.This allows the MarqueeControl to discover any Marquee-related child controls and give them special treatment.

Para implementar la característica de animación periódica, utilizará BackgroundWorker objetos desde el System.ComponentModel espacio de nombres.To implement the periodic animation feature, you will use BackgroundWorker objects from the System.ComponentModel namespace. Puede usar Timer objetos, pero, cuando muchas IMarqueeWidget objetos están presentes, el subproceso de interfaz de usuario único puede ser incapaz de procesar con la animación.You could use Timer objects, but when many IMarqueeWidget objects are present, the single UI thread may be unable to keep up with the animation.

Para crear un control secundario para el control personalizadoTo create a child control for your custom control

  1. Agregar un nuevo elemento de la clase a la MarqueeControlLibrary proyecto.Add a new class item to the MarqueeControlLibrary project. Asigne el nuevo archivo de origen en un nombre base de "IMarqueeWidget".Give the new source file a base name of "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:Open the IMarqueeWidget source file in the Code Editor and change the declaration from class to 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 manipulan la animación de la marquesina:Add the following code to the IMarqueeWidget interface to expose two methods and a property that manipulate the marquee animation:

    // 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 Custom Control de elemento para el MarqueeControlLibrary proyecto.Add a new Custom Control item to the MarqueeControlLibrary project. Asigne el nuevo archivo de origen en un nombre base de "MarqueeText".Give the new source file a base name of "MarqueeText."

  5. Arrastre un BackgroundWorker componente desde el cuadro de herramientas en su MarqueeText control.Drag a BackgroundWorker component from the Toolbox onto your MarqueeText control. Este componente permitirá la MarqueeText control para actualizarse de forma asincrónica.This component will allow the MarqueeText control to update itself asynchronously.

  6. En la ventana Propiedades, establezca la BackgroundWorker del componente WorkerReportsProgress y WorkerSupportsCancellation propiedades a true.In the Properties window, set the BackgroundWorker component's WorkerReportsProgress and WorkerSupportsCancellation properties to true. Esta configuración permite que el BackgroundWorker componente provocar periódicamente el ProgressChanged eventos y cancelar las actualizaciones asincrónicas.These settings allow the BackgroundWorker component to periodically raise the ProgressChanged event and to cancel asynchronous updates. Para obtener más información, consulte componente BackgroundWorker.For more information, see BackgroundWorker Component.

  7. Abra el MarqueeText archivo de código fuente en el Editor de código.Open the MarqueeText source file in the Code Editor. En la parte superior del archivo, importe los espacios de nombres siguientes:At the top of the file, import the following 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
    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 de MarqueeText va a heredar Label e implementar el IMarqueeWidget interfaz:Change the declaration of MarqueeText to inherit from Label and to implement the IMarqueeWidget interface:

    [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 inicializarlos en el constructor.Declare the instance variables that correspond to the exposed properties, and initialize them in the constructor. El isLit campo determina si el texto que se va a pintar el color proporcionado por el LightColor propiedad.The isLit field determines if the text is to be painted in the color given by the LightColor property.

    // 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 'New
    
  10. Implementar la interfaz IMarqueeWidget.Implement the IMarqueeWidget interface.

    El StartMarquee y StopMarquee métodos invocan la BackgroundWorker del componente RunWorkerAsync y CancelAsync métodos para iniciar y detener la animación.The StartMarquee and StopMarquee methods invoke the BackgroundWorker component's RunWorkerAsync and CancelAsync methods to start and stop the animation.

    El Category y Browsable atributos se aplican a la UpdatePeriod propiedad para que aparezca en una sección personalizada de la ventana de propiedades denominada "Marco".The Category and Browsable attributes are applied to the UpdatePeriod property so it appears in a custom section of the Properties window called "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.Implement the property accessors. Va a exponer dos propiedades a los clientes: LightColor y DarkColor.You will expose two properties to clients: LightColor and DarkColor. El Category y Browsable atributos se aplican a estas propiedades, por lo que las propiedades aparecen en una sección personalizada de la ventana de propiedades denominada "Marco".The Category and Browsable attributes are applied to these properties, so the properties appear in a custom section of the Properties window called "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 la BackgroundWorker del componente DoWork y ProgressChanged eventos.Implement the handlers for the BackgroundWorker component's DoWork and ProgressChanged events.

    El DoWork se suspende el controlador de eventos para el número de milisegundos especificados por UpdatePeriod , a continuación, genera el ProgressChanged evento, hasta que el código detiene la animación mediante una llamada a CancelAsync.The DoWork event handler sleeps for the number of milliseconds specified by UpdatePeriod then raises the ProgressChanged event, until your code stops the animation by calling CancelAsync.

    El ProgressChanged controlador de eventos alterna el texto entre su estado claro y oscuro para dar la apariencia de parpadeo.The ProgressChanged event handler toggles the text between its light and dark state to give the appearance of flashing.

    // 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. Invalidar el OnPaint método para habilitar la animación.Override the OnPaint method to enable the animation.

    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.Press F6 to build the solution.

Crear el Control MarqueeBorder secundarioCreate the MarqueeBorder Child Control

El MarqueeBorder control es ligeramente más sofisticado que el MarqueeText control.The MarqueeBorder control is slightly more sophisticated than the MarqueeText control. Tiene más propiedades y la animación en el OnPaint método es más complicado.It has more properties and the animation in the OnPaint method is more involved. En principio, es bastante similar a la MarqueeText control.In principle, it is quite similar to the MarqueeText control.

Dado que el MarqueeBorder control puede tener controles secundarios, debe tener en cuenta Layout eventos.Because the MarqueeBorder control can have child controls, it needs to be aware of Layout events.

Para crear el control MarqueeBorderTo create the MarqueeBorder control

  1. Agregue un nuevo Custom Control de elemento para el MarqueeControlLibrary proyecto.Add a new Custom Control item to the MarqueeControlLibrary project. Asigne el nuevo archivo de origen en un nombre base de "MarqueeControl".Give the new source file a base name of "MarqueeBorder."

  2. Arrastre un BackgroundWorker componente desde el cuadro de herramientas en su MarqueeBorder control.Drag a BackgroundWorker component from the Toolbox onto your MarqueeBorder control. Este componente permitirá la MarqueeBorder control para actualizarse de forma asincrónica.This component will allow the MarqueeBorder control to update itself asynchronously.

  3. En la ventana Propiedades, establezca la BackgroundWorker del componente WorkerReportsProgress y WorkerSupportsCancellation propiedades a true.In the Properties window, set the BackgroundWorker component's WorkerReportsProgress and WorkerSupportsCancellation properties to true. Esta configuración permite que el BackgroundWorker componente provocar periódicamente el ProgressChanged eventos y cancelar las actualizaciones asincrónicas.These settings allow the BackgroundWorker component to periodically raise the ProgressChanged event and to cancel asynchronous updates. Para obtener más información, consulte componente BackgroundWorker.For more information, see BackgroundWorker Component.

  4. En la ventana Propiedades, haga clic en el botón de eventos.In the Properties window, click the Events button. Adjuntar controladores para la DoWork y ProgressChanged eventos.Attach handlers for the DoWork and ProgressChanged events.

  5. Abra el MarqueeBorder archivo de código fuente en el Editor de código.Open the MarqueeBorder source file in the Code Editor. En la parte superior del archivo, importe los espacios de nombres siguientes:At the top of the file, import the following 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
    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 va a heredar Panel e implementar el IMarqueeWidget interfaz.Change the declaration of MarqueeBorder to inherit from Panel and to implement the IMarqueeWidget interface.

    [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 "" gire sobre sí mismo borde, y MarqueeLightShape, que determina la forma de las luces (cuadradas o circulares).Declare two enumerations for managing the MarqueeBorder control's state: MarqueeSpinDirection, which determines the direction in which the lights "spin" around the border, and MarqueeLightShape, which determines the shape of the lights (square or circular). Coloque estas declaraciones antes el MarqueeBorder declaración de clase.Place these declarations before the MarqueeBorder class declaration.

    // 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 inicializarlos en el constructor.Declare the instance variables that correspond to the exposed properties, and initialize them in the 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. Implementar la interfaz IMarqueeWidget.Implement the IMarqueeWidget interface.

    El StartMarquee y StopMarquee métodos invocan la BackgroundWorker del componente RunWorkerAsync y CancelAsync métodos para iniciar y detener la animación.The StartMarquee and StopMarquee methods invoke the BackgroundWorker component's RunWorkerAsync and CancelAsync methods to start and stop the animation.

    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.Because the MarqueeBorder control can contain child controls, the StartMarquee method enumerates all child controls and calls StartMarquee on those that implement IMarqueeWidget. El StopMarquee método tiene una implementación similar.The StopMarquee method has a similar implementation.

    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.Implement the property accessors. El MarqueeBorder control tiene varias propiedades para controlar su apariencia.The MarqueeBorder control has several properties for controlling its appearance.

    [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 la BackgroundWorker del componente DoWork y ProgressChanged eventos.Implement the handlers for the BackgroundWorker component's DoWork and ProgressChanged events.

    El DoWork se suspende el controlador de eventos para el número de milisegundos especificados por UpdatePeriod , a continuación, genera el ProgressChanged evento, hasta que el código detiene la animación mediante una llamada a CancelAsync.The DoWork event handler sleeps for the number of milliseconds specified by UpdatePeriod then raises the ProgressChanged event, until your code stops the animation by calling CancelAsync.

    El ProgressChanged controlador de eventos incrementa la posición de la luz "base", desde el que se determina el estado claro/oscuro de las luces de otros, y llama a la Refresh método para hacer que el control a pintarse.The ProgressChanged event handler increments the position of the "base" light, from which the light/dark state of the other lights is determined, and calls the Refresh method to cause the control to repaint itself.

    // 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.Implement the helper methods, IsLit and DrawLight.

    El IsLit método determina el color de una luz en una posición determinada.The IsLit method determines the color of a light at a given position. Las luces que se "ilumina" se dibujan en el color proporcionado por el LightColor propiedad y los "oscuro" que se dibujan en el color proporcionado por el DarkColor propiedad.Lights that are "lit" are drawn in the color given by the LightColor property, and those that are "dark" are drawn in the color given by the DarkColor property.

    El DrawLight método dibuja una luz mediante el color apropiado, la forma y la posición.The DrawLight method draws a light using the appropriate color, shape, and position.

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

    El OnPaint método dibuja las luces a lo largo de los bordes de la MarqueeBorder control.The OnPaint method draws the lights along the edges of the MarqueeBorder control.

    Dado que el OnPaint método depende de las dimensiones de la MarqueeBorder control, se debe llamar cuando cambia el diseño.Because the OnPaint method depends on the dimensions of the MarqueeBorder control, you need to call it whenever the layout changes. Para ello, invalide OnLayout y llamar a Refresh.To achieve this, override OnLayout and call 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 instantáneas y las propiedades de filtroCreating a Custom Designer to Shadow and Filter Properties

La MarqueeControlRootDesigner clase proporciona la implementación para el diseñador raíz.The MarqueeControlRootDesigner class provides the implementation for the root designer. Además de este diseñador, que actúa sobre el MarqueeControl, necesitará un diseñador personalizado que está asociado a específicamente el MarqueeBorder control.In addition to this designer, which operates on the MarqueeControl, you will need a custom designer that is specifically associated with the MarqueeBorder control. Este diseñador proporciona el comportamiento personalizado que sea adecuado para el contexto del diseñador raíz personalizado.This designer provides custom behavior that is appropriate in the context of the custom root designer.

En concreto, el MarqueeBorderDesigner se "sombra" y filtrar determinadas propiedades en el MarqueeBorder control, cambiar su interacción con el entorno de diseño.Specifically, the MarqueeBorderDesigner will "shadow" and filter certain properties on the MarqueeBorder control, changing their interaction with the design environment.

Interceptar las llamadas al descriptor de acceso de propiedad de un componente se conoce como "sombreado".Intercepting calls to a component's property accessor is known as "shadowing." Permite que un diseñador realizar un seguimiento del valor establecido por el usuario y, opcionalmente, pase ese valor para el componente que se está diseñando.It allows a designer to track the value set by the user and optionally pass that value to the component being designed.

En este ejemplo, el Visible y Enabled propiedades se está sombreadas por la MarqueeBorderDesigner, lo que impide al usuario realizar el MarqueeBorder control oculto o deshabilitado durante el tiempo de diseño.For this example, the Visible and Enabled properties will be shadowed by the MarqueeBorderDesigner, which prevents the user from making the MarqueeBorder control invisible or disabled during design time.

Los diseñadores también pueden agregar y quitar propiedades.Designers can also add and remove properties. En este ejemplo, el Padding propiedad se quitará en tiempo de diseño, porque el MarqueeBorder control establece mediante programación el relleno en función del tamaño de las luces especificado por el LightSize propiedad.For this example, the Padding property will be removed at design time, because the MarqueeBorder control programmatically sets the padding based on the size of the lights specified by the LightSize property.

La clase base para MarqueeBorderDesigner es ComponentDesigner, que tiene métodos que pueden cambiar los atributos, propiedades y eventos expuestos por un control en tiempo de diseño:The base class for MarqueeBorderDesigner is ComponentDesigner, which has methods that can change the attributes, properties, and events exposed by a control at design time:

Al cambiar la interfaz pública de un componente mediante estos métodos, debe seguir estas reglas:When changing the public interface of a component using these methods, you must follow these rules:

  • Agregar o quitar elementos de la PreFilter solo métodosAdd or remove items in the PreFilter methods only

  • Modificar los elementos existentes en el PostFilter solo métodosModify existing items in the PostFilter methods only

  • Llame siempre a la implementación base en primer lugar el PreFilter métodosAlways call the base implementation first in the PreFilter methods

  • Llame siempre a la implementación base por última vez el PostFilter métodosAlways call the base implementation last in the PostFilter methods

Adhesión a estas reglas, se garantiza que todos los diseñadores en el entorno de tiempo de diseño tienen una vista coherente de todos los componentes que se está diseñando.Adhering to these rules ensures that all designers in the design-time environment have a consistent view of all components being designed.

La ComponentDesigner clase proporciona un diccionario para administrar los valores de propiedades reemplazadas, lo que evita la necesidad de crear variables de instancia específico.The ComponentDesigner class provides a dictionary for managing the values of shadowed properties, which relieves you of the need to create specific instance variables.

Para crear un diseñador personalizado para sombrear y filtrar propiedadesTo create a custom designer to shadow and filter properties

  1. Haga clic en el diseño carpeta y agregue una nueva clase.Right-click the Design folder and add a new class. Asigne al archivo de origen nombre base de "MarqueeBorderDesigner".Give the source file a base name of "MarqueeBorderDesigner."

  2. Abra el MarqueeBorderDesigner archivo de código fuente en el Editor de código.Open the MarqueeBorderDesigner source file in the Code Editor. En la parte superior del archivo, importe los espacios de nombres siguientes:At the top of the file, import the following 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
    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 va a heredar ParentControlDesigner.Change the declaration of MarqueeBorderDesigner to inherit from ParentControlDesigner.

    Dado que el MarqueeBorder control puede contener controles secundarios, MarqueeBorderDesigner hereda ParentControlDesigner, que controla la interacción de elementos primarios y secundarios.Because the MarqueeBorder control can contain child controls, MarqueeBorderDesigner inherits from ParentControlDesigner, which handles the parent-child interaction.

    namespace MarqueeControlLibrary.Design
    {
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
    Namespace MarqueeControlLibrary.Design
    
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
  4. Invalidar la implementación base de PreFilterProperties.Override the base implementation of 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 propiedades Enabled y Visible.Implement the Enabled and Visible properties. Estas implementaciones sombrean las propiedades del control.These implementations shadow the control's properties.

    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
    

Gestionar los cambios de componenteHandling Component Changes

El MarqueeControlRootDesigner clase proporciona la experiencia en tiempo de diseño personalizada para su MarqueeControl instancias.The MarqueeControlRootDesigner class provides the custom design-time experience for your MarqueeControl instances. La mayoría de la funcionalidad en tiempo de diseño se hereda de la DocumentDesigner clase; el código se implementan dos personalizaciones específicas: control de cambios en los componentes y la adición de verbos de diseñador.Most of the design-time functionality is inherited from the DocumentDesigner class; your code will implement two specific customizations: handling component changes, and adding designer verbs.

Como el diseño de los usuarios sus MarqueeControl instancias, el diseñador raíz realizará el seguimiento de cambios en el MarqueeControl y sus controles secundarios.As users design their MarqueeControl instances, your root designer will track changes to the MarqueeControl and its child controls. El entorno en tiempo de diseño ofrece un práctico servicio IComponentChangeService, para el seguimiento de cambios en el estado del componente.The design-time environment offers a convenient service, IComponentChangeService, for tracking changes to component state.

Adquirir una referencia a este servicio consultando el entorno con el GetService método.You acquire a reference to this service by querying the environment with the GetService method. Si la consulta es correcta, el diseñador puede adjuntar un controlador para el ComponentChanged eventos y realizar las tareas necesarias para mantener un estado coherente en tiempo de diseño.If the query is successful, your designer can attach a handler for the ComponentChanged event and perform whatever tasks are required to maintain a consistent state at design time.

En el caso de los MarqueeControlRootDesigner (clase), llamará a la Refresh método en cada IMarqueeWidget objeto incluido en el MarqueeControl.In the case of the MarqueeControlRootDesigner class, you will call the Refresh method on each IMarqueeWidget object contained by the MarqueeControl. Esto hará que el IMarqueeWidget objeto a pintarse adecuadamente cuando propiedades como su elemento primario Size cambian.This will cause the IMarqueeWidget object to repaint itself appropriately when properties like its parent's Size are changed.

Para controlar los cambios de componenteTo handle component changes

  1. Abra el MarqueeControlRootDesigner archivo de código fuente en el Editor de código e invalidar la Initialize método.Open the MarqueeControlRootDesigner source file in the Code Editor and override the Initialize method. Llame a la implementación base de Initialize y consultar el IComponentChangeService.Call the base implementation of Initialize and query for the 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 OnComponentChanged controlador de eventos.Implement the OnComponentChanged event handler. Probar el tipo del componente envío, y si es un IMarqueeWidget, llame a su Refresh método.Test the sending component's type, and if it is an IMarqueeWidget, call its Refresh method.

    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
    

Agregar verbos de diseñador a un diseñador personalizadoAdding Designer Verbs to your Custom Designer

Un verbo de diseñador es un comando de menú vinculado a un controlador de eventos.A designer verb is a menu command linked to an event handler. Verbos de diseñador se agregan al menú contextual de un componente en tiempo de diseño.Designer verbs are added to a component's shortcut menu at design time. Para obtener más información, consulta DesignerVerb.For more information, see DesignerVerb.

Agregará dos verbos de diseñador a los diseñadores: Ejecutar prueba y Detener prueba.You will add two designer verbs to your designers: Run Test and Stop Test. Estos verbos, podrá ver el comportamiento de tiempo de ejecución de la MarqueeControl en tiempo de diseño.These verbs will allow you to view the run-time behavior of the MarqueeControl at design time. Estos verbos se agregarán a la MarqueeControlRootDesigner.These verbs will be added to the MarqueeControlRootDesigner.

Cuando Ejecutar prueba es invoca, el controlador de eventos del verbo llamará el StartMarquee método en el MarqueeControl.When Run Test is invoked, the verb event handler will call the StartMarquee method on the MarqueeControl. Cuando Detener prueba es invoca, el controlador de eventos del verbo llamará el StopMarquee método en el MarqueeControl.When Stop Test is invoked, the verb event handler will call the StopMarquee method on the MarqueeControl. La implementación de la StartMarquee y StopMarquee métodos llamar a estos métodos en controles contenidos que implementan IMarqueeWidget, por lo que cualquier contenido IMarqueeWidget controles también participará en la prueba.The implementation of the StartMarquee and StopMarquee methods call these methods on contained controls that implement IMarqueeWidget, so any contained IMarqueeWidget controls will also participate in the test.

Para agregar verbos de diseñador a los diseñadores personalizadosTo add designer verbs to your custom designers

  1. En el MarqueeControlRootDesigner de clases, agregue los controladores de eventos denominado OnVerbRunTest y OnVerbStopTest.In the MarqueeControlRootDesigner class, add event handlers named OnVerbRunTest and 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. Conectar estos controladores de eventos a sus verbos de diseñador correspondientes.Connect these event handlers to their corresponding designer verbs. MarqueeControlRootDesigner hereda un DesignerVerbCollection de su clase base.MarqueeControlRootDesigner inherits a DesignerVerbCollection from its base class. Creará dos nuevas DesignerVerb objetos y agregarlos a esta colección en el Initialize método.You will create two new DesignerVerb objects and add them to this collection in the Initialize method.

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

Crear un UITypeEditor personalizadoCreating a Custom UITypeEditor

Cuando se crea una experiencia personalizada en tiempo de diseño para los usuarios, suele ser deseable para crear una interacción personalizada con la ventana Propiedades.When you create a custom design-time experience for users, it is often desirable to create a custom interaction with the Properties window. Puede hacerlo mediante la creación de un UITypeEditor.You can accomplish this by creating a UITypeEditor. Para obtener más información, vea Cómo: Crear un Editor de tipos de interfaz de usuario.For more information, see How to: Create a UI Type Editor.

El MarqueeBorder control expone varias propiedades en la ventana Propiedades.The MarqueeBorder control exposes several properties in the Properties window. Dos de estas propiedades, MarqueeSpinDirection y MarqueeLightShape se representan mediante las enumeraciones.Two of these properties, MarqueeSpinDirection and MarqueeLightShape are represented by enumerations. Para ilustrar el uso de un editor de tipos de interfaz de usuario, el MarqueeLightShape propiedad tendrá asociado un UITypeEditor clase.To illustrate the use of a UI type editor, the MarqueeLightShape property will have an associated UITypeEditor class.

Para crear un tipo de interfaz de usuario personalizado de editorTo create a custom UI type editor

  1. Abra el MarqueeBorder archivo de código fuente en el Editor de código.Open the MarqueeBorder source file in the Code Editor.

  2. En la definición de la MarqueeBorder clase, declare una clase denominada LightShapeEditor que se deriva de UITypeEditor.In the definition of the MarqueeBorder class, declare a class called LightShapeEditor that derives from 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. Declarar un IWindowsFormsEditorService variable de instancia denominada editorService.Declare an IWindowsFormsEditorService instance variable called editorService.

    private IWindowsFormsEditorService editorService = null;
    
    Private editorService As IWindowsFormsEditorService = Nothing
    
  4. Invalide el método GetEditStyle .Override the GetEditStyle method. Esta implementación devuelve DropDown, que indica que el entorno de diseño cómo mostrar la LightShapeEditor.This implementation returns DropDown, which tells the design environment how to display the 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 método EditValue .Override the EditValue method. Esta implementación consulta el entorno de diseño para una IWindowsFormsEditorService objeto.This implementation queries the design environment for an IWindowsFormsEditorService object. Si es correcto, crea un LightShapeSelectionControl.If successful, it creates a LightShapeSelectionControl. El DropDownControl se invoca el método para iniciar el LightShapeEditor.The DropDownControl method is invoked to start the LightShapeEditor. El valor devuelto de esta invocación se devuelve al entorno de diseño.The return value from this invocation is returned to the design environment.

    public override object EditValue(
        ITypeDescriptorContext context,
        IServiceProvider provider,
        object value)
    {
        if (provider != null)
        {
            editorService =
                provider.GetService(
                typeof(IWindowsFormsEditorService))
                as IWindowsFormsEditorService;
        }
    
        if (editorService != null)
        {
            LightShapeSelectionControl selectionControl =
                new LightShapeSelectionControl(
                (MarqueeLightShape)value,
                editorService);
    
            editorService.DropDownControl(selectionControl);
    
            value = selectionControl.LightShape;
        }
    
        return value;
    }
    
    Public Overrides Function EditValue( _
    ByVal context As ITypeDescriptorContext, _
    ByVal provider As IServiceProvider, _
    ByVal value As Object) As Object
        If (provider IsNot Nothing) Then
            editorService = _
            CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
            IWindowsFormsEditorService)
        End If
    
        If (editorService IsNot Nothing) Then
            Dim selectionControl As _
            New LightShapeSelectionControl( _
            CType(value, MarqueeLightShape), _
            editorService)
    
            editorService.DropDownControl(selectionControl)
    
            value = selectionControl.LightShape
        End If
    
        Return value
    End Function
    

Creación de un Control de vista para UITypeEditor personalizadoCreating a View Control for your Custom UITypeEditor

  1. El MarqueeLightShape propiedad admite dos tipos de formas claros: Square y Circle.The MarqueeLightShape property supports two types of light shapes: Square and Circle. Creará un control personalizado que se utiliza únicamente para mostrar gráficamente estos valores en la ventana Propiedades.You will create a custom control used solely for the purpose of graphically displaying these values in the Properties window. Usará este control personalizado su UITypeEditor para interactuar con la ventana Propiedades.This custom control will be used by your UITypeEditor to interact with the Properties window.

Para crear un control de vista para la interfaz de usuario personalizada de editor de tiposTo create a view control for your custom UI type editor

  1. Agregue un nuevo UserControl de elemento para el MarqueeControlLibrary proyecto.Add a new UserControl item to the MarqueeControlLibrary project. Asigne el nuevo archivo de origen un nombre de base de "LightShapeSelectionControl".Give the new source file a base name of "LightShapeSelectionControl."

  2. Arrastre dos Panel controla desde el cuadro de herramientas hasta el LightShapeSelectionControl.Drag two Panel controls from the Toolbox onto the LightShapeSelectionControl. Denomínelos squarePanel y circlePanel.Name them squarePanel and circlePanel. Organícelos en paralelo.Arrange them side by side. Establecer el Size propiedad Panel controla a (60, 60).Set the Size property of both Panel controls to (60, 60). Establecer el Location propiedad de la squarePanel el control a (8, 10).Set the Location property of the squarePanel control to (8, 10). Establecer el Location propiedad de la circlePanel el control a (80, 10).Set the Location property of the circlePanel control to (80, 10). Por último, establezca el Size propiedad de la LightShapeSelectionControl a (150, 80).Finally, set the Size property of the LightShapeSelectionControl to (150, 80).

  3. Abra el LightShapeSelectionControl archivo de código fuente en el Editor de código.Open the LightShapeSelectionControl source file in the Code Editor. En la parte superior del archivo, importe el System.Windows.Forms.Design espacio de nombres:At the top of the file, import the System.Windows.Forms.Design namespace:

Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
  1. Implemente Click controladores de eventos para el squarePanel y circlePanel controles.Implement Click event handlers for the squarePanel and circlePanel controls. Estos métodos invocan CloseDropDown para finalizar la personalizada UITypeEditor sesión de edición.These methods invoke CloseDropDown to end the custom UITypeEditor editing session.

    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
    
  2. Declarar un IWindowsFormsEditorService variable de instancia denominada editorService.Declare an IWindowsFormsEditorService instance variable called editorService.

Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
  1. Declarar un MarqueeLightShape variable de instancia denominada lightShapeValue.Declare a MarqueeLightShape instance variable called lightShapeValue.

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
  2. En el LightShapeSelectionControl constructor, adjunte el Click controladores de eventos para el squarePanel y circlePanel los controles Click eventos.In the LightShapeSelectionControl constructor, attach the Click event handlers to the squarePanel and circlePanel controls' Click events. Asimismo, defina una sobrecarga del constructor que asigna el MarqueeLightShape valor desde el entorno de diseño para el lightShapeValue campo.Also, define a constructor overload that assigns the MarqueeLightShape value from the design environment to the lightShapeValue field.

    // 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
    
  3. En el Dispose método, desasociar el Click controladores de eventos.In the Dispose method, detach the Click event handlers.

    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
    
  4. En el Explorador de soluciones, haga clic en el botón Mostrar todos los archivos.In Solution Explorer, click the Show All Files button. Abra el archivo LightShapeSelectionControl.Designer.cs o LightShapeSelectionControl.Designer.vb y quite la definición predeterminada de la Dispose método.Open the LightShapeSelectionControl.Designer.cs or LightShapeSelectionControl.Designer.vb file, and remove the default definition of the Dispose method.

  5. Implemente la propiedad LightShape.Implement the LightShape property.

    // LightShape is the property for which this control provides
    // a custom user interface in the Properties window.
    public MarqueeLightShape LightShape
    {
        get
        {
            return this.lightShapeValue;
        }
        
        set
        {
            if( this.lightShapeValue != value )
            {
                this.lightShapeValue = value;
            }
        }
    }
    
    ' 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
    
  6. Invalide el método OnPaint .Override the OnPaint method. Esta implementación dibujará un cuadrado relleno y el círculo.This implementation will draw a filled square and circle. También resaltará el valor seleccionado dibujando un borde alrededor de una forma u otra.It will also highlight the selected value by drawing a border around one shape or the other.

    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ñadorTesting your Custom Control in the Designer

En este momento, puede compilar el MarqueeControlLibrary proyecto.At this point, you can build the MarqueeControlLibrary project. Probar la implementación mediante la creación de un control que hereda de la MarqueeControl clase y se usa con un formulario.Test your implementation by creating a control that inherits from the MarqueeControl class and using it on a form.

Para crear una implementación personalizada de MarqueeControlTo create a custom MarqueeControl implementation

  1. Abra DemoMarqueeControl en el Diseñador de Windows Forms.Open DemoMarqueeControl in the Windows Forms Designer. Esto crea una instancia de la DemoMarqueeControl escriba y lo muestra en una instancia de la MarqueeControlRootDesigner tipo.This creates an instance of the DemoMarqueeControl type and displays it in an instance of the MarqueeControlRootDesigner type.

  2. En el cuadro de herramientas, abra el MarqueeControlLibrary componentes ficha. Verá el MarqueeBorder y MarqueeText controles disponibles para la selección.In the Toolbox, open the MarqueeControlLibrary Components tab. You will see the MarqueeBorder and MarqueeText controls available for selection.

  3. Arrastre una instancia de la MarqueeBorder controlar hasta el DemoMarqueeControl superficie de diseño.Drag an instance of the MarqueeBorder control onto the DemoMarqueeControl design surface. Acoplar esto MarqueeBorder control al control primario.Dock this MarqueeBorder control to the parent control.

  4. Arrastre una instancia de la MarqueeText controlar hasta el DemoMarqueeControl superficie de diseño.Drag an instance of the MarqueeText control onto the DemoMarqueeControl design surface.

  5. Compile la solución.Build the solution.

  6. Haga clic en el DemoMarqueeControl y en el menú contextual, seleccione el Ejecutar prueba opción para iniciar la animación.Right-click the DemoMarqueeControl and from the shortcut menu select the Run Test option to start the animation. Haga clic en Detener prueba para detener la animación.Click Stop Test to stop the animation.

  7. Abra Form1 en la vista Diseño.Open Form1 in Design view.

  8. Coloque dos Button controles del formulario.Place two Button controls on the form. Denomínelos startButton y stopButtony cambie el Text a los valores de propiedad iniciar y detener, respectivamente.Name them startButton and stopButton, and change the Text property values to Start and Stop, respectively.

  9. Implemente Click controladores de eventos para ambos Button controles.Implement Click event handlers for both Button controls.

  10. En el cuadro de herramientas, abra el MarqueeControlTest componentes ficha. Verá el DemoMarqueeControl disponibles para la selección.In the Toolbox, open the MarqueeControlTest Components tab. You will see the DemoMarqueeControl available for selection.

  11. Arrastre una instancia de DemoMarqueeControl hasta la Form1 superficie de diseño.Drag an instance of DemoMarqueeControl onto the Form1 design surface.

  12. En el Click invocar controladores de eventos, el Start y Stop métodos en el DemoMarqueeControl.In the Click event handlers, invoke the Start and Stop methods on the DemoMarqueeControl.

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

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

private void stopButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Stop();
}
  1. Establecer el MarqueeControlTest como proyecto de inicio del proyecto y ejecútelo.Set the MarqueeControlTest project as the startup project and run it. Verá el formulario que muestra su DemoMarqueeControl.You will see the form displaying your DemoMarqueeControl. Haga clic en el iniciar botón para iniciar la animación.Click the Start button to start the animation. Debería ver el texto parpadeante y las luces desplazándose alrededor del borde.You should see the text flashing and the lights moving around the border.

Pasos siguientesNext Steps

La MarqueeControlLibrary muestra una implementación simple de controles personalizados y diseñadores asociados.The MarqueeControlLibrary demonstrates a simple implementation of custom controls and associated designers. Puede realizar este ejemplo más sofisticadas de varias maneras:You can make this sample more sophisticated in several ways:

  • Cambiar los valores de propiedad para el DemoMarqueeControl en el diseñador.Change the property values for the DemoMarqueeControl in the designer. Agregar más MarqueBorder controla y acoplarlas dentro de sus instancias primarias para crear un efecto anidado.Add more MarqueBorder controls and dock them within their parent instances to create a nested effect. Experimente con diferentes valores para el UpdatePeriod y las propiedades relacionadas con la luz.Experiment with different settings for the UpdatePeriod and the light-related properties.

  • Cree sus propias implementaciones de IMarqueeWidget.Author your own implementations of IMarqueeWidget. Por ejemplo, podría crear un parpadeante "neon inicio de sesión" o un signo animado con varias imágenes.You could, for example, create a flashing "neon sign" or an animated sign with multiple images.

  • Personalizar aún más la experiencia en tiempo de diseño.Further customize the design-time experience. Puede probar el sombreado más propiedades que Enabled y Visible, y puede agregar nuevas propiedades.You could try shadowing more properties than Enabled and Visible, and you could add new properties. Agregar nueva verbos de diseñador para simplificar las tareas comunes como el acoplamiento de controles secundarios.Add new designer verbs to simplify common tasks like docking child controls.

  • Licencia el MarqueeControl.License the MarqueeControl. Para obtener más información, vea Cómo: Licencias de componentes y controles.For more information, see How to: License Components and Controls.

  • Controlar cómo se serializan los controles y cómo se genera código para ellos.Control how your controls are serialized and how code is generated for them. Para obtener más información, consulte generación de código fuente dinámico y la compilación.For more information, see Dynamic Source Code Generation and Compilation.

Vea tambiénSee also