Demonstra Passo a passo: Criando um controle Windows Forms que tira proveito dos recursos de tempo de Design de Visual Studio

A experiência de tempo de design para um controle personalizado pode ser aprimorada pela criação de um designer personalizado associado.

Esta explicação passo a passo ilustra como criar um designer personalizado para um controle personalizado. Você implementará uma MarqueeControl tipo e uma classe designer associada, chamado MarqueeControlRootDesigner.

O MarqueeControl tipo implementa uma exibição semelhante de um letreiro de cinema com luzes animadas e o texto piscando.

O designer para este controle interage com o ambiente de design para fornecer uma experiência de tempo de design personalizada. Com o designer personalizado, você pode montar um personalizado MarqueeControl implementação com luzes animadas e o texto piscando em várias combinações. Você pode usar o controle montado em um formulário como qualquer outro controle Windows Forms.

As tarefas ilustradas neste passo a passo incluem:

  • Criando o projeto

  • Criando um projeto de biblioteca de controle

  • Referência ao projeto de controle personalizados

  • Definição de um controle personalizado e seu Designer personalizado

  • Criando uma instância do seu controle personalizado

  • Configurar o projeto para a depuração em tempo de design.

  • A implementação de seu controle personalizado

  • Criando um controle filho para o controle personalizado

  • Criar o controle filho de MarqueeBorder

  • Criando um Designer personalizado para sombra e propriedades de filtro

  • Alterações de componentes de tratamento

  • A adição de verbos do Designer para seu Designer personalizado

  • Criando um UITypeEditor personalizado

  • Teste o seu controle personalizado no Designer

Quando terminar, seu controle personalizado será algo parecido com o seguinte:

Um possível arranjo de MarqueeControl

Para o código completo do exemplo, veja Como: Criar um controle Windows Forms que tira proveito dos recursos de tempo de Design.

ObservaçãoObservação

As caixas de diálogo e comandos de menu demonstradas podem ser diferentes daqueles descritos na Ajuda, dependendo das configurações ativas ou configurações de edição. Para alterar as configurações, escolha Import and Export Settings sobre o Ferramentas menu. Para obter mais informações, consulte Trabalhando com configurações.

Pré-requisitos

A fim de concluir este explicação passo a passo, será necessário:

  • Dê permissões suficientes para poder criar e executar projetos de aplicativos de Formulários do Windows no computador onde o Visual Studio está instalado.

Criando o projeto

A primeira etapa é criar um projeto de aplicativo. Você usará este projeto para criar o aplicativo que hospeda o controle personalizado.

Para criar o projeto

Criando um projeto de biblioteca de controle

A próxima etapa é criar o projeto de biblioteca de controle. Você irá criar um novo controle personalizado e seu designer personalizado correspondente.

Para criar o projeto de biblioteca de controle

  1. Adicione um projeto de biblioteca de controle de formulários do Windows para a solução. Nome do projeto "marqueecontrollibrary".

  2. Usando Solution Explorer, excluir o controle do padrão do projeto, excluindo o arquivo de origem chamado "UserControl1. cs" ou "UserControl1.vb", dependendo do idioma de sua escolha. Para obter mais informações, consulte Como: Remover, Deletar e excluir itens.

  3. Adicionar um novo UserControl item para o MarqueeControlLibrary project. Nomeie o novo arquivo de fonte base de "do"(MarqueeControl).

  4. Usando Solution Explorer, crie uma nova pasta de MarqueeControlLibrary project. Para obter mais informações, consulte Como: Adicionar novos itens de projeto. Nomeie a nova pasta "Design".

  5. Com o botão direito do Design pasta e adicionar uma nova classe. Nomeie o arquivo de origem base de "marqueecontrolrootdesigner".

  6. Você precisará usar tipos do assembly System.Design, então, adicionar essa referência para o MarqueeControlLibrary project.

    ObservaçãoObservação

    Para usar o conjunto de System.Design, o seu projeto deve usar a versão completa do.NET Framework, não o.NET Framework Client Profile. Para alterar a estrutura de destino, consulte Como: Destino de um específico.NET Framework versão ou perfil.

Referência ao projeto de controle personalizados

Você usará o MarqueeControlTest o projeto para testar o controle personalizado. O projeto de teste ficará ciente do controle personalizado quando você adiciona uma referência de projeto para o MarqueeControlLibrary assembly.

A referência do projeto de controle personalizado

  • No MarqueeControlTest de projeto, adicione uma referência para o MarqueeControlLibrary assembly. Certifique-se de usar o projetos guia o Add Reference caixa de diálogo em vez de fazer referência a MarqueeControlLibrary assembly diretamente.

Definição de um controle personalizado e seu Designer personalizado

Seu controle personalizado será derivam de UserControl classe. Isso permite que seu controle contêm outros controles e ele oferece muita funcionalidade padrão de seu controle.

Seu controle personalizado terá um designer personalizado associado. Isso lhe permite criar uma experiência de design exclusivo desenvolvida especificamente para seu controle personalizado.

Você associa o controle seu designer usando o DesignerAttribute classe. Porque você está desenvolvendo o comportamento de tempo de design inteiro do seu controle personalizado, o designer personalizado implementará a IRootDesigner interface.

Para definir um controle personalizado e seu designer personalizado

  1. Abrir o MarqueeControl o arquivo de origem na O Editor de código. Na parte superior do arquivo, importe os namespaces a seguir:

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  2. Adicionar o DesignerAttribute para o MarqueeControl declaração de classe. Isto associa o controle personalizado com seu criador.

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
        [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
        public class MarqueeControl : UserControl
        {
    
  3. Abrir o MarqueeControlRootDesigner o arquivo de origem na O Editor de código. Na parte superior do arquivo, importe os namespaces a seguir:

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing.Design
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing.Design;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  4. Alterar a declaração do MarqueeControlRootDesigner para herdar a partir de DocumentDesigner classe. Aplicar o ToolboxItemFilterAttribute para especificar a interação do designer com o caixa de ferramentas.

    Notaa definição para o MarqueeControlRootDesigner classe tenha sido incluído em um namespace chamado "MarqueeControlLibrary.Design". Essa declaração coloca o designer em um namespace especial reservada para tipos de design.

    Namespace MarqueeControlLibrary.Design
    
        <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
        ToolboxItemFilterType.Require), _
        ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
        ToolboxItemFilterType.Require)> _
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeControlRootDesigner
            Inherits DocumentDesigner
    
    namespace MarqueeControlLibrary.Design
    {
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
        [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeControlRootDesigner : DocumentDesigner
        {
    
  5. Definir o construtor para o MarqueeControlRootDesigner classe. Inserir uma WriteLine a instrução no corpo de construtor. Isso será útil para fins de depuração.

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

Criando uma instância do seu controle personalizado

Para observar o comportamento de tempo de design personalizado de seu controle, você irá colocar uma instância do controle no formulário em MarqueeControlTest project.

Para criar uma instância do seu controle personalizado

  1. Adicionar um novo UserControl item para o MarqueeControlTest project. Nomeie o novo arquivo de fonte base de "demomarqueecontrol".

  2. Abrir o DemoMarqueeControl de arquivo na O Editor de código. Na parte superior do arquivo, importar o MarqueeControlLibrary namespace:

Imports MarqueeControlLibrary
using MarqueeControlLibrary;
  1. Alterar a declaração do DemoMarqueeControl para herdar a partir de MarqueeControl classe.

  2. Crie o projeto.

  3. Abra Form1 no Windows Forms Designer.

  4. Encontrar o MarqueeControlTest componentes guia de Toolbox e abri-lo. Arraste um DemoMarqueeControl da caixa de ferramentas para seu formulário.

  5. Crie o projeto.

Configurar o projeto para a depuração em tempo de design.

Quando você estiver desenvolvendo uma experiência personalizada de tempo de design, será necessário para depurar os controles e componentes. Há uma maneira simples de configurar o seu projeto para permitir a depuração em tempo de design. Para obter mais informações, consulte Demonstra Passo a passo: Controles de formulários do Windows personalizados de depuração em tempo de Design.

Para configurar o projeto para depuração de tempo de design

  1. Com o botão direito do MarqueeControlLibrary de projeto e selecione Propriedades.

  2. Em "Páginas de propriedade de MarqueeControlLibrary" caixa de diálogo, selecione o Debug página.

  3. No Start Action seção, selecione Iniciar programa externo. Você será então a depuração de uma instância separada do Visual Studio, clique nas reticências (Captura de tela de VisualStudioEllipsesButton) o botão para procurar o Visual Studio IDE. O nome do arquivo executável é devenv. exe e se você instalou o local padrão, seu caminho é %programfiles%\Microsoft 9.0\Common7\IDE\devenv.exe de Visual Studio.

  4. Clique OK para fechar a caixa de diálogo.

  5. Com o botão direito do MarqueeControlLibrary de projeto e selecione "definir como projeto de inicialização" Para habilitar essa configuração de depuração.

Ponto de Verificação

Agora você está pronto para depurar o comportamento de tempo de design do seu controle personalizado. Depois de ter determinado que o ambiente de depuração está configurado corretamente, você testará a associação entre o controle personalizado e o designer personalizado.

Para testar o ambiente de depuração e a associação de designer

  1. Abrir o MarqueeControlRootDesigner o arquivo de origem no O Editor de código e coloque um ponto de interrupção no WriteLine instrução.

  2. Pressione F5 para iniciar a depuração da solução. Observe que uma nova instância do Visual Studio é criada.

  3. Na nova instância de Visual Studio, abrir "marqueecontroltest" solução. Você poderá localizar facilmente a solução selecionando Projetos recentes da arquivo menu. "MarqueeControlTest.sln" o arquivo de solução será listado como o arquivo usado recentemente.

  4. Abrir o DemoMarqueeControl no designer. Observe que a instância de depuração do Visual Studio adquire o foco e a execução pára no ponto de interrupção. Pressione F5 para continuar a sessão de depuração.

Neste ponto, tudo o que é local para você desenvolver e depurar seu controle personalizado e seu designer personalizado associado. O restante desta explicação passo a passo irá se concentrar nos detalhes de implementação de recursos do controle e o designer.

A implementação de seu controle personalizado

O MarqueeControl é um UserControl com um pouco de personalização. Ela apresenta dois métodos: Start, que inicia a animação do letreiro digital, e Stop, que interrompe a animação. Porque o MarqueeControl contém os controles filho que implementam o IMarqueeWidget interface, Start e Stop enumerar cada controle filho e a chamada a StartMarquee e StopMarquee de métodos, respectivamente, em cada criança controlam que implementa IMarqueeWidget.

A aparência da MarqueeBorder e MarqueeText controles é dependente de layout, para que MarqueeControl substitui o OnLayout método e chamadas PerformLayout nos controles de filho deste tipo.

Esta é a extensão da MarqueeControl personalizações. Os recursos de tempo de execução são implementados pela MarqueeBorder e MarqueeText os recursos de tempo de design e controles são implementados pela MarqueeBorderDesigner e MarqueeControlRootDesigner classes.

Para implementar o seu controle personalizado

  1. Abrir o MarqueeControl o arquivo de origem na O Editor de código. Implementar a Start e Stop métodos.

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

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

Criando um controle filho para o controle personalizado

O MarqueeControl hospedará dois tipos de controle filho: o MarqueeBorder controle e o MarqueeText de controle.

  • MarqueeBorder: Esse controle pinta uma borda de "luminárias" ao redor de suas bordas. As luzes piscam em seqüência, para que elas apareçam mover ao redor da borda. A velocidade com que as luzes piscam é controlada por uma propriedade chamada UpdatePeriod. Várias outras propriedades personalizadas determinam outros aspectos da aparência do controle. Dois métodos, chamados StartMarquee e StopMarquee, o controle quando a animação começa e pára.

  • MarqueeText: Esse controle pinta uma seqüência de caracteres piscando. Como o MarqueeBorder o controle, a velocidade na qual o texto pisca é controlada pelo UpdatePeriod propriedade. O MarqueeText também tem um controle de StartMarquee e StopMarquee métodos em comum com o MarqueeBorder de controle.

Em tempo de design, o MarqueeControlRootDesigner permite que esses tipos de controle de dois a serem adicionados a uma MarqueeControl em qualquer combinação.

Recursos comuns dos dois controles são fatorados uma interface chamada IMarqueeWidget. Isso permite que o MarqueeControl para descobrir quaisquer controles filho relacionado de letreiro e dar-lhes tratamento especial.

Para implementar o recurso de animação periódicos, use BackgroundWorker objetos a partir de System.ComponentModel namespace. Você poderia usar Timer objetos, mas quando muitos IMarqueeWidget objetos estiverem presentes, o único thread de interface do usuário pode não conseguir acompanhar a animação.

Para criar um controle filho para o controle personalizado

  1. Adicionar um novo item de classe para o MarqueeControlLibrary project. Nomeie o novo arquivo de fonte base de "imarqueewidget".

  2. Abrir o IMarqueeWidget o arquivo de origem na O Editor de código e altere a declaração de class para 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. Adicione o seguinte código para o IMarqueeWidget interface para expor uma propriedade que manipulam a animação do letreiro digital e dois métodos:

    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
       ' This method starts the animation. If the control can 
       ' contain other classes that implement IMarqueeWidget as
       ' children, the control should call StartMarquee on all
       ' its IMarqueeWidget child controls.
       Sub StartMarquee()
    
       ' This method stops the animation. If the control can 
       ' contain other classes that implement IMarqueeWidget as
       ' children, the control should call StopMarquee on all
       ' its IMarqueeWidget child controls.
       Sub StopMarquee()
    
       ' This method specifies the refresh rate for the animation,
       ' in milliseconds.
       Property UpdatePeriod() As Integer
    
    End Interface
    
    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
        // This method starts the animation. If the control can 
        // contain other classes that implement IMarqueeWidget as
        // children, the control should call StartMarquee on all
        // its IMarqueeWidget child controls.
        void StartMarquee();
    
        // This method stops the animation. If the control can 
        // contain other classes that implement IMarqueeWidget as
        // children, the control should call StopMarquee on all
        // its IMarqueeWidget child controls.
        void StopMarquee();
    
        // This method specifies the refresh rate for the animation,
        // in milliseconds.
        int UpdatePeriod
        {
            get;
            set;
        }
    }
    
  4. Adicionar um novo Controle personalizado item para o MarqueeControlLibrary project. Nomeie o novo arquivo de fonte base de "marqueetext".

  5. Arrastar um BackgroundWorker componente da Toolbox para seu MarqueeText de controle. Esse componente permitirá que o MarqueeText o controle se atualize assincronicamente.

  6. Na janela Properties, defina a BackgroundWorker do componente WorkerReportsProgess e WorkerSupportsCancellation Propriedades para true. Essas configurações permitem que o BackgroundWorker componente aumentar periodicamente o ProgressChanged eventos e para cancelar atualizações assíncrona. Para obter mais informações, consulte Componente BackgroundWorker.

  7. Abrir o MarqueeText o arquivo de origem na O Editor de código. Na parte superior do arquivo, importe os namespaces a seguir:

    Imports System
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  8. Alterar a declaração do MarqueeText para herdar de Label e para implementar a IMarqueeWidget interface:

    <ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeText
        Inherits Label
        Implements IMarqueeWidget
    
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    public partial class MarqueeText : Label, IMarqueeWidget
    {
    
  9. Declare as variáveis de instância que correspondem às propriedades expostas e inicializá-las no construtor. O isLit campo determina se o texto será pintado na cor fornecida pelo LightColor propriedade.

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

    O StartMarquee e StopMarquee a invocação de métodos de BackgroundWorker do componente RunWorkerAsync e CancelAsync métodos para iniciar e parar a animação.

    O Category e Browsable atributos são aplicados para a UpdatePeriod propriedade, então ele aparece em uma seção personalizada da janela de propriedades chamada "marca".

    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' Start the updating thread and pass it the UpdatePeriod.
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub
    
    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' Stop the updating thread.
        Me.backgroundWorker1.CancelAsync()
    End Sub
    
    
    <Category("Marquee"), Browsable(True)> _
    Public Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod
    
        Get
            Return Me.updatePeriodValue
        End Get
    
        Set(ByVal Value As Integer)
            If Value > 0 Then
                Me.updatePeriodValue = Value
            Else
                Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0")
            End If
        End Set
    
    End Property
    
    public virtual void StartMarquee()
    {
        // Start the updating thread and pass it the UpdatePeriod.
        this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
    }
    
    public virtual void StopMarquee()
    {
        // Stop the updating thread.
        this.backgroundWorker1.CancelAsync();
    }
    
    [Category("Marquee")]
    [Browsable(true)]
    public int UpdatePeriod
    {
        get
        {
            return this.updatePeriodValue;
        }
    
        set
        {
            if (value > 0)
            {
                this.updatePeriodValue = value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
            }
        }
    }
    
  11. Implemente acessadores de propriedade. Você expõe duas propriedades para clientes: LightColor e DarkColor. O Category e Browsable atributos são aplicados a essas propriedades, para que as propriedades são exibidas em uma seção personalizada da janela Propriedades chamada "marca".

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

    O DoWork "dormência" do manipulador de eventos para o número de milissegundos especificado em UpdatePeriod aumenta a ProgressChanged evento, até que seu código deixa a animação chamando CancelAsync.

    O ProgressChanged manipulador de eventos alterna o texto entre o seu estado claro e escuro para dar a aparência de piscar.

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

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

Criar o controle filho de MarqueeBorder

O MarqueeBorder controle é um pouco mais sofisticada de MarqueeText de controle. Ele tem mais propriedades e a animação do OnPaint método é mais envolvido. Em princípio, é bem semelhante do MarqueeText de controle.

Porque o MarqueeBorder controle pode ter controles filho, ele precisa estar ciente das Layout eventos.

Para criar o controle de MarqueeBorder

  1. Adicionar um novo Controle personalizado item para o MarqueeControlLibrary project. Nomeie o novo arquivo de fonte base de "marqueeborder".

  2. Arrastar um BackgroundWorker componente da Toolbox para seu MarqueeBorder de controle. Esse componente permitirá que o MarqueeBorder o controle se atualize assincronicamente.

  3. Na janela Properties, defina a BackgroundWorker do componente WorkerReportsProgess e WorkerSupportsCancellation Propriedades para true. Essas configurações permitem que o BackgroundWorker componente aumentar periodicamente o ProgressChanged eventos e para cancelar atualizações assíncrona. Para obter mais informações, consulte Componente BackgroundWorker.

  4. Na janela Propriedades, clique no botão Eventos. Anexar manipuladores para o DoWork e ProgressChanged eventos.

  5. Abrir o MarqueeBorder o arquivo de origem na O Editor de código. Na parte superior do arquivo, importe os namespaces a seguir:

    Imports System
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Drawing
    Imports System.Drawing.Design
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Threading;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  6. Alterar a declaração do MarqueeBorder para herdar de Panel e para implementar a IMarqueeWidget interface.

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _
    ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeBorder
        Inherits Panel
        Implements IMarqueeWidget
    
    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {
    
  7. Declare duas enumerações para gerenciar o MarqueeBorder estado do controle: MarqueeSpinDirection, que determina a direção em que as luzes é "giram" ao redor da borda e MarqueeLightShape, que determina a forma das luzes (quadradas ou circulares). Essas declarações antes de colocar o MarqueeBorder declaração de classe.

    ' This defines the possible values for the MarqueeBorder
    ' control's SpinDirection property.
    Public Enum MarqueeSpinDirection
       CW
       CCW
    End Enum
    
    ' This defines the possible values for the MarqueeBorder
    ' control's LightShape property.
    Public Enum MarqueeLightShape
        Square
        Circle
    End Enum
    
    // This defines the possible values for the MarqueeBorder
    // control's SpinDirection property.
    public enum MarqueeSpinDirection
    {
        CW,
        CCW
    }
    
    // This defines the possible values for the MarqueeBorder
    // control's LightShape property.
    public enum MarqueeLightShape
    {
        Square,
        Circle
    }
    
  8. Declare as variáveis de instância que correspondem às propriedades expostas e inicializá-las no construtor.

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

    O StartMarquee e StopMarquee a invocação de métodos de BackgroundWorker do componente RunWorkerAsync e CancelAsync métodos para iniciar e parar a animação.

    Porque o MarqueeBorder controle pode conter controles filho, o StartMarquee método enumera todos os controles filho e chamadas StartMarquee naqueles que implementam IMarqueeWidget. O StopMarquee método tem uma implementação semelhante.

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

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

    O DoWork "dormência" do manipulador de eventos para o número de milissegundos especificado em UpdatePeriod aumenta a ProgressChanged evento, até que seu código deixa a animação chamando CancelAsync.

    O ProgressChanged manipulador de eventos incrementa a posição de "base" luz, da qual o estado de claro/escuro das luzes de é determinado, e chama o Refresh método para fazer com que o controle se redesenhar.

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

    O IsLit método determina a cor de uma luz em uma determinada posição. Luzes são "aceso" são desenhadas na cor fornecida pelo LightColor propriedade e aqueles que são "escuro" são desenhadas na cor fornecida pelo DarkColor propriedade.

    O DrawLight método desenha uma luz usando a cor apropriada, forma e posição.

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

    O OnPaint método desenha as luzes de arestas da MarqueeBorder de controle.

    Porque o OnPaint método depende das dimensões da MarqueeBorder o controle, você precisa chamá-la sempre que as alterações de layout. Para conseguir isso, substituir OnLayout e chame Refresh.

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

Criando um Designer personalizado para sombra e propriedades de filtro

O MarqueeControlRootDesigner classe fornece a implementação para o designer de raiz. Além de para esse designer, que opera na MarqueeControl, você precisará de um designer personalizado que está especificamente associado com o MarqueeBorder de controle. Esse designer fornece um comportamento personalizado adequado no contexto do designer personalizado raiz.

Especificamente, o MarqueeBorderDesigner será "sombra" e certas propriedades de filtro de MarqueeBorder o controle, alterar a sua interação com o ambiente de design.

Interceptação de chamadas para o acessador de propriedade do componente é conhecida como "sombrear". Ele permite que um designer controlar o valor definido pelo usuário e, opcionalmente, passar esse valor para o componente que está sendo criado.

Neste exemplo, o Visible e Enabled Propriedades serão ser sombreadas pela MarqueeBorderDesigner, que impede o usuário a fazer a MarqueeBorder controle invisível ou desativado durante o tempo de design.

Designers também podem adicionar e remover propriedades. Neste exemplo, o Padding propriedade será removida em tempo de design, porque o MarqueeBorder controle programaticamente define o preenchimento com base no tamanho das luzes especificado pelo LightSize propriedade.

A classe base para MarqueeBorderDesigner é ComponentDesigner, que tem métodos que podem alterar os atributos, propriedades e eventos expostos por um controle em tempo de design:

Ao alterar a interface pública de um componente usando esses métodos, você deve seguir estas regras:

  • Adicionar ou remover itens do PreFilter apenas métodos

  • Modificar itens existentes na PostFilter apenas métodos

  • Sempre chamar a implementação base primeiro o PreFilter métodos

  • Sempre chamar a implementação base pela última vez o PostFilter métodos

Aderir a essas regras garante que todos os designers no ambiente de tempo de design tem uma visão consistente de todos os componentes que está sendo criado.

O ComponentDesigner classe fornece um dicionário para gerenciar os valores das propriedades sombreados, que libera você da necessidade de criar variáveis de instância específica.

Para criar um designer personalizado para propriedades de sombra e filtro

  1. Com o botão direito do Design pasta e adicionar uma nova classe. Nomeie o arquivo de origem base de "marqueeborderdesigner".

  2. Abrir o MarqueeBorderDesigner o arquivo de origem na O Editor de código. Na parte superior do arquivo, importe os namespaces a seguir:

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  3. Alterar a declaração do MarqueeBorderDesigner para herdar de ParentControlDesigner.

    Porque o MarqueeBorder controle pode conter controles filho, MarqueeBorderDesigner herda de ParentControlDesigner, que manipula a interação de pai-filho.

    Namespace MarqueeControlLibrary.Design
    
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
    namespace MarqueeControlLibrary.Design
    {
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
  4. Substituir a implementação base do PreFilterProperties.

    Protected Overrides Sub PreFilterProperties( _
    ByVal properties As IDictionary)
    
        MyBase.PreFilterProperties(properties)
    
        If properties.Contains("Padding") Then
            properties.Remove("Padding")
        End If
    
        properties("Visible") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Visible"), PropertyDescriptor), _
        New Attribute(-1) {})
    
        properties("Enabled") = _
        TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
        CType(properties("Enabled"), _
        PropertyDescriptor), _
        New Attribute(-1) {})
    
    End Sub
    
    protected override void PreFilterProperties(IDictionary properties)
    {
        base.PreFilterProperties(properties);
    
        if (properties.Contains("Padding"))
        {
            properties.Remove("Padding");
        }
    
        properties["Visible"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Visible"],
            new Attribute[0]);
    
        properties["Enabled"] = TypeDescriptor.CreateProperty(
            typeof(MarqueeBorderDesigner),
            (PropertyDescriptor)properties["Enabled"],
            new Attribute[0]);
    }
    
  5. Implementar a Enabled e Visible Propriedades. Essas implementações sombra as propriedades do controle.

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

Alterações de componentes de tratamento

O MarqueeControlRootDesigner classe fornece a experiência de tempo de design personalizada para seu MarqueeControl instâncias. A maioria da funcionalidade de tempo de design é herdada do DocumentDesigner classe; seu código irá implementar duas personalizações específicas: manipulação de alterações do componente e, em seguida, adicionar verbos do designer.

Como os usuários a projetar seus MarqueeControl instâncias, o seu designer raiz irá controlar alterações para o MarqueeControl e seus controles filho. O ambiente de tempo de design oferece um serviço conveniente, IComponentChangeService, para monitoramento de alterações para o estado do componente.

Você adquire uma referência a esse serviço consultando o ambiente com o GetService método. Se a consulta for bem-sucedida, o designer pode anexar um manipulador para o ComponentChanged eventos e executar quaisquer tarefas são necessárias para manter um estado consistente no tempo de design.

No caso do MarqueeControlRootDesigner classe, você chamará o Refresh método em cada IMarqueeWidget objeto contido pela MarqueeControl. Isso fará com que o IMarqueeWidget objeto a redesenhar próprio adequadamente quando as propriedades como seu pai Size são alteradas.

Para lidar com alterações de componentes

  1. Abrir o MarqueeControlRootDesigner o arquivo de origem na O Editor de código e substituir o Initialize método. Chamar a implementação base do Initialize e consultar a IComponentChangeService.

    MyBase.Initialize(component)
    
    Dim cs As IComponentChangeService = _
    CType(GetService(GetType(IComponentChangeService)), _
    IComponentChangeService)
    
    If (cs IsNot Nothing) Then
        AddHandler cs.ComponentChanged, AddressOf OnComponentChanged
    End If
    
    base.Initialize(component);
    
    IComponentChangeService cs =
        GetService(typeof(IComponentChangeService)) 
        as IComponentChangeService;
    
    if (cs != null)
    {
        cs.ComponentChanged +=
            new ComponentChangedEventHandler(OnComponentChanged);
    }
    
  2. Implementar a OnComponentChanged manipulador de eventos. Testar o tipo do componente de envio, e se ele for um IMarqueeWidget, ligue para seu Refresh método.

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

A adição de verbos do Designer para seu Designer personalizado

Um verbo designer é um comando de menu vinculado a um manipulador de eventos. Verbos do Designer são adicionados ao menu de atalho de um componente em tempo de design. Para obter mais informações, consulte DesignerVerb.

Você irá adicionar dois verbos do designer para os designers: Executar o teste e Parar teste. Esses verbos permitirá que você visualizar o comportamento de tempo de execução do MarqueeControl em tempo de design. Esses verbos serão adicionados a MarqueeControlRootDesigner.

Quando Executar teste é invocado, o manipulador de eventos do verbo chamará o StartMarquee método o MarqueeControl. Quando Parar teste é invocado, o manipulador de eventos do verbo chamará o StopMarquee método o MarqueeControl. A implementação da StartMarquee e StopMarquee métodos chamar esses métodos de controles contidos que implementam IMarqueeWidget, para qualquer contidos IMarqueeWidget controles também participam do Test.

Para adicionar verbos do designer para os designers personalizados

  1. No MarqueeControlRootDesigner classe, adicionar manipuladores de evento chamados OnVerbRunTest e OnVerbStopTest.

    Private Sub OnVerbRunTest( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
        c.Start()
    
    End Sub
    
    Private Sub OnVerbStopTest( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
        c.Stop()
    
    End Sub
    
    private void OnVerbRunTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Start();
    }
    
    private void OnVerbStopTest(object sender, EventArgs e)
    {
        MarqueeControl c = this.Control as MarqueeControl;
    
        c.Stop();
    }
    
  2. Conecte esses manipuladores de eventos para os verbos do designer correspondentes. MarqueeControlRootDesignerherda um DesignerVerbCollection de sua classe de base. Você criará dois novos DesignerVerb objetos e adicioná-los para essa coleção na Initialize método.

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

Criando um UITypeEditor personalizado

Quando você cria uma experiência de tempo de design personalizada para usuários, muitas vezes é desejável para criar uma interação personalizada com a janela Propriedades. Você pode fazer isso criando uma UITypeEditor. Para obter mais informações, consulte Como: Criar um Editor UI de tipo.

O MarqueeBorder controle expõe várias propriedades na janela Propriedades. Duas dessas propriedades, MarqueeSpinDirection e MarqueeLightShape são representados por enumerações. Para ilustrar o uso de um editor UI de tipo, o MarqueeLightShape propriedade terá um associado UITypeEditor classe.

Para criar um tipo de interface do usuário personalizado editor

  1. Abrir o MarqueeBorder o arquivo de origem na O Editor de código.

  2. Na definição de MarqueeBorder classe, declare uma classe chamada LightShapeEditor que deriva de UITypeEditor.

    ' This class demonstrates the use of a custom UITypeEditor. 
    ' It allows the MarqueeBorder control's LightShape property
    ' to be changed at design time using a customized UI element
    ' that is invoked by the Properties window. The UI is provided
    ' by the LightShapeSelectionControl class.
    Friend Class LightShapeEditor
        Inherits UITypeEditor
    
    // This class demonstrates the use of a custom UITypeEditor. 
    // It allows the MarqueeBorder control's LightShape property
    // to be changed at design time using a customized UI element
    // that is invoked by the Properties window. The UI is provided
    // by the LightShapeSelectionControl class.
    internal class LightShapeEditor : UITypeEditor
    {
    
  3. Declarar um IWindowsFormsEditorService variável de instância chamada editorService.

    Private editorService As IWindowsFormsEditorService = Nothing
    
    private IWindowsFormsEditorService editorService = null;
    
  4. Substitua o método GetEditStyle. Essa implementação retorna DropDown, que informa ao ambiente de design como exibir o LightShapeEditor.

            Public Overrides Function GetEditStyle( _
            ByVal context As System.ComponentModel.ITypeDescriptorContext) _
            As UITypeEditorEditStyle
                Return UITypeEditorEditStyle.DropDown
            End Function
    
    
    public override UITypeEditorEditStyle GetEditStyle(
    System.ComponentModel.ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    
  5. Substitua o método EditValue. Essa implementação consultará o ambiente de design para um IWindowsFormsEditorService objeto. Se for bem-sucedido, ele cria um LightShapeSelectionControl. O DropDownControl método é chamado para iniciar o LightShapeEditor. O valor de retorno desta chamada é retornado para o ambiente de design.

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

A criação de um controle de exibição para o UITypeEditor personalizado

  1. O MarqueeLightShape propriedade oferece suporte a dois tipos de formas de luz: Square e Circle. Você irá criar um controle personalizado usado unicamente para fins de graficamente exibindo esses valores na janela Propriedades. Esse controle personalizado será usado pelo seu UITypeEditor para interagir com a janela de propriedades.

Para criar um controle de exibição para a interface do usuário personalizada do editor de tipo

  1. Adicionar um novo UserControl item para o MarqueeControlLibrary project. Nomeie o novo arquivo de fonte base de "lightshapeselectioncontrol".

  2. Arraste dois Panel controla a partir de Toolbox até o LightShapeSelectionControl. Nomeá-los squarePanel e circlePanel. Organizá-los lado a lado. Definir o Size propriedade dos Panel controles (60, 60). Definir o Location propriedade da squarePanel controle (8, 10). Definir o Location propriedade da circlePanel controle (80, 10). Finalmente, defina a Size propriedade da LightShapeSelectionControl para (150, 80).

  3. Abrir o LightShapeSelectionControl o arquivo de origem na O Editor de código. Na parte superior do arquivo, importar o System.Windows.Forms.Design namespace:

Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
  1. Implementar Click manipuladores de eventos para o squarePanel e circlePanel controles. Esses métodos invocar CloseDropDown para finalizar o custom UITypeEditor edição da sessão.

    Private Sub squarePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Me.lightShapeValue = MarqueeLightShape.Square
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()
    
    End Sub
    
    
    Private Sub circlePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    
        Me.lightShapeValue = MarqueeLightShape.Circle
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()
    
    End Sub
    
            private void squarePanel_Click(object sender, EventArgs e)
            {
                this.lightShapeValue = MarqueeLightShape.Square;
    
                this.Invalidate( false );
    
                this.editorService.CloseDropDown();
            }
    
            private void circlePanel_Click(object sender, EventArgs e)
            {
                this.lightShapeValue = MarqueeLightShape.Circle;
    
                this.Invalidate( false );
    
                this.editorService.CloseDropDown();
            }
    
  2. Declarar um IWindowsFormsEditorService variável de instância chamada editorService.

Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
  1. Declarar um MarqueeLightShape variável de instância chamada lightShapeValue.

    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
  2. No LightShapeSelectionControl construtor, anexe o Click manipuladores de eventos para o squarePanel e circlePanel dos controles Clickeventos. Além disso, definir uma sobrecarga de construtor atribui o MarqueeLightShape valor a partir do ambiente de design para o lightShapeValue campo.

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

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
    
            ' Be sure to unhook event handlers
            ' to prevent "lapsed listener" leaks.
            RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click
            RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click
    
            If (components IsNot Nothing) Then
                components.Dispose()
            End If
    
        End If
        MyBase.Dispose(disposing)
    End Sub
    
            protected override void Dispose( bool disposing )
            {
                if( disposing )
                {
                    // Be sure to unhook event handlers
                    // to prevent "lapsed listener" leaks.
                    this.squarePanel.Click -= 
                        new EventHandler(squarePanel_Click);
                    this.circlePanel.Click -= 
                        new EventHandler(circlePanel_Click);
    
                    if(components != null)
                    {
                        components.Dispose();
                    }
                }
                base.Dispose( disposing );
            }
    
  4. Em Solution Explorer, clique na Mostrar todos os arquivos botão. Abra o arquivo LightShapeSelectionControl.Designer.cs ou LightShapeSelectionControl.Designer.vb e remova a definição padrão de Dispose método.

  5. Implementar a LightShape propriedade.

    ' LightShape is the property for which this control provides
    ' a custom user interface in the Properties window.
    Public Property LightShape() As MarqueeLightShape
    
        Get
            Return Me.lightShapeValue
        End Get
    
        Set(ByVal Value As MarqueeLightShape)
            If Me.lightShapeValue <> Value Then
                Me.lightShapeValue = Value
            End If
        End Set
    
    End Property
    
            // LightShape is the property for which this control provides
            // a custom user interface in the Properties window.
            public MarqueeLightShape LightShape
            {
                get
                {
                    return this.lightShapeValue;
                }
    
                set
                {
                    if( this.lightShapeValue != value )
                    {
                        this.lightShapeValue = value;
                    }
                }
            }
    
  6. Substitua o método OnPaint. Essa implementação será desenhar um quadrado preenchido e o círculo. Ele também irá realçar o valor selecionado desenhando uma borda ao redor de uma forma ou de outro.

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

Teste o seu controle personalizado no Designer

Neste ponto, você pode criar o MarqueeControlLibrary project. Testar a implementação, criando um controle que herda do MarqueeControl classe e usá-lo em um formulário.

Para criar uma implementação personalizada do MarqueeControl

  1. Abra DemoMarqueeControl no Windows Forms Designer. Isso cria uma instância da DemoMarqueeControl Digite e o exibe em uma instância da MarqueeControlRootDesigner tipo.

  2. No caixa de ferramentas, abra o MarqueeControlLibrary componentes guia. Você verá o MarqueeBorder e MarqueeText controles disponíveis para seleção.

  3. Arraste uma ocorrência da MarqueeBorder controlar até o DemoMarqueeControl superfície de design. Encaixar isso MarqueeBorder o controle para o controle pai.

  4. Arraste uma ocorrência da MarqueeText controlar até o DemoMarqueeControl superfície de design.

  5. Crie a solução.

  6. Com o botão direito do DemoMarqueeControl e a partir do menu de atalho, selecione o Executar o teste opção para iniciar a animação. Clique em Parar teste para interromper a animação.

  7. Abrir Form1 no modo Design.

  8. Coloque dois Button controles no formulário. Nomeá-los startButton e stopButtone alterar o Text os valores de propriedade para iniciar e parar, respectivamente.

  9. Implementar Click manipuladores de eventos para ambos Button controles.

  10. No caixa de ferramentas, abra o MarqueeControlTest componentes guia. Você verá o DemoMarqueeControl disponíveis para seleção.

  11. Arraste uma ocorrência de DemoMarqueeControl até o Form1 a superfície de design.

  12. No Click manipuladores de eventos, chamar o Start e Stop métodos o DemoMarqueeControl.

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

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

private void stopButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Stop();
}
  1. Definir o MarqueeControlTest do projeto como o projeto de inicialização e executar o proprietário. Você verá o formulário exibindo sua DemoMarqueeControl. Clique o Iniciar o botão para iniciar a animação. Você deve ver o texto piscando e as luzes movendo-se ao redor da borda.

Próximas etapas

O MarqueeControlLibrary demonstra uma implementação simples de controles personalizados e designers associados. Você pode fazer esta amostra mais sofisticados de várias maneiras:

  • Alterar os valores de propriedade para o DemoMarqueeControl no designer. Adicionar mais MarqueBorder controla e encaixá-los dentro de suas instâncias de pai para criar um efeito aninhados. Experimente configurações diferentes para o UpdatePeriod e as propriedades relacionadas a luz.

  • Crie suas próprias implementações de IMarqueeWidget. Você poderia, por exemplo, criar um piscando "sinal de néon" ou um sinal de animado com várias imagens.

  • Personalize ainda mais a experiência de tempo de design. Você pode tentar sombreamento mais propriedades do que Enabled e Visible, e você poderá adicionar novas propriedades. Adicione novos verbos do designer para simplificar tarefas comuns como controles filho de encaixe.

  • Licença do MarqueeControl. Para obter mais informações, consulte Como: Controles e componentes de licença.

  • Controle como os controles são serializados e como o código é gerado para elas. Para obter mais informações, consulte Geração e compilação dinâmicas de código fonte.

Consulte também

Tarefas

Como: Criar um controle Windows Forms que tira proveito dos recursos de tempo de Design

Referência

UserControl

ParentControlDesigner

DocumentDesigner

IRootDesigner

DesignerVerb

UITypeEditor

BackgroundWorker

Outros recursos

Estendendo suporte em tempo de design

Designers personalizados

.NET de forma biblioteca: Um Designer de exemplo

Histórico de alterações

Date

History

Motivo

Julho de 2010

Adicionada uma nota sobre o.NET Framework Client Profile.

Comentários do cliente.