Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
A experiência em tempo de design para um controle personalizado pode ser aprimorada criando um designer personalizado associado.
Atenção
Este conteúdo foi escrito para .NET Framework. Se você estiver usando o .NET 6 ou uma versão posterior, use este conteúdo com cuidado. O sistema de designer foi alterado para Windows Forms e é importante que você revise as alterações do Designer desde o artigo do .NET Framework.
Este artigo ilustra como criar um designer personalizado para um controle personalizado. Você implementará um tipo de MarqueeControl e uma classe de designer associada chamada MarqueeControlRootDesigner.
O tipo MarqueeControl implementa uma exibição semelhante a uma marquesa de teatro com luzes animadas e texto intermitente.
O desenvolvedor deste controlo interage com o ambiente de design para fornecer uma experiência personalizada durante a fase de design. Com a ferramenta de design personalizada, pode-se montar uma implementação de MarqueeControl personalizada com luzes animadas e texto intermitente em muitas combinações. Você pode usar o controle montado em um formulário como qualquer outro controle Windows Forms.
Quando você terminar este passo a passo, seu controle personalizado terá a seguinte aparência:
Para obter a listagem completa de código, consulte Como criar um controlo do Windows Forms que tira partido dos Design-Time recursos.
Pré-requisitos
Para concluir este passo a passo, você precisará do Visual Studio.
Criar o projeto
O primeiro passo é criar o projeto de aplicação. 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 de biblioteca de controle
Adicione um projeto da Biblioteca de Controle do Windows Forms à solução. Nomeie o projeto MarqueeControlLibrary.
Usando 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 item de UserControl ao projeto
MarqueeControlLibrary. Dê ao novo arquivo de origem um nome base de MarqueeControl.Usando Gerenciador de Soluções, crie uma nova pasta no projeto
MarqueeControlLibrary.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 projeto
MarqueeControlLibrary.
Fazer referência ao projeto de controle personalizado
Você usará o projeto MarqueeControlTest para testar o controle personalizado. O projeto de teste ficará ciente do controle personalizado quando você adicionar uma referência de projeto ao assembly MarqueeControlLibrary.
No projeto MarqueeControlTest, adicione ao assembly MarqueeControlLibrary uma referência de projeto. Certifique-se de usar a guia Projetos na caixa de diálogo Adicionar Referência em vez de fazer referência ao assembly MarqueeControlLibrary diretamente.
Definir um controle personalizado e seu designer personalizado
Seu controle personalizado derivará da classe UserControl. Isso permite que seu controle contenha outros controles e dá 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 única sob medida especificamente para seu controle personalizado.
Você associa o controle com seu designer usando a classe DesignerAttribute. Como você está desenvolvendo todo o comportamento em tempo de design do seu controle personalizado, o designer personalizado implementará a interface IRootDesigner.
Para definir um controlo personalizado e o respetivo designer personalizado
Abra o arquivo de origem
MarqueeControlno Code Editor. 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.DesignAdicione o DesignerAttribute à declaração de classe
MarqueeControl. Isso associa o controle personalizado ao seu designer.[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : UserControl {<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControlAbra o arquivo de origem
MarqueeControlRootDesignerno Code Editor. 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.DesignAltere a declaração de
MarqueeControlRootDesignerpara herdar da classe DocumentDesigner. Aplique o ToolboxItemFilterAttribute para especificar a interação do designer com a Caixa de Ferramentas .Observação
A definição para a classe
MarqueeControlRootDesignerfoi incluída 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 DocumentDesignerDefina o construtor para a classe
MarqueeControlRootDesigner. 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 seu controle personalizado
Adicione um novo item de UserControl ao projeto
MarqueeControlTest. Dê ao novo arquivo de origem um nome base de DemoMarqueeControl.Abra o arquivo
DemoMarqueeControlno Code Editor. Na parte superior do arquivo, importe o namespaceMarqueeControlLibrary:Imports MarqueeControlLibraryusing MarqueeControlLibrary;Altere a declaração de
DemoMarqueeControlpara herdar da classeMarqueeControl.Construa o projeto.
Abra o Form1 no Windows Forms Designer.
Encontre a guia MarqueeControlTest Components na Toolbox e abra-a. Arraste um
DemoMarqueeControlda Caixa de Ferramentas para o seu formulário.Construa o projeto.
Configurar o projeto para depuração de Design-Time
Quando estiver a desenvolver uma experiência personalizada em tempo de desenvolvimento, será necessário depurar os seus controlos e componentes. Há uma maneira simples de configurar o seu projeto para permitir a depuração em tempo de concepção. Para obter mais informações, consulte Passo a passo: Depurando controles personalizados do Windows Forms em tempo de design.
Clique com o botão direito do mouse no projeto
MarqueeControlLibrarye selecione Propriedades.Na caixa de diálogo MarqueeControlLibrary Property Pages, selecione a página Debug.
Na seção Iniciar Ação, selecione Iniciar Programa Externo. Você estará a depurar uma instância separada do Visual Studio, portanto, clique no botão Ellipsis (reticências) (
) para localizar o IDE do Visual Studio. O nome do arquivo executável é devenv.exee, se você instalou 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 rato no projeto MarqueeControlLibrary e selecione Definir como projeto de inicialização para habilitar essa configuração de depuração.
Ponto de controlo
Agora você está pronto para depurar o comportamento em tempo 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 debug e a associação do designer
Abra o arquivo de origem MarqueeControlRootDesigner na do Editor de Códigos do
e coloque um ponto de interrupção na 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 ao selecionar Projetos Recentes no menu Arquivo. O arquivo de solução MarqueeControlTest.sln será listado como o arquivo usado mais recentemente.
Abra o
DemoMarqueeControlno designer.Quando a instância de depuração do Visual Studio ganha foco, a execução para no seu ponto de interrupção. Pressione F5 para continuar a sessão de depuração.
Neste ponto, tudo está pronto para você desenvolver e depurar seu controle personalizado e seu designer personalizado associado. O restante deste artigo concentra-se nos detalhes de implementação de funcionalidades do controle e do designer.
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 de letreiro, e Stop, que interrompe a animação. Como o MarqueeControl contém controles filho que implementam a interface IMarqueeWidget, Start e Stop enumeram cada controle filho e chamam os métodos StartMarquee e StopMarquee, respectivamente, em cada controle filho que implementa IMarqueeWidget.
A aparência dos controles MarqueeBorder e MarqueeText depende do layout, portanto, MarqueeControl substitui o método OnLayout e chama PerformLayout em controles filho desse tipo.
Este é o alcance das personalizações do MarqueeControl. 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
MarqueeControlno Code Editor. Implemente os métodosStarteStop.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 SubSubstitua 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
O MarqueeControl hospedará dois tipos de controle infantil: o controle MarqueeBorder e o controle MarqueeText.
MarqueeBorder: Este controlador aplica uma borda de "luzes" ao redor de suas extremidades. As luzes piscam em sequência, de modo que parecem estar se movendo ao redor da fronteira. A velocidade com que as luzes piscam é controlada por uma propriedade chamadaUpdatePeriod. Várias outras propriedades personalizadas determinam outros aspetos da aparência do controle. Dois métodos, chamadosStartMarqueeeStopMarquee, controlam quando a animação começa e para.MarqueeText: Este controlo pinta um texto intermitente. Como o controleMarqueeBorder, a velocidade na qual o texto pisca é controlada pela propriedadeUpdatePeriod. O controleMarqueeTexttambém tem os métodosStartMarqueeeStopMarqueeem comum com o controleMarqueeBorder.
No momento do design, o MarqueeControlRootDesigner permite que estes dois tipos de controle sejam adicionados a um MarqueeControl em qualquer combinação.
As características comuns dos dois controles são consideradas em uma interface chamada IMarqueeWidget. Isso permite que o MarqueeControl descubra qualquer controle infantil relacionado à marca e dê a eles um tratamento especial.
Para implementar o recurso de animação periódica, você usará BackgroundWorker objetos do namespace System.ComponentModel. Você pode usar objetos Timer, mas quando muitos objetos IMarqueeWidget estão presentes, o thread único da 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 projeto
MarqueeControlLibrary. Dê ao novo arquivo de origem um nome base de "IMarqueeWidget".Abra o arquivo de origem
no do Editor de Códigos do e altere a declaração de para : // 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 IMarqueeWidgetAdicione o seguinte código à interface
IMarqueeWidgetpara expor dois métodos e uma propriedade que manipulam a animação de letreiro:// 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 InterfaceAdicione um novo item de Controle Personalizado ao projeto
MarqueeControlLibrary. Dê ao novo arquivo de origem um nome base de "MarqueeText".Arraste um componente
do Caixa de Ferramentas do para o controle . Esse componente permitirá que o controle MarqueeTextse atualize de forma assíncrona.Na janela Properties, defina as propriedades BackgroundWorker e
WorkerReportsProgressdo componente WorkerSupportsCancellation como true. Essas configurações permitem que o componente BackgroundWorker aumente periodicamente o evento ProgressChanged e cancele atualizações assíncronas.Para obter mais informações, consulte BackgroundWorker Component.
Abra o arquivo de origem
MarqueeTextno Code Editor. 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.DesignAltere a declaração de
MarqueeTextpara herdar do 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 IMarqueeWidgetDeclare as variáveis de instância que correspondem às propriedades expostas e inicialize-as no construtor. O campo
isLitdetermina se o texto deve ser pintado na cor dada pela propriedadeLightColor.// 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 SubImplemente a interface
IMarqueeWidget.Os métodos
StartMarqueeeStopMarqueeinvocam os métodos BackgroundWorker e RunWorkerAsync do componente CancelAsync para iniciar e parar a animação.Os atributos Category e Browsable são aplicados à propriedade
UpdatePeriodpara que ela apareça numa seção personalizada da janela de Propriedades chamada "Marquê".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 PropertyImplemente os acessadores de propriedade. Você exporá duas propriedades aos clientes:
LightColoreDarkColor. Os atributos Category e Browsable são aplicados a essas propriedades, portanto, as propriedades aparecem numa secção personalizada da janela de 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 PropertyImplemente os manipuladores para os eventos BackgroundWorker e DoWork do componente ProgressChanged.
O manipulador de eventos DoWork aguarda pelo número de milissegundos especificado por
UpdatePeriode depois gera o evento ProgressChanged, até que o seu código pare a animação chamando CancelAsync.O manipulador de eventos ProgressChanged alterna o texto entre o seu estado claro e escuro para criar 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 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 SubSobrescreva o método OnPaint para ativar 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 SubPressione F6 para criar a solução.
Criar o controle filho MarqueeBorder
O controle MarqueeBorder é um pouco mais sofisticado do que o controle MarqueeText. Tem mais propriedades e a animação no método OnPaint é mais complexa. Em princípio, é bastante semelhante ao controlo MarqueeText.
Como o controle MarqueeBorder pode ter controles filho, precisa monitorar eventos Layout.
Para criar o controlo MarqueeBorder
Adicione um novo item de Controle Personalizado ao projeto
MarqueeControlLibrary. Dê ao novo arquivo de origem um nome base de "MarqueeBorder".Arraste um componente
do Caixa de Ferramentas do para o controle . Esse componente permitirá que o controle MarqueeBorderse atualize de forma assíncrona.Na janela Properties, defina as propriedades BackgroundWorker e
WorkerReportsProgressdo componente WorkerSupportsCancellation como true. Essas configurações permitem que o componente BackgroundWorker aumente periodicamente o evento ProgressChanged e cancele atualizações assíncronas. Para obter mais informações, consulte BackgroundWorker Component.Na janela Propriedades, selecione o botão Eventos . Anexe manipuladores para os eventos DoWork e ProgressChanged.
Abra o arquivo de origem
MarqueeBorderno Code Editor. 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.DesignAltere a declaração de
MarqueeBorderpara 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 IMarqueeWidgetDeclare duas enumerações para gerenciar o estado do controle
MarqueeBorder:MarqueeSpinDirection, que determina a direção em que 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 de classeMarqueeBorder.// 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 EnumDeclare 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 SubImplemente a interface
IMarqueeWidget.Os métodos
StartMarqueeeStopMarqueeinvocam os métodos BackgroundWorker e RunWorkerAsync do componente CancelAsync para iniciar e parar a animação.Como o controle
MarqueeBorderpode conter controles filho, o métodoStartMarqueeenumera todos os controles filho e chamaStartMarqueenaqueles que implementamIMarqueeWidget. O métodoStopMarqueetem 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 PropertyImplemente os acessadores de propriedade. O controle
MarqueeBordertem 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 PropertyImplemente os manipuladores para os eventos BackgroundWorker e DoWork do componente ProgressChanged.
O manipulador de eventos DoWork aguarda pelo número de milissegundos especificado por
UpdatePeriode depois gera o evento ProgressChanged, até que o seu código pare a animação chamando CancelAsync.O manipulador de eventos ProgressChanged incrementa a posição da luz "base", a partir da qual o estado claro/escuro das outras luzes é determinado, e chama o método Refresh para que o controle se redesenhe.
// 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 SubImplemente os métodos auxiliares,
IsLiteDrawLight.O método
IsLitdetermina a cor de uma luz em uma determinada posição. As luzes que são "acesas" são desenhadas na cor dada pela propriedadeLightColor, e as que são "escuras" são desenhadas na cor dada pela propriedadeDarkColor.O método
DrawLightdesenha 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 SubSubstitua os métodos OnLayout e OnPaint.
O método OnPaint desenha as luzes ao longo das bordas do controle
MarqueeBorder.Como o método OnPaint depende das dimensões do controle
MarqueeBorder, 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 classe MarqueeControlRootDesigner 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. Este designer fornece um comportamento específico que é adequado dentro do contexto do designer raiz personalizado.
Especificamente, o MarqueeBorderDesigner irá "sombrear" e filtrar certas propriedades no controle MarqueeBorder, alterando sua interação com o ambiente de design.
A intercetação de chamadas para o acessador de propriedade de um componente é conhecida 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.
Neste exemplo, as propriedades Visible e Enabled serão sombreadas pelo MarqueeBorderDesigner, o que impede que o usuário torne o controle MarqueeBorder invisível ou desativado durante o tempo de design.
Os designers também podem adicionar e remover propriedades. Neste exemplo, a propriedade Padding será removida em tempo de design, porque o controle MarqueeBorder define programaticamente 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 desenho:
Ao alterar a interface pública de um componente usando esses métodos, siga estas regras:
Adicionar ou remover itens apenas nos métodos
PreFilterModificar apenas os itens existentes nos métodos
PostFilterSempre chame a implementação base primeiro nos métodos
PreFilterSempre chame a implementação base por último nos métodos
PostFilter
A adesão a essas regras garante que todos os projetistas no ambiente de tempo de design tenham uma visão consistente de todos os componentes que estão sendo projetados.
A classe ComponentDesigner fornece um dicionário para gerenciar os valores de propriedades sombreadas, o que alivia você da necessidade de criar variáveis de instância específicas.
Para criar um designer personalizado para ocultar 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 Code Editor. 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.DesignAltere a declaração de
MarqueeBorderDesignerpara herdar de ParentControlDesigner.Como o controle
MarqueeBorderpode conter controles filho,MarqueeBorderDesignerherda de ParentControlDesigner, que lida com a interação pai-filho.namespace MarqueeControlLibrary.Design { public class MarqueeBorderDesigner : ParentControlDesigner {Namespace MarqueeControlLibrary.Design Public Class MarqueeBorderDesigner Inherits ParentControlDesignerSubstitua a implementação base do 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 SubImplemente as propriedades Enabled e Visible. Essas implementações sombreiam 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
Gerir alterações dos componentes
A classe MarqueeControlRootDesigner fornece a experiência de tempo de design personalizada para suas instâncias MarqueeControl. A maior parte das funcionalidades de tempo de projeto é herdada da classe DocumentDesigner. O código implementará duas personalizações específicas: manipular alterações de componentes e adicionar verbos de design.
À medida que os utilizadores concebem as suas instâncias MarqueeControl, o seu designer principal controlará as alterações no MarqueeControl e nos seus controlos filho. O ambiente de tempo de design oferece um serviço conveniente, IComponentChangeService, para rastrear alterações no estado do componente.
Você adquire uma referência a esse serviço consultando o ambiente com o método GetService. Se a consulta for bem-sucedida, o designer poderá anexar um manipulador para o evento ComponentChanged e executar as tarefas necessárias para assegurar um estado consistente no momento do design.
No caso da classe MarqueeControlRootDesigner, você chamará o método Refresh em cada objeto IMarqueeWidget contido pelo MarqueeControl. Isso fará com que o objeto IMarqueeWidget se redesenhe adequadamente quando as propriedades do seu pai, como Size, forem alteradas.
Para lidar com alterações de componentes
Abra o ficheiro de origem
MarqueeControlRootDesignerno Editor de Códigos e sobrescreva o método Initialize. Execute a implementação base do Initialize e consulte o 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 IfImplemente o manipulador de eventos OnComponentChanged. Teste o tipo do componente de envio e, se for um
IMarqueeWidget, chame seu método Refresh.private void OnComponentChanged( object sender, ComponentChangedEventArgs e) { if (e.Component is IMarqueeWidget) { this.Control.Refresh(); } }Private Sub OnComponentChanged( _ ByVal sender As Object, _ ByVal e As ComponentChangedEventArgs) If TypeOf e.Component Is IMarqueeWidget Then Me.Control.Refresh() End If End Sub
Adicionar Verbos de Desenvolvimento ao seu Designer Personalizado
Um verbo de designer é um comando de menu vinculado a um manipulador de eventos. Os verbos do designer são adicionados ao menu de atalho de um componente em tempo de conceção. Para obter mais informações, consulte DesignerVerb.
Você adicionará dois verbos de designer aos seus designers: Executar Teste e Parar Teste. Estes verbos permitirão que visualize o comportamento em tempo de execução do MarqueeControl no momento do design. Estes verbos serão adicionados a MarqueeControlRootDesigner.
Quando Run Test é invocado, o manipulador de eventos verbo chamará o método StartMarquee no MarqueeControl. Quando o Stop Test é invocado, o manipulador de eventos chamará o método StopMarquee no MarqueeControl. A implementação dos métodos StartMarquee e StopMarquee chama esses métodos nos controlos contidos que implementam IMarqueeWidget, de modo que qualquer controlo IMarqueeWidget contido também participará no teste.
Para adicionar verbos de programação aos seus projetos personalizados
Na classe
MarqueeControlRootDesigner, adicione manipuladores de eventos chamadosOnVerbRunTesteOnVerbStopTest.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 SubConecte esses manipuladores de eventos aos verbos de design correspondentes.
MarqueeControlRootDesignerherda um DesignerVerbCollection de sua classe base. Você criará dois novos objetos DesignerVerb e os adicionará a essa coleção no método Initialize.this.Verbs.Add( new DesignerVerb("Run Test", new EventHandler(OnVerbRunTest)) ); this.Verbs.Add( new DesignerVerb("Stop Test", new EventHandler(OnVerbStopTest)) );Me.Verbs.Add(New DesignerVerb("Run Test", _ New EventHandler(AddressOf OnVerbRunTest))) Me.Verbs.Add(New DesignerVerb("Stop Test", _ New EventHandler(AddressOf OnVerbStopTest)))
Criar um UITypeEditor personalizado
Quando você cria uma experiência de tempo de design personalizada para os usuários, geralmente é desejável criar uma interação personalizada com a janela Propriedades. Você pode fazer isso criando um UITypeEditor.
O controle MarqueeBorder expõe várias propriedades na janela Propriedades. Duas dessas propriedades, MarqueeSpinDirection e 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 de interface do usuário personalizado
Abra o arquivo de origem
MarqueeBorderno Code Editor.Na definição da classe
MarqueeBorder, declare uma classe chamadaLightShapeEditorque 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 UITypeEditorDeclare uma variável de instância IWindowsFormsEditorService chamada
editorService.private IWindowsFormsEditorService editorService = null;Private editorService As IWindowsFormsEditorService = NothingSubstitua 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 FunctionSubstitua o método EditValue. Essa implementação consulta o ambiente de design para um objeto IWindowsFormsEditorService. Se for bem-sucedido, criará um
LightShapeSelectionControl. O método DropDownControl é invocado para iniciar oLightShapeEditor. O valor de retorno 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 sua UITypeEditor personalizada
A propriedade MarqueeLightShape suporta dois tipos de formas de luz: 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 pelo seu UITypeEditor para interagir com a janela Propriedades.
Para criar um controle de exibição para seu editor de tipo de interface do usuário personalizado
Adicione um novo item de UserControl ao projeto
MarqueeControlLibrary. Dê ao novo arquivo de origem um nome base de LightShapeSelectionControl.Arraste dois controles
do Caixa de Ferramentas do para o . Nomeie-os squarePanelecirclePanel. Organize-os lado a lado. Defina a propriedade Size de ambos os controles Panel como (60, 60). Defina a propriedade Location do controlesquarePanelcomo (8, 10). Defina a propriedade Location do controlecirclePanelcomo (80, 10). Finalmente, defina a propriedade Size doLightShapeSelectionControlcomo (150, 80).Abra o arquivo de origem
LightShapeSelectionControlno Code Editor. Na parte superior do arquivo, importe o namespace System.Windows.Forms.Design:Imports System.Windows.Forms.Designusing System.Windows.Forms.Design;Implemente manipuladores de eventos Click para os controles
squarePanelecirclePanel. Esses métodos invocam CloseDropDown para encerrar a sessão de edição de UITypeEditor personalizada.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 SubDeclare uma variável de instância IWindowsFormsEditorService chamada
editorService.Private editorService As IWindowsFormsEditorServiceprivate IWindowsFormsEditorService editorService;Declare uma variável de instância
MarqueeLightShapechamadalightShapeValue.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.SquareNo construtor
LightShapeSelectionControl, anexe os manipuladores de eventos Click aos eventossquarePaneldos controlescirclePanele Click. Além disso, defina uma sobrecarga do construtor que atribua o valorMarqueeLightShapedo 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 SubNo método Dispose, desanexe os manipuladores de eventos Click.
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 SubNo 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 método Dispose.
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 PropertySubstitua o método OnPaint. Esta implementação desenhará um quadrado e um círculo preenchidos. Ele também destacará 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
Teste seu controle personalizado no Designer
Neste ponto, você pode construir o projeto MarqueeControlLibrary. Teste sua implementação criando um controle que herda da classe MarqueeControl e usando-o em um formulário.
Para criar uma implementação personalizada do MarqueeControl
Abra
DemoMarqueeControlno Windows Forms Designer. Isso cria uma instância do tipoDemoMarqueeControle a exibe em uma instância do tipoMarqueeControlRootDesigner.Na Toolbox, abra a guia MarqueeControlLibrary Components. Verá os controlos
MarqueeBordereMarqueeTextdisponíveis para seleção.Arraste uma instância do controle
MarqueeBorderpara a superfície de designDemoMarqueeControl. Encaixe esse controleMarqueeBorderao controle pai.Arraste uma instância do controle
MarqueeTextpara a superfície de designDemoMarqueeControl.Crie a solução.
Clique com o botão direito do mouse no
DemoMarqueeControle, no menu de atalho, selecione a opção Executar de teste para iniciar a animação. Clique em Parar Teste para parar a animação.Abra o Formulário1 no modo de Design.
Coloque dois controles Button no formulário. Atribua-lhes os nomes
startButtonestopButton, e altere os valores de propriedade Text para Iniciar e Parar, respectivamente.Implemente manipuladores de eventos Click para ambos os controles Button.
Na Caixa de Ferramentas, abra a guia MarqueeControlTest Components. Verá os
DemoMarqueeControldisponíveis para seleção.Arraste uma instância de
DemoMarqueeControlpara a superfície de design do Form1.Nos manipuladores de eventos Click, chame os métodos
StarteStopnoDemoMarqueeControl.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_Clickprivate 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 projeto
MarqueeControlTestcomo o projeto de inicialização e execute-o. Você verá o formulário exibindo suaDemoMarqueeControl. 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óximos passos
O MarqueeControlLibrary demonstra uma implementação simples de controles personalizados e designers associados. Você pode tornar essa amostra mais sofisticada de várias maneiras:
Altere os valores de propriedade do
DemoMarqueeControlno designer. Adicione mais controlesMarqueBordere encaixe-os em suas instâncias pai para criar um efeito aninhado. Experimente diferentes configurações para oUpdatePeriode as propriedades relacionadas à luz.Crie suas próprias implementações de
IMarqueeWidget. Você pode, por exemplo, criar um "sinal de neon" piscando ou um sinal animado com várias imagens.Personalize ainda mais a experiência de tempo de design. Você pode tentar sombrear mais propriedades do que Enabled e Visiblee adicionar novas propriedades. Adicione novos verbos de designer para simplificar tarefas comuns, como encaixar controles filho.
Licencie o
MarqueeControl.Controle como seus controles são serializados e como o código é gerado para eles. Para obter mais informações, consulte Dynamic Source Code Generation and Compilation.
Ver também
.NET Desktop feedback