Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
A experiência durante o tempo de design para um controle personalizado pode ser aprimorada ao criar um designer personalizado associado.
Cuidado
Este conteúdo foi escrito para o .NET Framework. Se você estiver usando o .NET 6 ou uma versão posterior, use esse conteúdo com cuidado. O sistema de design foi alterado para Windows Forms e é importante que você examine o artigo Alterações do Designer desde o .NET Framework.
Este artigo ilustra como criar um designer personalizado para um controle personalizado. Você implementará um MarqueeControl
tipo e uma classe de designer associada chamada MarqueeControlRootDesigner
.
O MarqueeControl
tipo implementa uma exibição semelhante a uma marquise de teatro com luzes animadas e texto piscando.
O designer para esse controle interage com o ambiente de design para fornecer uma experiência personalizada em tempo de design. Com o designer personalizado, você pode montar uma implementação personalizada MarqueeControl
com luzes animadas e texto piscando em muitas combinações. Você pode usar o controle montado em um formulário como qualquer outro controle do Windows Forms.
Quando você terminar de usar este passo a passo, o controle personalizado será semelhante ao seguinte:
Para obter a listagem de código completa, consulte Como criar um controle do Windows Forms que aproveite Design-Time recursos.
Pré-requisitos
Para concluir este passo a passo, você precisará do Visual Studio.
Criar o projeto
A primeira etapa é criar o projeto de aplicativo. Você usará esse projeto para criar o aplicativo que hospeda o controle personalizado.
No Visual Studio, crie um novo projeto de aplicativo do Windows Forms e nomeie-o MarqueeControlTest.
Criar o projeto da biblioteca de controle
Adicione um projeto da Biblioteca de Controle do Windows Forms à solução. Nomeie o projeto MarqueeControlLibrary.
Usando o Gerenciador de Soluções, exclua o controle padrão do projeto excluindo o arquivo de origem chamado "UserControl1.cs" ou "UserControl1.vb", dependendo do idioma de sua escolha.
Adicione um novo UserControl item ao
MarqueeControlLibrary
projeto. Dê ao novo arquivo de origem um nome base de MarqueeControl.Usando o Gerenciador de Soluções, crie uma nova pasta no
MarqueeControlLibrary
projeto.Clique com o botão direito do mouse na pasta Design e adicione uma nova classe. Nomeie-o MarqueeControlRootDesigner.
Você precisará usar tipos do assembly System.Design, portanto, adicione essa referência ao
MarqueeControlLibrary
projeto.
Referenciar o projeto de controle personalizado
Você usará o MarqueeControlTest
projeto para testar o controle personalizado. O projeto de teste ficará ciente do controle personalizado quando você adicionar uma referência de projeto ao MarqueeControlLibrary
assembly.
No projeto MarqueeControlTest
, adicione uma referência de projeto ao assembly MarqueeControlLibrary
. Certifique-se de usar a aba Projetos da caixa de diálogo Adicionar Referência em vez de referenciar diretamente o assembly MarqueeControlLibrary
.
Definir um controle personalizado e seu designer personalizado
Seu controle personalizado derivará da UserControl classe. Isso permite que seu controle contenha outros controles e oferece ao seu controle uma grande quantidade de funcionalidade padrão.
Seu controle personalizado terá um designer personalizado associado. Isso permite que você crie uma experiência de design exclusiva adaptada especificamente para seu controle personalizado.
Você associa o controle ao designer usando a DesignerAttribute classe. Como você está desenvolvendo todo o comportamento de tempo de design do controle personalizado, o designer personalizado implementará a IRootDesigner interface.
Para definir um controle personalizado e seu designer personalizado
Abra o arquivo de origem
MarqueeControl
no Editor de Código. Na parte superior do arquivo, importe os seguintes namespaces:using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Drawing Imports System.Windows.Forms Imports System.Windows.Forms.Design
Adicione o DesignerAttribute à declaração de classe
MarqueeControl
. Isso associa o controle personalizado ao seu desenvolvedor.[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : UserControl {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControl
Abra o arquivo de origem
MarqueeControlRootDesigner
no Editor de Código. Na parte superior do arquivo, importe os seguintes namespaces:using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Drawing.Design; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Drawing.Design Imports System.Windows.Forms Imports System.Windows.Forms.Design
Altere a declaração de
MarqueeControlRootDesigner
para herdar da classe DocumentDesigner. Aplique o ToolboxItemFilterAttribute para especificar a interação do designer com a Caixa de Ferramentas.Observação
A definição da
MarqueeControlRootDesigner
classe foi colocada em um namespace chamado MarqueeControlLibrary.Design. Essa declaração coloca o designer em um namespace especial reservado para tipos relacionados ao design.namespace MarqueeControlLibrary.Design { [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public class MarqueeControlRootDesigner : DocumentDesigner {
Namespace MarqueeControlLibrary.Design <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Public Class MarqueeControlRootDesigner Inherits DocumentDesigner
Defina o construtor para a
MarqueeControlRootDesigner
classe. Insira uma instrução WriteLine no corpo do construtor. Isso será útil para depuração.public MarqueeControlRootDesigner() { Trace.WriteLine("MarqueeControlRootDesigner ctor"); }
Public Sub New() Trace.WriteLine("MarqueeControlRootDesigner ctor") End Sub
Criar uma instância do controle personalizado
Adicione um novo UserControl item ao
MarqueeControlTest
projeto. Dê ao novo arquivo de origem um nome base de DemoMarqueeControl.Abra o
DemoMarqueeControl
arquivo no Editor de Código. Na parte superior do arquivo, importe oMarqueeControlLibrary
namespace:Imports MarqueeControlLibrary
using MarqueeControlLibrary;
Altere a declaração de
DemoMarqueeControl
para herdar da classeMarqueeControl
.Compile o projeto.
Abra o Form1 no Designer de Formulários do Windows.
Localize a guia Componentes MarqueeControlTest na Caixa de Ferramentas e abra-a. Arraste um
DemoMarqueeControl
da Caixa de Ferramentas para seu formulário.Compile o projeto.
Configurar o projeto para depuração de Design-Time
Quando você estiver desenvolvendo uma experiência de design personalizada, será necessário depurar seus controles e componentes. Há uma maneira simples de configurar seu projeto para permitir a depuração em tempo de desenvolvimento. Para obter mais informações, consulte Guia: Depurando controles personalizados do Windows Forms em tempo de design.
Clique com o botão direito do mouse no
MarqueeControlLibrary
projeto e selecione Propriedades.Na caixa de diálogo Páginas de Propriedades MarqueeControlLibrary , selecione a página Depurar .
Na seção Iniciar Ação , selecione Iniciar Programa Externo. Você depurará uma instância separada do Visual Studio, portanto, clique no botão de reticências (
) para procurar o IDE do Visual Studio. O nome do arquivo executável é devenv.exee, se você tiver instalado no local padrão, seu caminho será %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe.
Selecione OK para fechar a caixa de diálogo.
Clique com o botão direito do mouse no projeto MarqueeControlLibrary e selecione Definir como Projeto de Inicialização para habilitar essa configuração de depuração.
Ponto de verificação
Você está pronto agora para depurar o comportamento no momento de design do seu controle personalizado. Depois de determinar 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
Abra o arquivo de origem MarqueeControlRootDesigner no Editor de Código e coloque um ponto de interrupção na WriteLine instrução.
Pressione F5 para iniciar a sessão de depuração.
Uma nova instância do Visual Studio é criada.
Na nova instância do Visual Studio, abra a solução MarqueeControlTest. Você pode encontrar facilmente a solução selecionando Projetos Recentes no menu Arquivo. O arquivo de solução MarqueeControlTest.sln será listado como o arquivo usado mais recentemente.
Abra o
DemoMarqueeControl
designer.A instância de depuração do Visual Studio ganha foco e a execução é interrompida no ponto de interrupção. Pressione F5 para continuar a sessão de depuração.
Neste ponto, tudo está em vigor para que você desenvolva e depure seu controle personalizado e seu designer personalizado associado. O restante deste artigo se concentra nos detalhes da implementação de funcionalidades do controle e do design.
Implementar o controle personalizado
O MarqueeControl
é um UserControl com um pouco de personalização. Ele expõe dois métodos: Start
, que inicia a animação da marquise e Stop
, que interrompe a animação. Como eles MarqueeControl
contêm controles filho que implementam a IMarqueeWidget
interface Start
e enumeram Stop
cada controle filho e chamam os métodos eStartMarquee
, StopMarquee
respectivamente, em cada controle filho que implementaIMarqueeWidget
.
A aparência dos controles MarqueeBorder
e MarqueeText
depende do layout, portanto MarqueeControl
substitui o método OnLayout e chama PerformLayout nos controles filho deste tipo.
Essa é a extensão das MarqueeControl
personalizações. Os recursos de tempo de execução são implementados pelos controles MarqueeBorder
e MarqueeText
, e os recursos de tempo de design são implementados pelas classes MarqueeBorderDesigner
e MarqueeControlRootDesigner
.
Para implementar seu controle personalizado
Abra o arquivo de origem
MarqueeControl
no Editor de Código. Implemente os métodosStart
eStop
.public void Start() { // The MarqueeControl may contain any number of // controls that implement IMarqueeWidget, so // find each IMarqueeWidget child and call its // StartMarquee method. foreach( Control cntrl in this.Controls ) { if( cntrl is IMarqueeWidget ) { IMarqueeWidget widget = cntrl as IMarqueeWidget; widget.StartMarquee(); } } } public void Stop() { // The MarqueeControl may contain any number of // controls that implement IMarqueeWidget, so find // each IMarqueeWidget child and call its StopMarquee // method. foreach( Control cntrl in this.Controls ) { if( cntrl is IMarqueeWidget ) { IMarqueeWidget widget = cntrl as IMarqueeWidget; widget.StopMarquee(); } } }
Public Sub Start() ' The MarqueeControl may contain any number of ' controls that implement IMarqueeWidget, so ' find each IMarqueeWidget child and call its ' StartMarquee method. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) widget.StartMarquee() End If Next cntrl End Sub Public Sub [Stop]() ' The MarqueeControl may contain any number of ' controls that implement IMarqueeWidget, so find ' each IMarqueeWidget child and call its StopMarquee ' method. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) widget.StopMarquee() End If Next cntrl End Sub
Substituir o método OnLayout.
protected override void OnLayout(LayoutEventArgs levent) { base.OnLayout (levent); // Repaint all IMarqueeWidget children if the layout // has changed. foreach( Control cntrl in this.Controls ) { if( cntrl is IMarqueeWidget ) { Control control = cntrl as Control; control.PerformLayout(); } } }
Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs) MyBase.OnLayout(levent) ' Repaint all IMarqueeWidget children if the layout ' has changed. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) cntrl.PerformLayout() End If Next cntrl End Sub
Criar um controle filho para seu controle personalizado
Ele MarqueeControl
hospedará dois tipos de controle filho: o MarqueeBorder
controle e o MarqueeText
controle.
MarqueeBorder
: esse controle pinta uma borda de "luzes" ao redor de suas bordas. As luzes piscam em sequência, então parecem estar se movendo ao redor da borda. A velocidade com que as luzes piscam é controlada por uma propriedade chamadaUpdatePeriod
. Várias outras propriedades personalizadas determinam outros aspectos da aparência do controle. Dois métodos, chamadosStartMarquee
eStopMarquee
, controlam quando a animação é iniciada e interrompida.MarqueeText
: esse controle pinta uma cadeia de caracteres piscando. Assim como oMarqueeBorder
controle, a velocidade com que o texto pisca é controlada pelaUpdatePeriod
propriedade. OMarqueeText
controle também tem os métodos e emStartMarquee
StopMarquee
comum com oMarqueeBorder
controle.
Em tempo de design, MarqueeControlRootDesigner
permite que estes dois tipos de controle sejam adicionados a uma MarqueeControl
em qualquer combinação.
Os recursos comuns dos dois controles são levados em conta em uma interface chamada IMarqueeWidget
. Isso permite que o MarqueeControl
descubra quaisquer controles filhos relacionados ao Marquee e confira a eles um tratamento especial.
Para implementar o recurso de animação periódica, você usará BackgroundWorker objetos do System.ComponentModel namespace. Você pode usar Timer objetos, mas quando muitos IMarqueeWidget
objetos estão presentes, o único thread de interface do usuário pode não conseguir acompanhar a animação.
Para criar um controle filho para seu controle personalizado
Adicione um novo item de classe ao
MarqueeControlLibrary
projeto. Dê ao novo arquivo de origem um nome base de "IMarqueeWidget".Abra o arquivo de origem
IMarqueeWidget
no Editor de Código e altere a declaração declass
parainterface
.// 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
Adicione o seguinte código à
IMarqueeWidget
interface para expor dois métodos e uma propriedade que manipula a animação da marquise:// This interface defines the contract for any class that is to // be used in constructing a MarqueeControl. public interface IMarqueeWidget { // This method starts the animation. If the control can // contain other classes that implement IMarqueeWidget as // children, the control should call StartMarquee on all // its IMarqueeWidget child controls. void StartMarquee(); // This method stops the animation. If the control can // contain other classes that implement IMarqueeWidget as // children, the control should call StopMarquee on all // its IMarqueeWidget child controls. void StopMarquee(); // This method specifies the refresh rate for the animation, // in milliseconds. int UpdatePeriod { get; set; } }
' This interface defines the contract for any class that is to ' be used in constructing a MarqueeControl. Public Interface IMarqueeWidget ' This method starts the animation. If the control can ' contain other classes that implement IMarqueeWidget as ' children, the control should call StartMarquee on all ' its IMarqueeWidget child controls. Sub StartMarquee() ' This method stops the animation. If the control can ' contain other classes that implement IMarqueeWidget as ' children, the control should call StopMarquee on all ' its IMarqueeWidget child controls. Sub StopMarquee() ' This method specifies the refresh rate for the animation, ' in milliseconds. Property UpdatePeriod() As Integer End Interface
Adicione um novo item de Controle Personalizado ao
MarqueeControlLibrary
projeto. Dê ao novo arquivo de origem um nome base de "MarqueeText".Arraste um BackgroundWorker componente da Caixa de Ferramentas para o
MarqueeText
controle. Esse componente permitirá que oMarqueeText
controle se atualize de forma assíncrona.Na janela Propriedades, defina as propriedades BackgroundWorker e
WorkerReportsProgress
do componente WorkerSupportsCancellation como true. Essas configurações permitem que o BackgroundWorker componente acione periodicamente o ProgressChanged evento e cancele atualizações assíncronas.Para obter mais informações, consulte Componente BackgroundWorker.
Abra o arquivo de origem
MarqueeText
no Editor de Código. Na parte superior do arquivo, importe os seguintes namespaces:using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Drawing; using System.Threading; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Drawing Imports System.Threading Imports System.Windows.Forms Imports System.Windows.Forms.Design
Altere a declaração de
MarqueeText
para herdar de Label e implementar a interfaceIMarqueeWidget
.[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {
<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
Declare as variáveis de instância que correspondem às propriedades expostas e inicialize-as no construtor. O
isLit
campo determina se o texto deve ser pintado na cor fornecida pelaLightColor
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 bool isLit = true; // These fields back the public properties. private int updatePeriodValue = 50; private Color lightColorValue; private Color darkColorValue; // These brushes are used to paint the light and dark // colors of the text. private Brush lightBrush; private Brush darkBrush; // This component updates the control asynchronously. private BackgroundWorker backgroundWorker1; public MarqueeText() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // Initialize light and dark colors // to the control's default values. this.lightColorValue = this.ForeColor; this.darkColorValue = this.BackColor; this.lightBrush = new SolidBrush(this.lightColorValue); this.darkBrush = new SolidBrush(this.darkColorValue); }
' When isLit is true, the text is painted in the light color; ' When isLit is false, the text is painted in the dark color. ' This value changes whenever the BackgroundWorker component ' raises the ProgressChanged event. Private isLit As Boolean = True ' These fields back the public properties. Private updatePeriodValue As Integer = 50 Private lightColorValue As Color Private darkColorValue As Color ' These brushes are used to paint the light and dark ' colors of the text. Private lightBrush As Brush Private darkBrush As Brush ' This component updates the control asynchronously. Private WithEvents backgroundWorker1 As BackgroundWorker Public Sub New() ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Initialize light and dark colors ' to the control's default values. Me.lightColorValue = Me.ForeColor Me.darkColorValue = Me.BackColor Me.lightBrush = New SolidBrush(Me.lightColorValue) Me.darkBrush = New SolidBrush(Me.darkColorValue) End Sub
Implemente a
IMarqueeWidget
interface.Os métodos
StartMarquee
eStopMarquee
invocam os métodos BackgroundWorker e RunWorkerAsync do componente CancelAsync para iniciar e parar a animação.Os Category atributos e os atributos Browsable são aplicados à
UpdatePeriod
propriedade para que ela apareça em uma seção personalizada da janela Propriedades chamada "Marquee".public virtual void StartMarquee() { // Start the updating thread and pass it the UpdatePeriod. this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod); } public virtual void StopMarquee() { // Stop the updating thread. this.backgroundWorker1.CancelAsync(); } [Category("Marquee")] [Browsable(true)] public int UpdatePeriod { get { return this.updatePeriodValue; } set { if (value > 0) { this.updatePeriodValue = value; } else { throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0"); } } }
Public Overridable Sub StartMarquee() _ Implements IMarqueeWidget.StartMarquee ' Start the updating thread and pass it the UpdatePeriod. Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod) End Sub Public Overridable Sub StopMarquee() _ Implements IMarqueeWidget.StopMarquee ' Stop the updating thread. Me.backgroundWorker1.CancelAsync() End Sub <Category("Marquee"), Browsable(True)> _ Public Property UpdatePeriod() As Integer _ Implements IMarqueeWidget.UpdatePeriod Get Return Me.updatePeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.updatePeriodValue = Value Else Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0") End If End Set End Property
Implemente os acessadores de propriedade. Você exporá duas propriedades aos clientes:
LightColor
eDarkColor
. Os Category atributos e os atributos Browsable são aplicados a essas propriedades, de modo que as propriedades aparecem em uma seção personalizada da janela Propriedades chamada "Marquee".[Category("Marquee")] [Browsable(true)] public Color LightColor { get { return this.lightColorValue; } set { // The LightColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.lightColorValue.ToArgb() != value.ToArgb()) { this.lightColorValue = value; this.lightBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public Color DarkColor { get { return this.darkColorValue; } set { // The DarkColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.darkColorValue.ToArgb() != value.ToArgb()) { this.darkColorValue = value; this.darkBrush = new SolidBrush(value); } } }
<Category("Marquee"), Browsable(True)> _ Public Property LightColor() As Color Get Return Me.lightColorValue End Get Set(ByVal Value As Color) ' The LightColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then Me.lightColorValue = Value Me.lightBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property DarkColor() As Color Get Return Me.darkColorValue End Get Set(ByVal Value As Color) ' The DarkColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then Me.darkColorValue = Value Me.darkBrush = New SolidBrush(Value) End If End Set End Property
Implemente os manipuladores para eventos BackgroundWorker e DoWork componentesProgressChanged.
O DoWork manipulador de eventos dorme pelo número de milissegundos especificado por
UpdatePeriod
e então dispara o evento ProgressChanged, até que seu código interrompa a animação chamando CancelAsync.O ProgressChanged manipulador de eventos alterna o texto entre seus estados claro e escuro para simular o efeito 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 void backgroundWorker1_DoWork( object sender, System.ComponentModel.DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; // This event handler will run until the client cancels // the background task by calling CancelAsync. while (!worker.CancellationPending) { // The Argument property of the DoWorkEventArgs // object holds the value of UpdatePeriod, which // was passed as the argument to the RunWorkerAsync // method. Thread.Sleep((int)e.Argument); // The DoWork eventhandler does not actually report // progress; the ReportProgress event is used to // periodically alert the control to update its state. worker.ReportProgress(0); } } // The ProgressChanged event is raised by the DoWork method. // This event handler does work that is internal to the // control. In this case, the text is toggled between its // light and dark state, and the control is told to // repaint itself. private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) { this.isLit = !this.isLit; this.Refresh(); }
' This method is called in the worker thread's context, ' so it must not make any calls into the MarqueeText control. ' Instead, it communicates to the control using the ' ProgressChanged event. ' ' The only work done in this event handler is ' to sleep for the number of milliseconds specified ' by UpdatePeriod, then raise the ProgressChanged event. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles backgroundWorker1.DoWork Dim worker As BackgroundWorker = CType(sender, BackgroundWorker) ' This event handler will run until the client cancels ' the background task by calling CancelAsync. While Not worker.CancellationPending ' The Argument property of the DoWorkEventArgs ' object holds the value of UpdatePeriod, which ' was passed as the argument to the RunWorkerAsync ' method. Thread.Sleep(Fix(e.Argument)) ' The DoWork eventhandler does not actually report ' progress; the ReportProgress event is used to ' periodically alert the control to update its state. worker.ReportProgress(0) End While End Sub ' The ProgressChanged event is raised by the DoWork method. ' This event handler does work that is internal to the ' control. In this case, the text is toggled between its ' light and dark state, and the control is told to ' repaint itself. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.isLit = Not Me.isLit Me.Refresh() End Sub
Sobrescreva o método OnPaint para habilitar a animação.
protected override void OnPaint(PaintEventArgs e) { // The text is painted in the light or dark color, // depending on the current value of isLit. this.ForeColor = this.isLit ? this.lightColorValue : this.darkColorValue; base.OnPaint(e); }
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) ' The text is painted in the light or dark color, ' depending on the current value of isLit. Me.ForeColor = IIf(Me.isLit, Me.lightColorValue, Me.darkColorValue) MyBase.OnPaint(e) End Sub
Pressione F6 para criar a solução.
Crie o controle de filho MarqueeBorder
O MarqueeBorder
controle é um pouco mais sofisticado do que o MarqueeText
controle. Ele tem mais propriedades e a animação no OnPaint método está mais envolvida. Em princípio, é bastante semelhante ao controle MarqueeText
.
Como o MarqueeBorder
controle pode ter controles filho, ele precisa estar ciente dos Layout eventos.
Para criar o controle do MarqueeBorder
Adicione um novo item de Controle Personalizado ao
MarqueeControlLibrary
projeto. Dê ao novo arquivo de origem um nome base de "MarqueeBorder".Arraste um BackgroundWorker componente da Caixa de Ferramentas para o
MarqueeBorder
controle. Esse componente permitirá que oMarqueeBorder
controle se atualize de forma assíncrona.Na janela Propriedades, defina as propriedades BackgroundWorker e
WorkerReportsProgress
do componente WorkerSupportsCancellation como true. Essas configurações permitem que o BackgroundWorker componente acione periodicamente o ProgressChanged evento e cancele atualizações assíncronas. Para obter mais informações, consulte Componente BackgroundWorker.Na janela Propriedades , selecione o botão Eventos . Anexe manipuladores para os eventos DoWork e ProgressChanged.
Abra o arquivo de origem
MarqueeBorder
no Editor de Código. Na parte superior do arquivo, importe os seguintes namespaces:using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Drawing; using System.Drawing.Design; using System.Threading; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Drawing Imports System.Drawing.Design Imports System.Threading Imports System.Windows.Forms Imports System.Windows.Forms.Design
Altere a declaração de
MarqueeBorder
para herdar de Panel e implementar a interfaceIMarqueeWidget
.[Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] public partial class MarqueeBorder : Panel, IMarqueeWidget {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeBorder Inherits Panel Implements IMarqueeWidget
Declare duas enumerações para gerenciar o
MarqueeBorder
estado do controle:MarqueeSpinDirection
, que determina a direção na qual as luzes "giram" ao redor da borda eMarqueeLightShape
, que determina a forma das luzes (quadradas ou circulares). Coloque essas declarações antes da declaração deMarqueeBorder
classe.// This defines the possible values for the MarqueeBorder // control's SpinDirection property. public enum MarqueeSpinDirection { CW, CCW } // This defines the possible values for the MarqueeBorder // control's LightShape property. public enum MarqueeLightShape { Square, Circle }
' This defines the possible values for the MarqueeBorder ' control's SpinDirection property. Public Enum MarqueeSpinDirection CW CCW End Enum ' This defines the possible values for the MarqueeBorder ' control's LightShape property. Public Enum MarqueeLightShape Square Circle End Enum
Declare as variáveis de instância que correspondem às propriedades expostas e inicialize-as no construtor.
public static int MaxLightSize = 10; // These fields back the public properties. private int updatePeriodValue = 50; private int lightSizeValue = 5; private int lightPeriodValue = 3; private int lightSpacingValue = 1; private Color lightColorValue; private Color darkColorValue; private MarqueeSpinDirection spinDirectionValue = MarqueeSpinDirection.CW; private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square; // These brushes are used to paint the light and dark // colors of the marquee lights. private Brush lightBrush; private Brush darkBrush; // This field tracks the progress of the "first" light as it // "travels" around the marquee border. private int currentOffset = 0; // This component updates the control asynchronously. private System.ComponentModel.BackgroundWorker backgroundWorker1; public MarqueeBorder() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // Initialize light and dark colors // to the control's default values. this.lightColorValue = this.ForeColor; this.darkColorValue = this.BackColor; this.lightBrush = new SolidBrush(this.lightColorValue); this.darkBrush = new SolidBrush(this.darkColorValue); // The MarqueeBorder control manages its own padding, // because it requires that any contained controls do // not overlap any of the marquee lights. int pad = 2 * (this.lightSizeValue + this.lightSpacingValue); this.Padding = new Padding(pad, pad, pad, pad); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); }
Public Shared MaxLightSize As Integer = 10 ' These fields back the public properties. Private updatePeriodValue As Integer = 50 Private lightSizeValue As Integer = 5 Private lightPeriodValue As Integer = 3 Private lightSpacingValue As Integer = 1 Private lightColorValue As Color Private darkColorValue As Color Private spinDirectionValue As MarqueeSpinDirection = MarqueeSpinDirection.CW Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square ' These brushes are used to paint the light and dark ' colors of the marquee lights. Private lightBrush As Brush Private darkBrush As Brush ' This field tracks the progress of the "first" light as it ' "travels" around the marquee border. Private currentOffset As Integer = 0 ' This component updates the control asynchronously. Private WithEvents backgroundWorker1 As System.ComponentModel.BackgroundWorker Public Sub New() ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Initialize light and dark colors ' to the control's default values. Me.lightColorValue = Me.ForeColor Me.darkColorValue = Me.BackColor Me.lightBrush = New SolidBrush(Me.lightColorValue) Me.darkBrush = New SolidBrush(Me.darkColorValue) ' The MarqueeBorder control manages its own padding, ' because it requires that any contained controls do ' not overlap any of the marquee lights. Dim pad As Integer = 2 * (Me.lightSizeValue + Me.lightSpacingValue) Me.Padding = New Padding(pad, pad, pad, pad) SetStyle(ControlStyles.OptimizedDoubleBuffer, True) End Sub
Implemente a
IMarqueeWidget
interface.Os métodos
StartMarquee
eStopMarquee
invocam os métodos BackgroundWorker e RunWorkerAsync do componente CancelAsync para iniciar e parar a animação.Como o controle
MarqueeBorder
pode conter controles filho, o métodoStartMarquee
enumera todos os controles filho e chamaStartMarquee
naqueles que implementamIMarqueeWidget
. OStopMarquee
método tem uma implementação semelhante.public virtual void StartMarquee() { // The MarqueeBorder control may contain any number of // controls that implement IMarqueeWidget, so find // each IMarqueeWidget child and call its StartMarquee // method. foreach (Control cntrl in this.Controls) { if (cntrl is IMarqueeWidget) { IMarqueeWidget widget = cntrl as IMarqueeWidget; widget.StartMarquee(); } } // Start the updating thread and pass it the UpdatePeriod. this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod); } public virtual void StopMarquee() { // The MarqueeBorder control may contain any number of // controls that implement IMarqueeWidget, so find // each IMarqueeWidget child and call its StopMarquee // method. foreach (Control cntrl in this.Controls) { if (cntrl is IMarqueeWidget) { IMarqueeWidget widget = cntrl as IMarqueeWidget; widget.StopMarquee(); } } // Stop the updating thread. this.backgroundWorker1.CancelAsync(); } [Category("Marquee")] [Browsable(true)] public virtual int UpdatePeriod { get { return this.updatePeriodValue; } set { if (value > 0) { this.updatePeriodValue = value; } else { throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0"); } } }
Public Overridable Sub StartMarquee() _ Implements IMarqueeWidget.StartMarquee ' The MarqueeBorder control may contain any number of ' controls that implement IMarqueeWidget, so find ' each IMarqueeWidget child and call its StartMarquee ' method. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) widget.StartMarquee() End If Next cntrl ' Start the updating thread and pass it the UpdatePeriod. Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod) End Sub Public Overridable Sub StopMarquee() _ Implements IMarqueeWidget.StopMarquee ' The MarqueeBorder control may contain any number of ' controls that implement IMarqueeWidget, so find ' each IMarqueeWidget child and call its StopMarquee ' method. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) widget.StopMarquee() End If Next cntrl ' Stop the updating thread. Me.backgroundWorker1.CancelAsync() End Sub <Category("Marquee"), Browsable(True)> _ Public Overridable Property UpdatePeriod() As Integer _ Implements IMarqueeWidget.UpdatePeriod Get Return Me.updatePeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.updatePeriodValue = Value Else Throw New ArgumentOutOfRangeException("UpdatePeriod", _ "must be > 0") End If End Set End Property
Implemente os acessadores de propriedade. O
MarqueeBorder
controle tem várias propriedades para controlar sua aparência.[Category("Marquee")] [Browsable(true)] public int LightSize { get { return this.lightSizeValue; } set { if (value > 0 && value <= MaxLightSize) { this.lightSizeValue = value; this.DockPadding.All = 2 * value; } else { throw new ArgumentOutOfRangeException("LightSize", "must be > 0 and < MaxLightSize"); } } } [Category("Marquee")] [Browsable(true)] public int LightPeriod { get { return this.lightPeriodValue; } set { if (value > 0) { this.lightPeriodValue = value; } else { throw new ArgumentOutOfRangeException("LightPeriod", "must be > 0 "); } } } [Category("Marquee")] [Browsable(true)] public Color LightColor { get { return this.lightColorValue; } set { // The LightColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.lightColorValue.ToArgb() != value.ToArgb()) { this.lightColorValue = value; this.lightBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public Color DarkColor { get { return this.darkColorValue; } set { // The DarkColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.darkColorValue.ToArgb() != value.ToArgb()) { this.darkColorValue = value; this.darkBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public int LightSpacing { get { return this.lightSpacingValue; } set { if (value >= 0) { this.lightSpacingValue = value; } else { throw new ArgumentOutOfRangeException("LightSpacing", "must be >= 0"); } } } [Category("Marquee")] [Browsable(true)] [EditorAttribute(typeof(LightShapeEditor), typeof(System.Drawing.Design.UITypeEditor))] public MarqueeLightShape LightShape { get { return this.lightShapeValue; } set { this.lightShapeValue = value; } } [Category("Marquee")] [Browsable(true)] public MarqueeSpinDirection SpinDirection { get { return this.spinDirectionValue; } set { this.spinDirectionValue = value; } }
<Category("Marquee"), Browsable(True)> _ Public Property LightSize() As Integer Get Return Me.lightSizeValue End Get Set(ByVal Value As Integer) If Value > 0 AndAlso Value <= MaxLightSize Then Me.lightSizeValue = Value Me.DockPadding.All = 2 * Value Else Throw New ArgumentOutOfRangeException("LightSize", _ "must be > 0 and < MaxLightSize") End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightPeriod() As Integer Get Return Me.lightPeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.lightPeriodValue = Value Else Throw New ArgumentOutOfRangeException("LightPeriod", _ "must be > 0 ") End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightColor() As Color Get Return Me.lightColorValue End Get Set(ByVal Value As Color) ' The LightColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then Me.lightColorValue = Value Me.lightBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property DarkColor() As Color Get Return Me.darkColorValue End Get Set(ByVal Value As Color) ' The DarkColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then Me.darkColorValue = Value Me.darkBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightSpacing() As Integer Get Return Me.lightSpacingValue End Get Set(ByVal Value As Integer) If Value >= 0 Then Me.lightSpacingValue = Value Else Throw New ArgumentOutOfRangeException("LightSpacing", _ "must be >= 0") End If End Set End Property <Category("Marquee"), Browsable(True), _ EditorAttribute(GetType(LightShapeEditor), _ GetType(System.Drawing.Design.UITypeEditor))> _ Public Property LightShape() As MarqueeLightShape Get Return Me.lightShapeValue End Get Set(ByVal Value As MarqueeLightShape) Me.lightShapeValue = Value End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property SpinDirection() As MarqueeSpinDirection Get Return Me.spinDirectionValue End Get Set(ByVal Value As MarqueeSpinDirection) Me.spinDirectionValue = Value End Set End Property
Implemente os manipuladores para eventos BackgroundWorker e DoWork componentesProgressChanged.
O DoWork manipulador de eventos dorme pelo número de milissegundos especificado por
UpdatePeriod
e então dispara o evento ProgressChanged, até que seu código interrompa a animação chamando CancelAsync.O ProgressChanged manipulador de eventos incrementa a posição da luz "base", da qual o estado claro/escuro das outras luzes é determinado, e chama o Refresh método para que o controle se re-pinte.
// This method is called in the worker thread's context, // so it must not make any calls into the MarqueeBorder // control. Instead, it communicates to the control using // the ProgressChanged event. // // The only work done in this event handler is // to sleep for the number of milliseconds specified // by UpdatePeriod, then raise the ProgressChanged event. private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; // This event handler will run until the client cancels // the background task by calling CancelAsync. while (!worker.CancellationPending) { // The Argument property of the DoWorkEventArgs // object holds the value of UpdatePeriod, which // was passed as the argument to the RunWorkerAsync // method. Thread.Sleep((int)e.Argument); // The DoWork eventhandler does not actually report // progress; the ReportProgress event is used to // periodically alert the control to update its state. worker.ReportProgress(0); } } // The ProgressChanged event is raised by the DoWork method. // This event handler does work that is internal to the // control. In this case, the currentOffset is incremented, // and the control is told to repaint itself. private void backgroundWorker1_ProgressChanged( object sender, System.ComponentModel.ProgressChangedEventArgs e) { this.currentOffset++; this.Refresh(); }
' This method is called in the worker thread's context, ' so it must not make any calls into the MarqueeBorder ' control. Instead, it communicates to the control using ' the ProgressChanged event. ' ' The only work done in this event handler is ' to sleep for the number of milliseconds specified ' by UpdatePeriod, then raise the ProgressChanged event. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles backgroundWorker1.DoWork Dim worker As BackgroundWorker = CType(sender, BackgroundWorker) ' This event handler will run until the client cancels ' the background task by calling CancelAsync. While Not worker.CancellationPending ' The Argument property of the DoWorkEventArgs ' object holds the value of UpdatePeriod, which ' was passed as the argument to the RunWorkerAsync ' method. Thread.Sleep(Fix(e.Argument)) ' The DoWork eventhandler does not actually report ' progress; the ReportProgress event is used to ' periodically alert the control to update its state. worker.ReportProgress(0) End While End Sub ' The ProgressChanged event is raised by the DoWork method. ' This event handler does work that is internal to the ' control. In this case, the currentOffset is incremented, ' and the control is told to repaint itself. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.currentOffset += 1 Me.Refresh() End Sub
Implemente os métodos auxiliares
IsLit
eDrawLight
.O
IsLit
método determina a cor de uma luz em uma determinada posição. As luzes que são "iluminadas" são desenhadas na cor fornecida pelaLightColor
propriedade e as que são "escuras" são desenhadas na cor fornecida pelaDarkColor
propriedade.O
DrawLight
método desenha uma luz usando a cor, a forma e a posição apropriadas.// This method determines if the marquee light at lightIndex // should be lit. The currentOffset field specifies where // the "first" light is located, and the "position" of the // light given by lightIndex is computed relative to this // offset. If this position modulo lightPeriodValue is zero, // the light is considered to be on, and it will be painted // with the control's lightBrush. protected virtual bool IsLit(int lightIndex) { int directionFactor = (this.spinDirectionValue == MarqueeSpinDirection.CW ? -1 : 1); return ( (lightIndex + directionFactor * this.currentOffset) % this.lightPeriodValue == 0 ); } protected virtual void DrawLight( Graphics g, Brush brush, int xPos, int yPos) { switch (this.lightShapeValue) { case MarqueeLightShape.Square: { g.FillRectangle(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue); break; } case MarqueeLightShape.Circle: { g.FillEllipse(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue); break; } default: { Trace.Assert(false, "Unknown value for light shape."); break; } } }
' This method determines if the marquee light at lightIndex ' should be lit. The currentOffset field specifies where ' the "first" light is located, and the "position" of the ' light given by lightIndex is computed relative to this ' offset. If this position modulo lightPeriodValue is zero, ' the light is considered to be on, and it will be painted ' with the control's lightBrush. Protected Overridable Function IsLit(ByVal lightIndex As Integer) As Boolean Dim directionFactor As Integer = _ IIf(Me.spinDirectionValue = MarqueeSpinDirection.CW, -1, 1) Return (lightIndex + directionFactor * Me.currentOffset) Mod Me.lightPeriodValue = 0 End Function Protected Overridable Sub DrawLight( _ ByVal g As Graphics, _ ByVal brush As Brush, _ ByVal xPos As Integer, _ ByVal yPos As Integer) Select Case Me.lightShapeValue Case MarqueeLightShape.Square g.FillRectangle( _ brush, _ xPos, _ yPos, _ Me.lightSizeValue, _ Me.lightSizeValue) Exit Select Case MarqueeLightShape.Circle g.FillEllipse( _ brush, _ xPos, _ yPos, _ Me.lightSizeValue, _ Me.lightSizeValue) Exit Select Case Else Trace.Assert(False, "Unknown value for light shape.") Exit Select End Select End Sub
Substitua os métodos OnLayout e OnPaint.
O método OnPaint desenha as luzes ao longo das bordas do controle
MarqueeBorder
.Como o OnPaint método depende das dimensões do
MarqueeBorder
controle, você precisa chamá-lo sempre que o layout mudar. Para conseguir isso, substitua OnLayout e chame Refresh.protected override void OnLayout(LayoutEventArgs levent) { base.OnLayout(levent); // Repaint when the layout has changed. this.Refresh(); } // This method paints the lights around the border of the // control. It paints the top row first, followed by the // right side, the bottom row, and the left side. The color // of each light is determined by the IsLit method and // depends on the light's position relative to the value // of currentOffset. protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.Clear(this.BackColor); base.OnPaint(e); // If the control is large enough, draw some lights. if (this.Width > MaxLightSize && this.Height > MaxLightSize) { // The position of the next light will be incremented // by this value, which is equal to the sum of the // light size and the space between two lights. int increment = this.lightSizeValue + this.lightSpacingValue; // Compute the number of lights to be drawn along the // horizontal edges of the control. int horizontalLights = (this.Width - increment) / increment; // Compute the number of lights to be drawn along the // vertical edges of the control. int verticalLights = (this.Height - increment) / increment; // These local variables will be used to position and // paint each light. int xPos = 0; int yPos = 0; int lightCounter = 0; Brush brush; // Draw the top row of lights. for (int i = 0; i < horizontalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); xPos += increment; lightCounter++; } // Draw the lights flush with the right edge of the control. xPos = this.Width - this.lightSizeValue; // Draw the right column of lights. for (int i = 0; i < verticalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); yPos += increment; lightCounter++; } // Draw the lights flush with the bottom edge of the control. yPos = this.Height - this.lightSizeValue; // Draw the bottom row of lights. for (int i = 0; i < horizontalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); xPos -= increment; lightCounter++; } // Draw the lights flush with the left edge of the control. xPos = 0; // Draw the left column of lights. for (int i = 0; i < verticalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); yPos -= increment; lightCounter++; } } }
Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs) MyBase.OnLayout(levent) ' Repaint when the layout has changed. Me.Refresh() End Sub ' This method paints the lights around the border of the ' control. It paints the top row first, followed by the ' right side, the bottom row, and the left side. The color ' of each light is determined by the IsLit method and ' depends on the light's position relative to the value ' of currentOffset. Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim g As Graphics = e.Graphics g.Clear(Me.BackColor) MyBase.OnPaint(e) ' If the control is large enough, draw some lights. If Me.Width > MaxLightSize AndAlso Me.Height > MaxLightSize Then ' The position of the next light will be incremented ' by this value, which is equal to the sum of the ' light size and the space between two lights. Dim increment As Integer = _ Me.lightSizeValue + Me.lightSpacingValue ' Compute the number of lights to be drawn along the ' horizontal edges of the control. Dim horizontalLights As Integer = _ (Me.Width - increment) / increment ' Compute the number of lights to be drawn along the ' vertical edges of the control. Dim verticalLights As Integer = _ (Me.Height - increment) / increment ' These local variables will be used to position and ' paint each light. Dim xPos As Integer = 0 Dim yPos As Integer = 0 Dim lightCounter As Integer = 0 Dim brush As Brush ' Draw the top row of lights. Dim i As Integer For i = 0 To horizontalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) xPos += increment lightCounter += 1 Next i ' Draw the lights flush with the right edge of the control. xPos = Me.Width - Me.lightSizeValue ' Draw the right column of lights. 'Dim i As Integer For i = 0 To verticalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) yPos += increment lightCounter += 1 Next i ' Draw the lights flush with the bottom edge of the control. yPos = Me.Height - Me.lightSizeValue ' Draw the bottom row of lights. 'Dim i As Integer For i = 0 To horizontalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) xPos -= increment lightCounter += 1 Next i ' Draw the lights flush with the left edge of the control. xPos = 0 ' Draw the left column of lights. 'Dim i As Integer For i = 0 To verticalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) yPos -= increment lightCounter += 1 Next i End If End Sub
Criar um designer personalizado para sombrear e filtrar propriedades
A MarqueeControlRootDesigner
classe fornece a implementação para o designer raiz. Além desse designer, que opera no MarqueeControl
, você precisará de um designer personalizado que esteja especificamente associado ao controle MarqueeBorder
. Esse designer fornece um comportamento personalizado apropriado no contexto do designer raiz personalizado.
Especificamente, a MarqueeBorderDesigner
"sombra" será exibida e filtrará determinadas propriedades no MarqueeBorder
controle, alterando sua interação com o ambiente de design.
Interceptar chamadas para o acessador de propriedade de um componente é conhecido como "sombreamento". Ele permite que um designer acompanhe o valor definido pelo usuário e, opcionalmente, passe esse valor para o componente que está sendo projetado.
Para este exemplo, as propriedades e Visible as Enabled propriedades serão sombreadas pelo , o que impede o usuário de tornar o MarqueeBorderDesigner
controle invisível ou desabilitado durante o MarqueeBorder
tempo de design.
Os designers também podem adicionar e remover propriedades. Para este exemplo, a propriedade Padding será removida na fase de design, pois o controle MarqueeBorder
programa o preenchimento com base no tamanho das luzes especificadas pela propriedade LightSize
.
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, siga estas regras:
Adicionar ou remover itens somente nos
PreFilter
métodosModificar somente itens existentes nos
PostFilter
métodosSempre chame a implementação base primeiro nos
PreFilter
métodosSempre chame a implementação base por último nos
PostFilter
métodos
Aderir a essas regras garante que todos os designers no ambiente de tempo de design tenham uma visão consistente de todos os componentes que estão sendo projetados.
A ComponentDesigner classe fornece um dicionário para gerenciar os valores das propriedades sombreadas, o que alivia a necessidade de criar variáveis de instância específicas.
Para criar um designer personalizado para sombrear e filtrar propriedades
Clique com o botão direito do mouse na pasta Design e adicione uma nova classe. Dê ao arquivo de origem um nome base de MarqueeBorderDesigner.
Abra o arquivo de origem MarqueeBorderDesigner no Editor de Código. Na parte superior do arquivo, importe os seguintes namespaces:
using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Windows.Forms Imports System.Windows.Forms.Design
Alterar a declaração de
MarqueeBorderDesigner
para herdar de ParentControlDesigner.Como o
MarqueeBorder
controle pode conter controles filho,MarqueeBorderDesigner
herda de ParentControlDesigner, que manipula a interação pai-filho.namespace MarqueeControlLibrary.Design { public class MarqueeBorderDesigner : ParentControlDesigner {
Namespace MarqueeControlLibrary.Design Public Class MarqueeBorderDesigner Inherits ParentControlDesigner
Substitua a implementação base de PreFilterProperties.
protected override void PreFilterProperties(IDictionary properties) { base.PreFilterProperties(properties); if (properties.Contains("Padding")) { properties.Remove("Padding"); } properties["Visible"] = TypeDescriptor.CreateProperty( typeof(MarqueeBorderDesigner), (PropertyDescriptor)properties["Visible"], new Attribute[0]); properties["Enabled"] = TypeDescriptor.CreateProperty( typeof(MarqueeBorderDesigner), (PropertyDescriptor)properties["Enabled"], new Attribute[0]); }
Protected Overrides Sub PreFilterProperties( _ ByVal properties As IDictionary) MyBase.PreFilterProperties(properties) If properties.Contains("Padding") Then properties.Remove("Padding") End If properties("Visible") = _ TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _ CType(properties("Visible"), PropertyDescriptor), _ New Attribute(-1) {}) properties("Enabled") = _ TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _ CType(properties("Enabled"), _ PropertyDescriptor), _ New Attribute(-1) {}) End Sub
Implemente as propriedades Enabled e Visible. Essas implementações sombream as propriedades do controle.
public bool Visible { get { return (bool)ShadowProperties["Visible"]; } set { this.ShadowProperties["Visible"] = value; } } public bool Enabled { get { return (bool)ShadowProperties["Enabled"]; } set { this.ShadowProperties["Enabled"] = value; } }
Public Property Visible() As Boolean Get Return CBool(ShadowProperties("Visible")) End Get Set(ByVal Value As Boolean) Me.ShadowProperties("Visible") = Value End Set End Property Public Property Enabled() As Boolean Get Return CBool(ShadowProperties("Enabled")) End Get Set(ByVal Value As Boolean) Me.ShadowProperties("Enabled") = Value End Set End Property
Gerenciar alterações de componentes
A MarqueeControlRootDesigner
classe fornece a experiência personalizada de tempo de design para suas MarqueeControl
instâncias. A maior parte da funcionalidade de design em tempo real é herdada da classe DocumentDesigner. Seu código implementará duas personalizações específicas: manipulação de alterações de componentes e adição de verbos de projeto.
À medida que os usuários projetam suas MarqueeControl
instâncias, o designer raiz acompanhará as alterações no MarqueeControl
e nos seus controles filho. O ambiente em tempo de design oferece um conveniente serviço IComponentChangeService para monitorar as alterações no 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 poderá anexar um manipulador para o evento ComponentChanged e executar as tarefas necessárias para manter um estado consistente durante o tempo de design.
No caso da classe MarqueeControlRootDesigner
, você chamará o método Refresh em cada objeto IMarqueeWidget
contido por MarqueeControl
. Isso fará com que o objeto IMarqueeWidget
se repinte adequadamente quando propriedades como as do pai Size forem alteradas.
Para lidar com alterações de componentes
Abra o arquivo de origem
MarqueeControlRootDesigner
no Editor de Código e substitua o Initialize método. Chame a implementação base de Initialize e faça uma consulta ao IComponentChangeService.base.Initialize(component); IComponentChangeService cs = GetService(typeof(IComponentChangeService)) as IComponentChangeService; if (cs != null) { cs.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged); }
MyBase.Initialize(component) Dim cs As IComponentChangeService = _ CType(GetService(GetType(IComponentChangeService)), _ IComponentChangeService) If (cs IsNot Nothing) Then AddHandler cs.ComponentChanged, AddressOf OnComponentChanged End If
Implemente o OnComponentChanged manipulador de eventos. Teste o tipo do componente de envio e, se ele for um
IMarqueeWidget
, chame seu Refresh método.private void OnComponentChanged( object sender, ComponentChangedEventArgs e) { if (e.Component is IMarqueeWidget) { this.Control.Refresh(); } }
Private Sub OnComponentChanged( _ ByVal sender As Object, _ ByVal e As ComponentChangedEventArgs) If TypeOf e.Component Is IMarqueeWidget Then Me.Control.Refresh() End If End Sub
Adicione verbos de designer ao designer personalizado
Um verbo de designer é um comando de menu vinculado a um manipulador de eventos. Os comandos do designer são adicionados ao menu de atalho de um componente no momento de design. Para obter mais informações, consulte DesignerVerb.
Você adicionará dois comandos aos seus projetos de design: Executar Teste e Parar Teste. Esses verbos permitirão que você exiba o comportamento em tempo de execução do MarqueeControl
durante o tempo de design. Esses verbos serão adicionados a MarqueeControlRootDesigner
.
Quando Executar Teste é invocado, o manipulador de eventos de verbo chamará o StartMarquee
método na MarqueeControl
. Quando Stop Test é invocado, o manipulador de eventos de verbo chamará o StopMarquee
método no MarqueeControl
. A implementação dos métodos StartMarquee
e StopMarquee
chama esses métodos em controles contidos que implementam IMarqueeWidget
, portanto, todos os controles IMarqueeWidget
contidos também participarão do teste.
Para adicionar verbos específicos de designer aos designers personalizados
MarqueeControlRootDesigner
Na classe, adicione manipuladores de eventos nomeadosOnVerbRunTest
eOnVerbStopTest
.private void OnVerbRunTest(object sender, EventArgs e) { MarqueeControl c = this.Control as MarqueeControl; c.Start(); } private void OnVerbStopTest(object sender, EventArgs e) { MarqueeControl c = this.Control as MarqueeControl; c.Stop(); }
Private Sub OnVerbRunTest( _ ByVal sender As Object, _ ByVal e As EventArgs) Dim c As MarqueeControl = CType(Me.Control, MarqueeControl) c.Start() End Sub Private Sub OnVerbStopTest( _ ByVal sender As Object, _ ByVal e As EventArgs) Dim c As MarqueeControl = CType(Me.Control, MarqueeControl) c.Stop() End Sub
Conecte esses manipuladores de eventos aos verbos do designer correspondentes.
MarqueeControlRootDesigner
herda um DesignerVerbCollection de sua classe base. Você criará dois novos DesignerVerb objetos e os adicionará a essa coleção no Initialize método.this.Verbs.Add( new DesignerVerb("Run Test", new EventHandler(OnVerbRunTest)) ); this.Verbs.Add( new DesignerVerb("Stop Test", new EventHandler(OnVerbStopTest)) );
Me.Verbs.Add(New DesignerVerb("Run Test", _ New EventHandler(AddressOf OnVerbRunTest))) Me.Verbs.Add(New DesignerVerb("Stop Test", _ New EventHandler(AddressOf OnVerbStopTest)))
Criar um UITypeEditor personalizado
Quando você cria uma experiência personalizada no tempo de design para os usuários, é comum desejar criar uma interação personalizada com a janela de Propriedades. Você pode fazer isso criando um UITypeEditor.
O MarqueeBorder
controle expõe várias propriedades na janela Propriedades. Duas dessas propriedades e MarqueeSpinDirection
MarqueeLightShape
são representadas por enumerações. Para ilustrar o uso de um editor de tipo de interface do usuário, a propriedade MarqueeLightShape
terá uma classe UITypeEditor associada.
Para criar um editor de tipo personalizado de interface do usuário
Abra o arquivo de origem
MarqueeBorder
no Editor de Código.Na definição da
MarqueeBorder
classe, declare uma classe chamadaLightShapeEditor
que deriva de UITypeEditor.// This class demonstrates the use of a custom UITypeEditor. // It allows the MarqueeBorder control's LightShape property // to be changed at design time using a customized UI element // that is invoked by the Properties window. The UI is provided // by the LightShapeSelectionControl class. internal class LightShapeEditor : UITypeEditor {
' This class demonstrates the use of a custom UITypeEditor. ' It allows the MarqueeBorder control's LightShape property ' to be changed at design time using a customized UI element ' that is invoked by the Properties window. The UI is provided ' by the LightShapeSelectionControl class. Friend Class LightShapeEditor Inherits UITypeEditor
Declarar uma IWindowsFormsEditorService variável de instância chamada
editorService
.private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
Substituir o método GetEditStyle. Essa implementação retorna DropDown, que informa ao ambiente de design como exibir o
LightShapeEditor
.public override UITypeEditorEditStyle GetEditStyle( System.ComponentModel.ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; }
Public Overrides Function GetEditStyle( _ ByVal context As System.ComponentModel.ITypeDescriptorContext) _ As UITypeEditorEditStyle Return UITypeEditorEditStyle.DropDown End Function
Substituir o método EditValue. Essa implementação consulta o ambiente de design de um IWindowsFormsEditorService objeto. Se tiver êxito, ele criará um
LightShapeSelectionControl
. O DropDownControl método é invocado para iniciar oLightShapeEditor
. O valor retornado dessa invocação é retornado para o ambiente de design.public override object EditValue( ITypeDescriptorContext context, IServiceProvider provider, object value) { if (provider != null) { editorService = provider.GetService( typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService; } if (editorService != null) { LightShapeSelectionControl selectionControl = new LightShapeSelectionControl( (MarqueeLightShape)value, editorService); editorService.DropDownControl(selectionControl); value = selectionControl.LightShape; } return value; }
Public Overrides Function EditValue( _ ByVal context As ITypeDescriptorContext, _ ByVal provider As IServiceProvider, _ ByVal value As Object) As Object If (provider IsNot Nothing) Then editorService = _ CType(provider.GetService(GetType(IWindowsFormsEditorService)), _ IWindowsFormsEditorService) End If If (editorService IsNot Nothing) Then Dim selectionControl As _ New LightShapeSelectionControl( _ CType(value, MarqueeLightShape), _ editorService) editorService.DropDownControl(selectionControl) value = selectionControl.LightShape End If Return value End Function
Criar um controle de exibição para seu UITypeEditor personalizado
A MarqueeLightShape
propriedade dá suporte a dois tipos de formas leves: Square
e Circle
. Você criará um controle personalizado usado exclusivamente com a finalidade de exibir graficamente esses valores na janela Propriedades. Esse controle personalizado será usado por você UITypeEditor para interagir com a janela Propriedades.
Para criar um controle de visão para seu editor personalizado de tipos de interface de usuário
Adicione um novo UserControl item ao
MarqueeControlLibrary
projeto. Dê ao novo arquivo de origem um nome base de LightShapeSelectionControl.Arraste dois Panel controles da Caixa de Ferramentas para o
LightShapeSelectionControl
. Nomeie-ossquarePanel
ecirclePanel
. Coloque-os lado a lado. Defina a Size propriedade de ambos os Panel controles como (60, 60). Defina a Location propriedade dosquarePanel
controle como (8, 10). Defina a Location propriedade docirclePanel
controle como (80, 10). Por fim, defina a Size propriedade comoLightShapeSelectionControl
(150, 80).Abra o arquivo de origem
LightShapeSelectionControl
no Editor de Código. Na parte superior do arquivo, importe o System.Windows.Forms.Design namespace:Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
Implemente Click manipuladores de eventos para os controles
squarePanel
ecirclePanel
. Esses métodos invocam CloseDropDown para encerrar a sessão de edição personalizada UITypeEditor .private void squarePanel_Click(object sender, EventArgs e) { this.lightShapeValue = MarqueeLightShape.Square; this.Invalidate( false ); this.editorService.CloseDropDown(); } private void circlePanel_Click(object sender, EventArgs e) { this.lightShapeValue = MarqueeLightShape.Circle; this.Invalidate( false ); this.editorService.CloseDropDown(); }
Private Sub squarePanel_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Me.lightShapeValue = MarqueeLightShape.Square Me.Invalidate(False) Me.editorService.CloseDropDown() End Sub Private Sub circlePanel_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Me.lightShapeValue = MarqueeLightShape.Circle Me.Invalidate(False) Me.editorService.CloseDropDown() End Sub
Declarar uma IWindowsFormsEditorService variável de instância chamada
editorService
.Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
Declarar uma
MarqueeLightShape
variável de instância chamadalightShapeValue
.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
No construtor
LightShapeSelectionControl
, anexe os manipuladores de eventos Click aos eventossquarePanel
dos controlescirclePanel
e Click. Além disso, defina uma sobrecarga de construtor que atribua o valorMarqueeLightShape
do ambiente de design ao campolightShapeValue
.// This constructor takes a MarqueeLightShape value from the // design-time environment, which will be used to display // the initial state. public LightShapeSelectionControl( MarqueeLightShape lightShape, IWindowsFormsEditorService editorService ) { // This call is required by the designer. InitializeComponent(); // Cache the light shape value provided by the // design-time environment. this.lightShapeValue = lightShape; // Cache the reference to the editor service. this.editorService = editorService; // Handle the Click event for the two panels. this.squarePanel.Click += new EventHandler(squarePanel_Click); this.circlePanel.Click += new EventHandler(circlePanel_Click); }
' This constructor takes a MarqueeLightShape value from the ' design-time environment, which will be used to display ' the initial state. Public Sub New( _ ByVal lightShape As MarqueeLightShape, _ ByVal editorService As IWindowsFormsEditorService) ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Cache the light shape value provided by the ' design-time environment. Me.lightShapeValue = lightShape ' Cache the reference to the editor service. Me.editorService = editorService ' Handle the Click event for the two panels. AddHandler Me.squarePanel.Click, AddressOf squarePanel_Click AddHandler Me.circlePanel.Click, AddressOf circlePanel_Click End Sub
No método Dispose, desanexe os Click manipuladores de eventos.
protected override void Dispose( bool disposing ) { if( disposing ) { // Be sure to unhook event handlers // to prevent "lapsed listener" leaks. this.squarePanel.Click -= new EventHandler(squarePanel_Click); this.circlePanel.Click -= new EventHandler(circlePanel_Click); if(components != null) { components.Dispose(); } } base.Dispose( disposing ); }
Protected Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then ' Be sure to unhook event handlers ' to prevent "lapsed listener" leaks. RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click If (components IsNot Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub
No Gerenciador de Soluções, clique no botão Mostrar Todos os Arquivos. Abra o arquivo LightShapeSelectionControl.Designer.cs ou LightShapeSelectionControl.Designer.vb e remova a definição padrão do Dispose método.
Implemente a propriedade
LightShape
.// LightShape is the property for which this control provides // a custom user interface in the Properties window. public MarqueeLightShape LightShape { get { return this.lightShapeValue; } set { if( this.lightShapeValue != value ) { this.lightShapeValue = value; } } }
' LightShape is the property for which this control provides ' a custom user interface in the Properties window. Public Property LightShape() As MarqueeLightShape Get Return Me.lightShapeValue End Get Set(ByVal Value As MarqueeLightShape) If Me.lightShapeValue <> Value Then Me.lightShapeValue = Value End If End Set End Property
Substituir o método OnPaint. Essa implementação desenhará um quadrado e um círculo preenchidos. Ele também realçará o valor selecionado desenhando uma borda em torno de uma forma ou outra.
protected override void OnPaint(PaintEventArgs e) { base.OnPaint (e); using( Graphics gSquare = this.squarePanel.CreateGraphics(), gCircle = this.circlePanel.CreateGraphics() ) { // Draw a filled square in the client area of // the squarePanel control. gSquare.FillRectangle( Brushes.Red, 0, 0, this.squarePanel.Width, this.squarePanel.Height ); // If the Square option has been selected, draw a // border inside the squarePanel. if( this.lightShapeValue == MarqueeLightShape.Square ) { gSquare.DrawRectangle( Pens.Black, 0, 0, this.squarePanel.Width-1, this.squarePanel.Height-1); } // Draw a filled circle in the client area of // the circlePanel control. gCircle.Clear( this.circlePanel.BackColor ); gCircle.FillEllipse( Brushes.Blue, 0, 0, this.circlePanel.Width, this.circlePanel.Height ); // If the Circle option has been selected, draw a // border inside the circlePanel. if( this.lightShapeValue == MarqueeLightShape.Circle ) { gCircle.DrawRectangle( Pens.Black, 0, 0, this.circlePanel.Width-1, this.circlePanel.Height-1); } } }
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) MyBase.OnPaint(e) Dim gCircle As Graphics = Me.circlePanel.CreateGraphics() Try Dim gSquare As Graphics = Me.squarePanel.CreateGraphics() Try ' Draw a filled square in the client area of ' the squarePanel control. gSquare.FillRectangle( _ Brushes.Red, _ 0, _ 0, _ Me.squarePanel.Width, _ Me.squarePanel.Height) ' If the Square option has been selected, draw a ' border inside the squarePanel. If Me.lightShapeValue = MarqueeLightShape.Square Then gSquare.DrawRectangle( _ Pens.Black, _ 0, _ 0, _ Me.squarePanel.Width - 1, _ Me.squarePanel.Height - 1) End If ' Draw a filled circle in the client area of ' the circlePanel control. gCircle.Clear(Me.circlePanel.BackColor) gCircle.FillEllipse( _ Brushes.Blue, _ 0, _ 0, _ Me.circlePanel.Width, _ Me.circlePanel.Height) ' If the Circle option has been selected, draw a ' border inside the circlePanel. If Me.lightShapeValue = MarqueeLightShape.Circle Then gCircle.DrawRectangle( _ Pens.Black, _ 0, _ 0, _ Me.circlePanel.Width - 1, _ Me.circlePanel.Height - 1) End If Finally gSquare.Dispose() End Try Finally gCircle.Dispose() End Try End Sub
Testar seu controle personalizado no Designer
Neste ponto, você pode criar o MarqueeControlLibrary
projeto. Teste sua implementação criando um controle que herda da MarqueeControl
classe e a use em um formulário.
Para criar uma implementação personalizada do MarqueeControl
Abra
DemoMarqueeControl
no Designer de Formulários do Windows. Isso cria uma instância do tipoDemoMarqueeControl
e a exibe em uma instância do tipoMarqueeControlRootDesigner
.Na Caixa de Ferramentas, abra a guia Componentes MarqueeControlLibrary. Você verá os controles
MarqueeBorder
eMarqueeText
disponíveis para seleção.Arraste uma instância do controle
MarqueeBorder
para a superfície de designDemoMarqueeControl
. Encaixe esseMarqueeBorder
controle no controle pai.Arraste uma instância do controle
MarqueeText
para a superfície de designDemoMarqueeControl
.Crie a solução.
Clique com o botão direito do mouse no
DemoMarqueeControl
menu de atalho e selecione a opção Executar Teste para iniciar a animação. Clique em Parar Teste para interromper a animação.Abra o Formulário1 no modo design.
Coloque dois Button controles no formulário. Nomeie-os
startButton
estopButton
altere os valores da Text propriedade para Iniciar e Parar, respectivamente.Implementar Click manipuladores de eventos para ambos os Button controles.
Na Caixa de Ferramentas, abra a guia Componentes MarqueeControlTest . Você verá o
DemoMarqueeControl
disponível para seleção.Arraste uma instância de
DemoMarqueeControl
para a superfície de design do Form1.Nos manipuladores de eventos, invoque os métodos Click e
Start
noStop
.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(); }
Defina o
MarqueeControlTest
projeto como o projeto de inicialização e execute-o. Você verá o formulário exibindo seuDemoMarqueeControl
. Selecione o botão Iniciar para iniciar a animação. Você deve ver o texto piscando e as luzes se movendo ao redor da borda.
Próximas etapas
Demonstra MarqueeControlLibrary
uma implementação simples de controles personalizados e designers associados. Você pode tornar este exemplo mais sofisticado de várias maneiras:
Altere os valores de propriedade do
DemoMarqueeControl
no designer. Adicione maisMarqueBorder
controles e encaixe-os em suas instâncias pai para criar um efeito aninhado. Experimente diferentes configurações para aUpdatePeriod
e as propriedades relacionadas à luz.Crie suas próprias implementações de
IMarqueeWidget
. Você pode, por exemplo, criar um "sinal de neon" ou um sinal animado com várias imagens.Personalize ainda mais a experiência de tempo de design. Você pode tentar sombrear mais propriedades além de Enabled e Visible, e também pode adicionar novas propriedades. Adicione novos verbos de designer para simplificar tarefas comuns, como encaixar controles filho.
Licenciar o
MarqueeControl
.Você pode controlar como os controles são serializados e como o código é gerado para eles. Para obter mais informações, consulte Geração e compilação de código-fonte dinâmico.
Consulte também
.NET Desktop feedback