Tutorial: crear un control que aproveche las características de tiempo de diseñoWalkthrough: Create a control that takes advantage of design-time features

La experiencia en tiempo de diseño de un control personalizado se puede mejorar 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.

En este artículo se muestra cómo crear un diseñador personalizado para un control personalizado.This article illustrates how to create a custom designer for a custom control. Implementará un tipo de MarqueeControl y una clase de diseñador asociada denominada MarqueeControlRootDesigner.You'll implement a MarqueeControl type and an associated designer class called MarqueeControlRootDesigner.

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

El diseñador de 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 implementación de MarqueeControl personalizado con luces animadas y texto intermitente 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.

Cuando haya terminado con este tutorial, el control personalizado tendrá un aspecto similar al siguiente:When you're finished with this walkthrough, your custom control will look something like the following:

Aplicación que muestra una marquesina que indica el texto y los botones de inicio y detención.

Para obtener la lista de código completa, vea Cómo: crear un control de Windows Forms que aprovecha las características en tiempo de diseño.For the complete code listing, see How to: Create a Windows Forms Control That Takes Advantage of Design-Time Features.

Requisitos previosPrerequisites

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

Crear el proyectoCreate the project

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

En Visual Studio, cree un nuevo proyecto de aplicación de Windows Forms y asígnele el nombre MarqueeControlTest.In Visual Studio, create a new Windows Forms Application project, and name it MarqueeControlTest.

Crear el proyecto de biblioteca de controlesCreate the control library project

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

  2. Use Explorador de solucionespara eliminar el control predeterminado del proyecto eliminando el archivo de código fuente denominado "UserControl1.cs" o "UserControl1. VB", según el lenguaje que elija.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.

  3. Agregue un nuevo elemento de UserControl al proyecto de MarqueeControlLibrary.Add a new UserControl item to the MarqueeControlLibrary project. Asigne al nuevo archivo de código fuente un nombre base de MarqueeControl.Give the new source file a base name of MarqueeControl.

  4. Con Explorador de soluciones, cree una nueva carpeta en el proyecto de MarqueeControlLibrary.Using Solution Explorer, create a new folder in the MarqueeControlLibrary project.

  5. Haga clic con el botón secundario en la carpeta diseño y agregue una nueva clase.Right-click the Design folder and add a new class. Asígnele el nombre MarqueeControlRootDesigner.Name it MarqueeControlRootDesigner.

  6. Tendrá que usar tipos del ensamblado System. Design, por lo que debe agregar esta referencia al proyecto MarqueeControlLibrary.You'll need to use types from the System.Design assembly, so add this reference to the MarqueeControlLibrary project.

Referencia al proyecto de control personalizadoReference the Custom Control Project

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

En el proyecto de MarqueeControlTest, agregue una referencia de proyecto al ensamblado MarqueeControlLibrary.In the MarqueeControlTest project, add a project reference to the MarqueeControlLibrary assembly. Asegúrese de usar la pestaña proyectos del cuadro de diálogo Agregar referencia en lugar de hacer referencia al ensamblado de MarqueeControlLibrary 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 personalizadoDefine a Custom Control and Its Custom Designer

El control personalizado se deriva de la clase UserControl.Your custom control will derive from the UserControl class. Esto permite que el control contenga otros controles y proporciona a su control una gran cantidad de funcionalidad predeterminada.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 única adaptada específicamente para su control personalizado.This allows you to create a unique design experience tailored specifically for your custom control.

Asocie el control a su diseñador mediante la clase DesignerAttribute.You associate the control with its designer by using the DesignerAttribute class. Dado que está desarrollando todo el comportamiento en tiempo de diseño del control personalizado, el diseñador personalizado implementará la interfaz IRootDesigner.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 archivo de origen de MarqueeControl en el Editor de código.Open the MarqueeControl source file in the Code Editor. En la parte superior del archivo, importe los siguientes espacios de nombres: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.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  2. Agregue el DesignerAttribute a la declaración de clase MarqueeControl.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 archivo de origen de MarqueeControlRootDesigner en el Editor de código.Open the MarqueeControlRootDesigner source file in the Code Editor. En la parte superior del archivo, importe los siguientes espacios de nombres: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.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing.Design
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  4. Cambie la declaración de MarqueeControlRootDesigner para que hereden de la clase DocumentDesigner.Change the declaration of MarqueeControlRootDesigner to inherit from the DocumentDesigner class. Aplique 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.

    Nota

    La definición de la clase MarqueeControlRootDesigner se ha incluido en un espacio de nombres denominado MarqueeControlLibrary. Design.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 clase MarqueeControlRootDesigner.Define the constructor for the MarqueeControlRootDesigner class. Inserte una instrucción WriteLine en el cuerpo del constructor.Insert a WriteLine statement in the constructor body. Esto será útil para la depuración.This will be useful for debugging.

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

Crear una instancia de un control personalizadoCreate an instance of your custom control

  1. Agregue un nuevo elemento de UserControl al proyecto de MarqueeControlTest.Add a new UserControl item to the MarqueeControlTest project. Asigne al nuevo archivo de código fuente el nombre base de DemoMarqueeControl.Give the new source file a base name of DemoMarqueeControl.

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

    Imports MarqueeControlLibrary
    
    using MarqueeControlLibrary;
    
  3. Cambie la declaración de DemoMarqueeControl para que hereden de la clase MarqueeControl.Change the declaration of DemoMarqueeControl to inherit from the MarqueeControl class.

  4. Compile el proyecto.Build the project.

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

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

  7. Compile el proyecto.Build the project.

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

Al desarrollar una experiencia personalizada en tiempo de diseño, será necesario depurar los controles y componentes.When you're 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: depurar controles de Windows Forms personalizados en tiempo de diseño.For more information, see Walkthrough: Debugging Custom Windows Forms Controls at Design Time.

  1. Haga clic con el botón derecho en el proyecto MarqueeControlLibrary 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 la página depurar .In the MarqueeControlLibrary Property Pages dialog box, select the Debug page.

  3. En la sección acción de inicio , seleccione programa externo de inicio.In the Start Action section, select Start External Program. Va a depurar una instancia independiente de Visual Studio, por lo que debe hacer clic en los puntos suspensivos (el botón de puntos suspensivos (...) en el botón ventana Propiedades de Visual Studio) 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 lo instaló en la ubicación predeterminada, su ruta de acceso es % ProgramFiles (x86)% \ Microsoft Visual Studio\2019\<edition > \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(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.

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

  5. Haga clic con el botón derecho en el proyecto MarqueeControlLibrary y seleccione establecer como proyecto de inicio para habilitar esta configuración de depuración.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. Una vez que haya determinado que el entorno de depuración está configurado correctamente, probará la asociación entre el control personalizado y el diseñador personalizado.Once you've determined that the debugging environment is set up correctly, you'll 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 archivo de origen MarqueeControlRootDesigner en el Editor de código y coloque un punto de interrupción en la instrucción WriteLine.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.

    Se crea una nueva instancia de Visual Studio.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 en el menú archivo .You can easily find the solution by selecting Recent Projects from the File menu. El archivo de solución de MarqueeControlTest. sln se mostrará como el archivo usado más 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.

    La instancia de depuración de Visual Studio obtiene el foco y la ejecución se detiene en el punto de interrupción.The debugging instance of Visual Studio obtains focus and execution stops at your breakpoint. Presione F5 para continuar con la sesión de depuración.Press F5 to continue the debugging session.

En este punto, todo está en su lugar 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 artículo se centra en los detalles de la implementación de características del control y el diseñador.The remainder of this article concentrates on the details of implementing features of the control and the designer.

Implementar el control personalizadoImplement the 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, 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 interfaz IMarqueeWidget, Start y Stop enumeran cada control secundario y llaman a los métodos StartMarquee y StopMarquee, respectivamente, en cada control secundario que implementa IMarqueeWidget.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 los controles MarqueeBorder y MarqueeText depende del diseño, por lo que MarqueeControl invalida el método OnLayout y llama a PerformLayout en los controles secundarios de este tipo.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.

Esta es la extensión de las personalizaciones de MarqueeControl.This is the extent of the MarqueeControl customizations. Las características de tiempo de ejecución se implementan mediante los controles MarqueeBorder y MarqueeText, y las características en tiempo de diseño se implementan mediante las clases MarqueeBorderDesigner y MarqueeControlRootDesigner.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 archivo de origen de MarqueeControl en el Editor de código.Open the MarqueeControl source file in the Code Editor. Implemente los métodos Start y Stop.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
    

Crear un control secundario para el control personalizadoCreate a Child Control for Your Custom Control

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

  • MarqueeBorder: este control pinta un borde de "Lights" alrededor de los bordes.MarqueeBorder: This control paints a border of "lights" around its edges. Las luces parpadean en secuencia, por lo que parecen moverse alrededor del borde.The lights flash in sequence, so they appear to be moving around the border. La velocidad a la que el parpadeo de las luces se controla mediante una propiedad denominada UpdatePeriod.The speed at which the lights flash is controlled by a property called UpdatePeriod. Otras 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, controlan Cuándo se inicia y se detiene la animación.Two methods, called StartMarquee and StopMarquee, control when the animation starts and stops.

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

En tiempo de diseño, el MarqueeControlRootDesigner permite agregar estos dos tipos de control 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.

Las 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 a los MarqueeControl detectar 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, usará BackgroundWorker objetos del espacio de nombres System.ComponentModel.To implement the periodic animation feature, you will use BackgroundWorker objects from the System.ComponentModel namespace. Podría usar objetos de Timer, pero cuando haya muchos objetos IMarqueeWidget, es posible que el subproceso de la interfaz de usuario no pueda mantener el ritmo de 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. Agregue un nuevo elemento de clase al proyecto MarqueeControlLibrary.Add a new class item to the MarqueeControlLibrary project. Asigne al nuevo archivo de código fuente el nombre base "IMarqueeWidget".Give the new source file a base name of "IMarqueeWidget."

  2. Abra el archivo de origen de IMarqueeWidget 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 interfaz IMarqueeWidget para exponer dos métodos y una propiedad que manipula la animación de 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 elemento de control personalizado al proyecto MarqueeControlLibrary.Add a new Custom Control item to the MarqueeControlLibrary project. Asigne el nombre base "MarqueeText" al nuevo archivo de código fuente.Give the new source file a base name of "MarqueeText."

  5. Arrastre un componente de BackgroundWorker desde el cuadro de herramientas hasta el control MarqueeText.Drag a BackgroundWorker component from the Toolbox onto your MarqueeText control. Este componente permitirá que el control de MarqueeText se actualice de forma asincrónica.This component will allow the MarqueeText control to update itself asynchronously.

  6. En la ventana propiedades , establezca las propiedades WorkerReportsProgress y WorkerSupportsCancellation del componente BackgroundWorker en true.In the Properties window, set the BackgroundWorker component's WorkerReportsProgress and WorkerSupportsCancellation properties to true. Esta configuración permite al componente BackgroundWorker generar periódicamente el evento ProgressChanged 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, vea BackgroundWorker (componente).For more information, see BackgroundWorker Component.

  7. Abra el archivo de origen de MarqueeText en el Editor de código.Open the MarqueeText source file in the Code Editor. En la parte superior del archivo, importe los siguientes espacios de nombres: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.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 para que hereden de Label y para implementar la interfaz IMarqueeWidget: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 inicialícela en el constructor.Declare the instance variables that correspond to the exposed properties, and initialize them in the constructor. El campo isLit determina si el texto se va a pintar en el color dado por la propiedad LightColor.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
    
  10. Implementar la interfaz IMarqueeWidget.Implement the IMarqueeWidget interface.

    Los métodos StartMarquee y StopMarquee invocan los métodos RunWorkerAsync y CancelAsync del componente BackgroundWorker 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.

    Los atributos Category y Browsable se aplican a la propiedad UpdatePeriod de modo que aparezca en una sección personalizada de la ventana Propiedades denominada "Marquesina".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. Implementar los descriptores de acceso de propiedad.Implement the property accessors. Expondrá dos propiedades a los clientes: LightColor y DarkColor.You'll expose two properties to clients: LightColor and DarkColor. Los atributos Category y Browsable se aplican a estas propiedades, por lo que las propiedades aparecen en una sección personalizada de la ventana Propiedades denominada "Marquesina".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 los eventos ProgressChanged y DoWork del componente de BackgroundWorker.Implement the handlers for the BackgroundWorker component's DoWork and ProgressChanged events.

    El controlador de eventos DoWork suspende durante el número de milisegundos especificado por UpdatePeriod, a continuación, genera el evento ProgressChanged, 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 controlador de eventos ProgressChanged 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. Invalide el método OnPaint 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 secundario de MarqueeBorderCreate the MarqueeBorder Child Control

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

Dado que el control de MarqueeBorder puede tener controles secundarios, debe tener en cuenta los eventos de Layout.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 elemento de control personalizado al proyecto MarqueeControlLibrary.Add a new Custom Control item to the MarqueeControlLibrary project. Asigne al nuevo archivo de código fuente el nombre base "MarqueeBorder".Give the new source file a base name of "MarqueeBorder."

  2. Arrastre un componente de BackgroundWorker desde el cuadro de herramientas hasta el control MarqueeBorder.Drag a BackgroundWorker component from the Toolbox onto your MarqueeBorder control. Este componente permitirá que el control de MarqueeBorder se actualice de forma asincrónica.This component will allow the MarqueeBorder control to update itself asynchronously.

  3. En la ventana propiedades , establezca las propiedades WorkerReportsProgress y WorkerSupportsCancellation del componente BackgroundWorker en true.In the Properties window, set the BackgroundWorker component's WorkerReportsProgress and WorkerSupportsCancellation properties to true. Esta configuración permite al componente BackgroundWorker generar periódicamente el evento ProgressChanged 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, vea BackgroundWorker (componente).For more information, see BackgroundWorker Component.

  4. En la ventana propiedades , seleccione el botón eventos .In the Properties window, select the Events button. Adjunte Controladores para los eventos DoWork y ProgressChanged.Attach handlers for the DoWork and ProgressChanged events.

  5. Abra el archivo de origen de MarqueeBorder en el Editor de código.Open the MarqueeBorder source file in the Code Editor. En la parte superior del archivo, importe los siguientes espacios de nombres: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.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Drawing.Design
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  6. Cambie la declaración de MarqueeBorder para que hereden de Panel y para implementar la interfaz IMarqueeWidget.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 estado del control MarqueeBorder: MarqueeSpinDirection, que determina la dirección en la que las luces "giran" alrededor del borde y MarqueeLightShape, que determina la forma de las luces (cuadradas o circulares).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 de la declaración de clase MarqueeBorder.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 inicialícela 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.

    Los métodos StartMarquee y StopMarquee invocan los métodos RunWorkerAsync y CancelAsync del componente BackgroundWorker 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 control MarqueeBorder puede contener controles secundarios, el método StartMarquee enumera todos los controles secundarios y llama 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 método StopMarquee 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. Implementar los descriptores de acceso de propiedad.Implement the property accessors. El control MarqueeBorder 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 los eventos ProgressChanged y DoWork del componente de BackgroundWorker.Implement the handlers for the BackgroundWorker component's DoWork and ProgressChanged events.

    El controlador de eventos DoWork suspende durante el número de milisegundos especificado por UpdatePeriod, a continuación, genera el evento ProgressChanged, 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 controlador de eventos ProgressChanged incrementa la posición de la luz "base", desde la que se determina el estado claro o oscuro de las demás luces y llama al método Refresh para que el control se vuelva a dibujar.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 método IsLit 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 están "encendidas" se dibujan en el color dado por la propiedad LightColor, y las que son "oscuras" se dibujan en el color que proporciona la propiedad DarkColor.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 método DrawLight dibuja una luz con el color, la forma y la posición adecuados.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. Invalide los métodos OnLayout y OnPaint.Override the OnLayout and OnPaint methods.

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

    Dado que el método OnPaint depende de las dimensiones del control MarqueeBorder, debe llamarlo cada vez que cambie 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 llame 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 sombrear y filtrar propiedadesCreate a Custom Designer to Shadow and Filter Properties

La clase MarqueeControlRootDesigner 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 opera en el MarqueeControl, necesitará un diseñador personalizado que esté asociado específicamente al control MarqueeBorder.In addition to this designer, which operates on the MarqueeControl, you'll need a custom designer that is specifically associated with the MarqueeBorder control. Este diseñador proporciona un comportamiento personalizado que es adecuado en 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 "prevalece" y filtra ciertas propiedades en el control MarqueeBorder, cambiando 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.

La interceptación de 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 a un diseñador realizar un seguimiento del valor establecido por el usuario y, opcionalmente, pasar ese valor al componente que se está diseñando.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, las propiedades Visible y Enabled se separan mediante el MarqueeBorderDesigner, lo que evita que el usuario realice el control MarqueeBorder invisible 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, la propiedad Padding se quitará en tiempo de diseño, porque el control MarqueeBorder establece mediante programación el relleno en función del tamaño de las luces especificadas por la propiedad LightSize.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, las propiedades y los 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, siga estas reglas:When changing the public interface of a component using these methods, follow these rules:

  • Agregar o quitar elementos solo en los métodos PreFilterAdd or remove items in the PreFilter methods only

  • Modificar elementos existentes solo en los métodos de PostFilterModify existing items in the PostFilter methods only

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

  • Llame siempre a la implementación base en último lugar en los métodos PostFilterAlways call the base implementation last in the PostFilter methods

La adhesión a estas reglas garantiza que todos los diseñadores del entorno en tiempo de diseño tienen una vista coherente de todos los componentes que se están 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 clase ComponentDesigner proporciona un diccionario para administrar los valores de las propiedades con sombra, lo que evita la necesidad de crear variables de instancia específicas.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 con el botón secundario en la carpeta diseño y agregue una nueva clase.Right-click the Design folder and add a new class. Asigne al archivo de origen un nombre base de MarqueeBorderDesigner.Give the source file a base name of MarqueeBorderDesigner.

  2. Abra el archivo de origen de MarqueeBorderDesigner en el Editor de código.Open the MarqueeBorderDesigner source file in the Code Editor. En la parte superior del archivo, importe los siguientes espacios de nombres: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.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
  3. Cambie la declaración de MarqueeBorderDesigner para que hereden de ParentControlDesigner.Change the declaration of MarqueeBorderDesigner to inherit from ParentControlDesigner.

    Dado que el control MarqueeBorder puede contener controles secundarios, MarqueeBorderDesigner hereda de 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. Invalide 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 prevalecen sobre 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
    

Controlar cambios de componentesHandle Component Changes

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

A medida que los usuarios diseñan sus instancias de MarqueeControl, el diseñador raíz realizará un seguimiento de los 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 servicio cómodo, IComponentChangeService, para realizar el seguimiento de los cambios en el estado del componente.The design-time environment offers a convenient service, IComponentChangeService, for tracking changes to component state.

Para adquirir una referencia a este servicio, consulte el entorno con el método GetService.You acquire a reference to this service by querying the environment with the GetService method. Si la consulta se realiza correctamente, el diseñador puede adjuntar un controlador para el evento de ComponentChanged 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 la clase MarqueeControlRootDesigner, llamará al método Refresh 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 objeto de IMarqueeWidget se vuelva a dibujar de manera adecuada cuando se cambien las propiedades como la Size de sus elementos primarios.This will cause the IMarqueeWidget object to repaint itself appropriately when properties like its parent's Size are changed.

Para controlar los cambios de componentesTo handle component changes

  1. Abra el archivo de origen de MarqueeControlRootDesigner en el Editor de código e invalide el método Initialize.Open the MarqueeControlRootDesigner source file in the Code Editor and override the Initialize method. Llame a la implementación base de Initialize y consulte la 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 controlador de eventos OnComponentChanged.Implement the OnComponentChanged event handler. Pruebe el tipo del componente de envío y, si es un IMarqueeWidget, llame a su método Refresh.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 al diseñador personalizadoAdd 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. Los 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, vea 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 le permitirán ver el comportamiento de tiempo de ejecución del 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 MarqueeControlRootDesigner.These verbs will be added to MarqueeControlRootDesigner.

Cuando se invoca la prueba de ejecución , el controlador de eventos de verbo llamará al método StartMarquee en el MarqueeControl.When Run Test is invoked, the verb event handler will call the StartMarquee method on the MarqueeControl. Cuando se invoca la prueba de detención , el controlador de eventos de verbo llamará al método StopMarquee en el MarqueeControl.When Stop Test is invoked, the verb event handler will call the StopMarquee method on the MarqueeControl. La implementación de los métodos StartMarquee y StopMarquee llama a estos métodos en controles contenidos que implementan IMarqueeWidget, por lo que todos los controles de IMarqueeWidget incluidos también participarán 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 la clase MarqueeControlRootDesigner, agregue controladores de eventos denominados 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. Conecte 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 nuevos objetos de DesignerVerb y los agregará a esta colección en el método Initialize.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 personalizadoCreate a Custom UITypeEditor

Cuando se crea una experiencia personalizada en tiempo de diseño para los usuarios, a menudo es conveniente crear una interacción personalizada con el 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. Para ello, cree una UITypeEditor.You can accomplish this by creating a UITypeEditor.

El control MarqueeBorder expone varias propiedades en el ventana Propiedades.The MarqueeBorder control exposes several properties in the Properties window. Dos de estas propiedades, MarqueeSpinDirection y MarqueeLightShape se representan mediante 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, la propiedad MarqueeLightShape tendrá una clase UITypeEditor asociada.To illustrate the use of a UI type editor, the MarqueeLightShape property will have an associated UITypeEditor class.

Para crear un editor de tipos de interfaz de usuario personalizadoTo create a custom UI type editor

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

  2. En la definición de la clase MarqueeBorder, declare una clase llamada LightShapeEditor que derive 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. Declare una variable de instancia de IWindowsFormsEditorService 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 al entorno de diseño cómo mostrar el 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 de un objeto IWindowsFormsEditorService.This implementation queries the design environment for an IWindowsFormsEditorService object. Si se realiza correctamente, crea un LightShapeSelectionControl.If successful, it creates a LightShapeSelectionControl. Se invoca al método DropDownControl 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
    

Crear un control de vista para su UITypeEditor personalizadoCreate a View Control for your Custom UITypeEditor

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

Para crear un control de vista para el editor de tipos de interfaz de usuario personalizadoTo create a view control for your custom UI type editor

  1. Agregue un nuevo elemento de UserControl al proyecto de MarqueeControlLibrary.Add a new UserControl item to the MarqueeControlLibrary project. Asigne al nuevo archivo de código fuente el nombre base de LightShapeSelectionControl.Give the new source file a base name of LightShapeSelectionControl.

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

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

    Imports System.Windows.Forms.Design
    
    using System.Windows.Forms.Design;
    
  4. Implemente Click controladores de eventos para los controles squarePanel y circlePanel.Implement Click event handlers for the squarePanel and circlePanel controls. Estos métodos invocan CloseDropDown para finalizar la sesión de edición de UITypeEditor personalizada.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
    
  5. Declare una variable de instancia de IWindowsFormsEditorService denominada editorService.Declare an IWindowsFormsEditorService instance variable called editorService.

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

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
  7. En el constructor LightShapeSelectionControl, adjunte los controladores de eventos de Click a los eventos de Click de los controles squarePanel y circlePanel.In the LightShapeSelectionControl constructor, attach the Click event handlers to the squarePanel and circlePanel controls' Click events. Además, defina una sobrecarga de constructor que asigne el valor MarqueeLightShape del entorno de diseño al campo lightShapeValue.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
    
  8. En el método Dispose, desasocie los controladores de eventos Click.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
    
  9. 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 del método Dispose.Open the LightShapeSelectionControl.Designer.cs or LightShapeSelectionControl.Designer.vb file, and remove the default definition of the Dispose method.

  10. 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
    
  11. Invalide el método OnPaint .Override the OnPaint method. Esta implementación dibujará un cuadrado y un círculo rellenos.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ñadorTest your Custom Control in the Designer

En este momento, puede compilar el proyecto MarqueeControlLibrary.At this point, you can build the MarqueeControlLibrary project. Pruebe la implementación creando un control que herede de la clase MarqueeControl y usando en 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 del tipo de DemoMarqueeControl y la muestra en una instancia del tipo de MarqueeControlRootDesigner.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 la pestaña componentes de MarqueeControlLibrary . Verá los controles MarqueeBorder y MarqueeText 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 del control MarqueeBorder a la superficie de diseño DemoMarqueeControl.Drag an instance of the MarqueeBorder control onto the DemoMarqueeControl design surface. Acople este control de MarqueeBorder al control principal.Dock this MarqueeBorder control to the parent control.

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

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

  6. Haga clic con el botón secundario en el DemoMarqueeControl y, en el menú contextual, seleccione la opción Ejecutar prueba 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 en el formulario.Place two Button controls on the form. Asígneles el nombre startButton y stopButton, y cambie los valores de la propiedad Text a 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 controles de Button.Implement Click event handlers for both Button controls.

  10. En el cuadro de herramientas, abra la pestaña componentes de MarqueeControlTest . Verá el DemoMarqueeControl disponible 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 a la superficie de diseño de Form1 .Drag an instance of DemoMarqueeControl onto the Form1 design surface.

  12. En los controladores de eventos Click, invoque los métodos Start y Stop 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();
    }
    
  13. Establezca el proyecto de MarqueeControlTest como el proyecto de inicio y ejecútelo.Set the MarqueeControlTest project as the startup project and run it. Verá el formulario en el que se muestra el DemoMarqueeControl.You will see the form displaying your DemoMarqueeControl. Seleccione el botón iniciar para iniciar la animación.Select the Start button to start the animation. Debería ver el parpadeo del texto y las luces desplazadas alrededor del borde.You should see the text flashing and the lights moving around the border.

Pasos siguientesNext steps

En el MarqueeControlLibrary se 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 hacer que este ejemplo sea más sofisticado de varias maneras:You can make this sample more sophisticated in several ways:

  • Cambie los valores de propiedad para el DemoMarqueeControl en el diseñador.Change the property values for the DemoMarqueeControl in the designer. Agregue más MarqueBorder controles y acoplelos 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 configuraciones para los 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 "signo de neón" intermitente 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. Podría intentar sombrear más propiedades que Enabled y Visibley agregar nuevas propiedades.You could try shadowing more properties than Enabled and Visible, and you could add new properties. Agregue nuevos verbos de diseñador para simplificar las tareas comunes, como acoplar los controles secundarios.Add new designer verbs to simplify common tasks like docking child controls.

  • Licencia del MarqueeControl.License the MarqueeControl.

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

Vea tambiénSee also