Пошаговое руководство. Создание элемента управления, в котором используются преимущества функций, применяемых во время разработки
Чтобы оптимизировать взаимодействие с пользовательским элементом управления во время разработки, можно создать соответствующий пользовательский конструктор.
Внимание
Это содержимое было написано для платформа .NET Framework. Если вы используете .NET 6 или более позднюю версию, используйте это содержимое с осторожностью. Система конструктора изменилась для Windows Forms, и важно ознакомиться с изменениями конструктора после платформа .NET Framework статьи.
В этой статье описывается создание пользовательского конструктора для пользовательского элемента управления. Вы реализуете тип MarqueeControl
и связанный с ним класс конструктора с именем MarqueeControlRootDesigner
.
Тип MarqueeControl
реализует экран, похожий на театральную афишу с анимированными фонарями и мигающим текстом.
Конструктор для этого элемента управления взаимодействует со средой разработки, позволяя реализовать пользовательский интерфейс времени разработки. С помощью пользовательского конструктора можно собрать пользовательскую реализацию MarqueeControl
с анимированными фонарями и мигающим текстом в различных сочетаниях. Собранный элемент управления можно использовать в форме аналогично любому другому элементу управления Windows Forms.
По завершении работы с этим пошаговым руководством ваш пользовательский элемент управления будет выглядеть примерно так:
Полный пример кода см. в статье Практическое руководство. Создание элемента управления Windows Forms, в котором используются преимущества функций, применяемых во время разработки.
Необходимые компоненты
Для выполнения действий, описанных в этом пошаговом руководстве, вам понадобится Visual Studio.
Создание проекта
Первым шагом является создание проекта приложения. В этом проекте выполняется сборка приложения, в котором размещается пользовательский элемент управления.
В Visual Studio создайте новый проект приложения Windows Forms и назовите его MarqueeControlTest.
Создание проекта библиотеки элементов управления
Добавьте в решение проект библиотеки элементов управления Windows Forms. Присвойте проекту имя MarqueeControlLibrary.
С помощью обозревателя решений удалите элемент управления проекта по умолчанию, удалив исходный файл с именем UserControl1.cs или UserControl1.vb в зависимости от выбранного языка.
Добавьте новый элемент UserControl в проект
MarqueeControlLibrary
. Присвойте новому исходному файлу базовое имя MarqueeControl.С помощью обозревателя решений создайте новую папку в проекте
MarqueeControlLibrary
.Щелкните правой кнопкой мыши папку Design и добавьте новый класс. Присвойте ему имя MarqueeControlRootDesigner.
Поскольку вы будете использовать типы из сборки System.Design, добавьте эту ссылку в проект
MarqueeControlLibrary
.
Ссылки на проект пользовательского элемента управления
Для тестирования пользовательского элемента управления будет использоваться проект MarqueeControlTest
. Для работы с пользовательским элементом управления в тестовом проекте необходимо добавить ссылку на сборку MarqueeControlLibrary
.
В проекте MarqueeControlTest
добавьте ссылку на сборку MarqueeControlLibrary
. Для этого необходимо использовать вкладку Проекты в диалоговом окне Добавление ссылки, а не задавать ссылку на сборку MarqueeControlLibrary
напрямую.
Определение пользовательского элемента управления и его пользовательского конструктора
Пользовательский элемент управления будет производным от класса UserControl. Благодаря этому ваш элемент управления может содержать другие элементы управления и будет иметь более широкие функциональные возможности по умолчанию.
С пользовательским элементом управления будет связан пользовательский конструктор. Это позволит создать уникальный интерфейс разработки, адаптированный специально для вашего пользовательского элемента управления.
Для связывания элемента управления с конструктором используется класс DesignerAttribute. Поскольку вы полностью определяете поведение пользовательского элемента управления во время разработки, пользовательский конструктор будет реализовывать интерфейс IRootDesigner.
Определение пользовательского элемента управления и его пользовательского конструктора
Откройте исходный файл
MarqueeControl
в редакторе кода. В верхней части файла импортируйте указанные ниже пространства имен: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
Добавьте DesignerAttribute в объявление класса
MarqueeControl
. Это позволит связать пользовательский элемент управления с его конструктором.[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : UserControl {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControl
Откройте исходный файл
MarqueeControlRootDesigner
в редакторе кода. В верхней части файла импортируйте указанные ниже пространства имен: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
Измените объявление
MarqueeControlRootDesigner
таким образом, чтобы задать наследование от класса DocumentDesigner. Примените ToolboxItemFilterAttribute, чтобы задать взаимодействие конструктора с панелью элементов.Примечание.
Определение класса
MarqueeControlRootDesigner
заключено в пространстве имен с именем MarqueeControlLibrary.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
Определите конструктор для класса
MarqueeControlRootDesigner
. Вставьте инструкцию WriteLine в текст конструктора. Это будет использоваться для отладки.public MarqueeControlRootDesigner() { Trace.WriteLine("MarqueeControlRootDesigner ctor"); }
Public Sub New() Trace.WriteLine("MarqueeControlRootDesigner ctor") End Sub
Создание экземпляра пользовательского элемента управления
Добавьте новый элемент UserControl в проект
MarqueeControlTest
. Присвойте новому исходному файлу базовое имя DemoMarqueeControl.Откройте файл
DemoMarqueeControl
в редакторе кода. В верхней части исходного файла импортируйте пространство именMarqueeControlLibrary
:Imports MarqueeControlLibrary
using MarqueeControlLibrary;
Измените объявление
DemoMarqueeControl
таким образом, чтобы задать наследование от классаMarqueeControl
.Выполните сборку проекта.
Откройте приложение Form1 в конструкторе Windows Forms.
Найдите вкладку Компоненты MarqueeControlTest на панели элементов и откройте ее. Перетащите
DemoMarqueeControl
с панели элементов в форму.Выполните сборку проекта.
Настройка проекта для отладки во время разработки
При разработке пользовательского интерфейса времени разработки потребуется выполнять отладку элементов управления и компонентов. Настроить проект для отладки во время разработки очень просто. Дополнительные сведения см. в статье Пошаговое руководство. Отладка пользовательских элементов управления Windows Forms во время разработки.
Щелкните проект
MarqueeControlLibrary
правой кнопкой мыши и выберите пункт Свойства.В диалоговом окне Страницы свойств MarqueeControlLibrary выберите страницу Отладка.
В разделе Действие при запуске выберите Запуск внешней программы. Вы будете отлаживать отдельный экземпляр Visual Studio, поэтому нажмите кнопку с многоточием (), чтобы перейти к интегрированной среде разработки Visual Studio. Имя исполняемого файла — devenv.exe. Если вы установили его в расположение по умолчанию, путь к нему — %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<выпуск>\Common7\IDE\devenv.exe.
Выберите OK, чтобы закрыть диалоговое окно.
Щелкните правой кнопкой мыши проект MarqueeControlLibrary и выберите пункт Назначить запускаемым проектом, чтобы включить эту конфигурацию отладки.
Контрольная точка
Теперь все готово к отладке поведения пользовательского элемента управления во время разработки. Проверьте, корректно ли настроена среда отладки, после чего проверьте связь между пользовательским элементом управления и пользовательским конструктором.
Проверка связи между средой отладки и конструктором
Откройте исходный файл MarqueeControlRootDesigner в редакторе кода и поместите точку останова в инструкцию WriteLine.
Нажмите F5 для запуска сеанса отладки.
Создается новый экземпляр Visual Studio.
В новом экземпляре Visual Studio откройте решение MarqueeControlTest. Чтобы найти решение, выберите Последние проекты в меню Файл. Файл решения MarqueeControlTest.sln будет указан в качестве последнего использованного файла.
Откройте файл
DemoMarqueeControl
в конструкторе.Фокус передается в экземпляр отладки Visual Studio, а выполнение останавливается в точке останова. Нажмите клавишу F5, чтобы продолжить сеанс отладки.
Теперь все готово для разработки и отладки пользовательского элемента управления и связанного с ним пользовательского конструктора. Оставшаяся часть этой статьи посвящена деталям реализации функциональных возможностей элемента управления и конструктора.
Реализация пользовательского элемента управления
Элемент MarqueeControl
представляет собой немного измененный элемент UserControl. Он предоставляет два метода, Start
и Stop
, которые соответственно запускают и останавливают анимацию афиши. Поскольку MarqueeControl
содержит дочерние элементы управления, реализующие интерфейс IMarqueeWidget
, в методах Start
и Stop
перечисляются все дочерние элементы управления и вызываются соответственно методы StartMarquee
и StopMarquee
для каждого дочернего элемента управления, реализующего интерфейс IMarqueeWidget
.
Внешний вид элементов управления MarqueeBorder
и MarqueeText
зависит от макета, поэтому MarqueeControl
переопределяет метод OnLayout и вызывает метод PerformLayout для дочерних элементов управления этого типа.
Это все изменения в MarqueeControl
. Функциональные возможности времени выполнения реализуются элементами управления MarqueeBorder
и MarqueeText
, а функции времени разработки — классами MarqueeBorderDesigner
и MarqueeControlRootDesigner
.
Реализация пользовательского элемента управления
Откройте исходный файл
MarqueeControl
в редакторе кода. Реализуйте методыStart
иStop
.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
Переопределите метод 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
Создание дочернего элемента управления для пользовательского элемента управления
Элемент MarqueeControl
будет содержать дочерние элементы управления двух видов: MarqueeBorder
и MarqueeText
.
MarqueeBorder
— рисует границу из фонарей по краям элемента. Эти фонари последовательно мигают, благодаря чему создается эффект их движения по краям элемента. Частота мигания фонарей определяется свойствомUpdatePeriod
. Другие аспекты внешнего вида элемента управления задаются с помощью ряда других настраиваемых свойств. Запуск и остановка анимации осуществляются с помощью методовStartMarquee
иStopMarquee
соответственно.MarqueeText
— рисует мигающую строку. Как и в случае с элементом управленияMarqueeBorder
, частота мигания текста определяется свойствомUpdatePeriod
. Элемент управленияMarqueeText
также имеет методыStartMarquee
иStopMarquee
, аналогичные соответствующим методам элемента управленияMarqueeBorder
.
Во время разработки MarqueeControlRootDesigner
позволяет добавлять эти два типа элементов управления в элемент MarqueeControl
в любом сочетании.
Общие функциональные возможности этих двух элементов управления факторизованы в интерфейсе IMarqueeWidget
. Благодаря этому MarqueeControl
может обнаруживать любые дочерние элементы управления, связанные с афишей, и соответствующим образом работать с ними.
Чтобы реализовать функцию периодической анимации, вы будете использовать объекты BackgroundWorker из пространства имен System.ComponentModel. Вы можете использовать объекты Timer, но при наличии большого количества объектов IMarqueeWidget
одного потока пользовательского интерфейса может быть недостаточно для обработки анимации.
Создание дочернего элемента управления для пользовательского элемента управления
Добавьте в проект
MarqueeControlLibrary
новый элемент класса. Присвойте новому исходному файлу базовое имя IMarqueeWidget.Откройте исходный файл
IMarqueeWidget
в редакторе кода и измените объявление сclass
наinterface
:// This interface defines the contract for any class that is to // be used in constructing a MarqueeControl. public interface IMarqueeWidget {
' This interface defines the contract for any class that is to ' be used in constructing a MarqueeControl. Public Interface IMarqueeWidget
Добавьте в интерфейс
IMarqueeWidget
следующий код, чтобы предоставить два метода и свойство, которые управляют анимацией афиши:// 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
Добавьте новый элемент Пользовательский элемент управления в проект
MarqueeControlLibrary
. Присвойте новому исходному файлу базовое имя MarqueeText.Перетащите компонент BackgroundWorker из панели элементов в свой элемент управления
MarqueeText
. Этот компонент будет обеспечивать асинхронное обновление элемента управленияMarqueeText
.В окне Свойства присвойте свойствам
WorkerReportsProgress
и WorkerSupportsCancellation компонента BackgroundWorker значение true. Благодаря этим настройкам компонент BackgroundWorker сможет периодически вызывать событие ProgressChanged и отменять асинхронные обновления.Дополнительные сведения см. в разделе Компонент BackgroundWorker.
Откройте исходный файл
MarqueeText
в редакторе кода. В верхней части файла импортируйте указанные ниже пространства имен: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
Измените объявление
MarqueeText
таким образом, чтобы задать наследование от Label и реализовать интерфейсIMarqueeWidget
:[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {
<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
Объявите переменные экземпляра, соответствующие предоставленным свойствам, и инициализируйте их в конструкторе. Поле
isLit
определяет, должен ли текст окрашиваться в цвет, заданный свойствомLightColor
.// 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
Реализуйте интерфейс
IMarqueeWidget
.Методы
StartMarquee
иStopMarquee
вызывают методы RunWorkerAsync и CancelAsync компонента BackgroundWorker для запуска и остановки анимации.Атрибуты Category и Browsable применяются к свойству
UpdatePeriod
, поэтому оно отображается в пользовательском разделе окна свойств с именем 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
Реализация методов доступа к свойства. Клиентам предоставляются два свойства:
LightColor
иDarkColor
. Атрибуты Category и Browsable применяются к этим свойствам, поэтому эти свойства отображаются в пользовательском разделе окна свойств с именем 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
Реализуйте обработчики для событий DoWork и ProgressChanged компонента BackgroundWorker.
Обработчик события DoWork находится в спящем режиме на протяжении заданного свойством
UpdatePeriod
периода времени в миллисекундах, после чего вызывает событие ProgressChanged, пока анимация не будет остановлена в коде посредством вызова метода CancelAsync.Обработчик события ProgressChanged последовательно изменяет цвет текста со светлого на темный и наоборот, создавая эффект мигания.
// 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
Чтобы включить анимацию, переопределите метод OnPaint.
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
Нажмите клавишу F6 , чтобы создать решение.
Создание дочернего элемента управления MarqueeBorder
Элемент управления MarqueeBorder
реализован немного сложнее, чем элемент управления MarqueeText
. Он содержит больше свойств и более активно использует анимацию в методе OnPaint. В целом, он во многом похож на элемент управления MarqueeText
.
Поскольку элемент управления MarqueeBorder
может иметь дочерние элементы управления, в нем должна быть реализована обработка событий Layout.
Создание дочернего элемента управления MarqueeBorder
Добавьте новый элемент Пользовательский элемент управления в проект
MarqueeControlLibrary
. Присвойте новому исходному файлу базовое имя MarqueeBorder.Перетащите компонент BackgroundWorker из панели элементов в свой элемент управления
MarqueeBorder
. Этот компонент будет обеспечивать асинхронное обновление элемента управленияMarqueeBorder
.В окне Свойства присвойте свойствам
WorkerReportsProgress
и WorkerSupportsCancellation компонента BackgroundWorker значение true. Благодаря этим настройкам компонент BackgroundWorker сможет периодически вызывать событие ProgressChanged и отменять асинхронные обновления. Дополнительные сведения см. в разделе Компонент BackgroundWorker.В окне Свойства нажмите кнопку События. Присоедините обработчики для событий DoWork и ProgressChanged.
Откройте исходный файл
MarqueeBorder
в редакторе кода. В верхней части файла импортируйте указанные ниже пространства имен: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
Измените объявление
MarqueeBorder
таким образом, чтобы задать наследование от Panel и реализовать интерфейсIMarqueeWidget
.[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
Объявите два перечисления для управления состоянием элемента управления
MarqueeBorder
:MarqueeSpinDirection
(определяет направление кругового движения фонарей по краям элемента) иMarqueeLightShape
(определяет квадратную или круглую форму фонарей). Поместите эти объявления перед объявлением классаMarqueeBorder
.// 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
Объявите переменные экземпляра, соответствующие предоставленным свойствам, и инициализируйте их в конструкторе.
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
Реализуйте интерфейс
IMarqueeWidget
.Методы
StartMarquee
иStopMarquee
вызывают методы RunWorkerAsync и CancelAsync компонента BackgroundWorker для запуска и остановки анимации.Поскольку элемент управления
MarqueeBorder
может содержать дочерние элементы управления, в методеStartMarquee
перечисляются все дочерние элементы управления и вызывается методStartMarquee
для тех из них, которые реализуют интерфейсIMarqueeWidget
. МетодStopMarquee
имеет аналогичную реализацию.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
Реализация методов доступа к свойства. Элемент управления
MarqueeBorder
содержит несколько свойств, которые используются для управления его внешним видом.[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
Реализуйте обработчики для событий DoWork и ProgressChanged компонента BackgroundWorker.
Обработчик события DoWork находится в спящем режиме на протяжении заданного свойством
UpdatePeriod
периода времени в миллисекундах, после чего вызывает событие ProgressChanged, пока анимация не будет остановлена в коде посредством вызова метода CancelAsync.Обработчик события ProgressChanged увеличивает позицию базового фонаря, на основании которого определяется светлый или темный цвет остальных фонарей, и вызывает метод 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 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
Реализуйте вспомогательные методы
IsLit
иDrawLight
.Метод
IsLit
определяет цвет фонаря в заданной позиции. Горящие фонари отрисовываются цветом, который задается свойствомLightColor
. Цвет не горящих (темных) фонарей задается свойствомDarkColor
.Метод
DrawLight
отрисовывает фонарь соответствующих цвета и формы в заданной позиции.// 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
Переопределите методы OnLayout и OnPaint.
Метод OnPaint отрисовывает фонари по краям элемента управления
MarqueeBorder
.Поскольку метод OnPaint зависит от размеров элемента управления
MarqueeBorder
, его необходимо вызывать при каждом изменении макета. Для этого следует переопределить метод OnLayout и вызвать метод 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
Создание пользовательского конструктора для скрытия и фильтрации свойств
Класс MarqueeControlRootDesigner
предоставляет реализацию корневого конструктора. Помимо этого конструктора, который работает с элементом управления MarqueeControl
, вам потребуется пользовательский конструктор, связанный с элементом управления MarqueeBorder
. Этот конструктор реализует пользовательское поведение, соответствующее контексту пользовательского корневого конструктора.
В частности, MarqueeBorderDesigner
будет скрывать и фильтровать определенные свойства элемента управления MarqueeBorder
, изменяя их взаимодействие со средой разработки.
Перехват вызовов метода доступа к свойству компонента называется "скрытием". Это позволяет конструктору отслеживать заданное пользователем значение и при необходимости передавать его в разрабатываемый компонент.
В этом примере MarqueeBorderDesigner
будет скрывать свойства Visible и Enabled, в результате чего пользователь не сможет скрыть или отключить элемент управления MarqueeBorder
во время разработки.
Конструкторы также могут добавлять и удалять свойства. В этом примере свойство Padding будет удалено во время разработки, поскольку элемент управления MarqueeBorder
программно задает заполнение на основании размера фонарей, задаваемого свойством LightSize
.
Базовым для MarqueeBorderDesigner
является класс ComponentDesigner, содержащий методы, которые могут изменять предоставляемые элементом управления атрибуты, свойства и события во время разработки:
При изменении общедоступного интерфейса компонента с использованием этих методов соблюдайте следующие правила:
Добавляйте или удаляйте элементы только в методах
PreFilter
.Изменяйте существующие элементы только в методах
PostFilter
.Всегда вызывайте базовую реализацию в начале в методах
PreFilter
.Всегда вызывайте базовую реализацию в конце в методах
PostFilter
.
Соблюдение этих правил гарантирует, что все конструкторы в среде разработки будут иметь согласованное представление всех разрабатываемых компонентов.
Класс ComponentDesigner предоставляет словарь для управления значениями скрытых свойств, что избавляет от необходимости создавать определенные переменные экземпляра.
Создание пользовательского конструктора для скрытия и фильтрации свойств
Щелкните правой кнопкой мыши папку Design и добавьте новый класс. Присвойте новому исходному файлу базовое имя MarqueeBorderDesigner.
Откройте исходный файл MarqueeBorderDesigner в редакторе кода. В верхней части файла импортируйте указанные ниже пространства имен:
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
Измените объявление
MarqueeBorderDesigner
таким образом, чтобы задать наследование от ParentControlDesigner.Поскольку элемент управления
MarqueeBorder
может содержать дочерние элементы управления,MarqueeBorderDesigner
наследуется от класса ParentControlDesigner, который обрабатывает взаимодействие между родительскими и дочерними элементами.namespace MarqueeControlLibrary.Design { public class MarqueeBorderDesigner : ParentControlDesigner {
Namespace MarqueeControlLibrary.Design Public Class MarqueeBorderDesigner Inherits ParentControlDesigner
Переопределите базовую реализацию метода 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
Реализуйте свойства Enabled и Visible. Эти реализации скрывают свойства элемента управления.
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
Обработка изменений компонентов
Класс MarqueeControlRootDesigner
предоставляет пользовательский интерфейс времени разработки для экземпляров MarqueeControl
. Большая часть функциональных возможностей времени разработки наследуется из класса DocumentDesigner. В вашем коде реализуются две конкретные настройки: обработка изменений компонентов и добавление команд конструктора.
Когда пользователи будут разрабатывать собственные экземпляры MarqueeControl
, корневой конструктор будет отслеживать изменения в дочерних элементах управления MarqueeControl
. Среда времени разработки предлагает удобную службу IComponentChangeService для отслеживания изменений состояния компонента.
Чтобы получить ссылку на эту службу, необходимо выполнить запрос к среде с помощью метода GetService. В случае успешного выполнения запроса конструктор может присоединить обработчик события ComponentChanged и выполнить все задачи, необходимые для поддержания согласованного состояния во время разработки.
Для класса MarqueeControlRootDesigner
следует вызывать метод Refresh для каждого объекта IMarqueeWidget
, содержащегося в MarqueeControl
. При этом объект IMarqueeWidget
соответствующим образом перерисовывается при изменении таких свойств, как свойство Size его родительского элемента.
Обработка изменений компонентов
Откройте исходный файл
MarqueeControlRootDesigner
в редакторе кода и переопределите метод Initialize. Вызовите базовую реализацию Initialize и выполните запрос к 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
Реализуйте обработчик события OnComponentChanged. Проверьте тип отправляющего компонента и, если он является интерфейсом
IMarqueeWidget
, вызовите его метод 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
Добавление команд конструктора в пользовательский конструктор
Команда конструктора ― это команда меню, связанная с обработчиком событий. Команды конструктора добавляются в контекстное меню компонента во время разработки. Дополнительные сведения см. в разделе DesignerVerb.
Здесь вы добавите в свои конструкторы две команды: Запустить тест и Остановить тест. С помощью этих команд во время разработки можно просматривать поведение MarqueeControl
во время выполнения. Эти команды будут добавлены в MarqueeControlRootDesigner
.
При вызове команды Запустить тест обработчик событий команды вызовет метод StartMarquee
для MarqueeControl
. При вызове команды Остановить тест обработчик событий команды вызовет метод StopMarquee
для MarqueeControl
. В реализациях методов StartMarquee
и StopMarquee
эти методы вызываются для содержащихся элементов управления, которые реализуют IMarqueeWidget
. Поэтому в тесте будут использоваться все содержащиеся элементы управления IMarqueeWidget
.
Добавление команд конструктора в пользовательские конструкторы
Добавьте в класс
MarqueeControlRootDesigner
обработчики событий с именамиOnVerbRunTest
иOnVerbStopTest
.private void OnVerbRunTest(object sender, EventArgs e) { MarqueeControl c = this.Control as MarqueeControl; c.Start(); } private void OnVerbStopTest(object sender, EventArgs e) { MarqueeControl c = this.Control as MarqueeControl; c.Stop(); }
Private Sub OnVerbRunTest( _ ByVal sender As Object, _ ByVal e As EventArgs) Dim c As MarqueeControl = CType(Me.Control, MarqueeControl) c.Start() End Sub Private Sub OnVerbStopTest( _ ByVal sender As Object, _ ByVal e As EventArgs) Dim c As MarqueeControl = CType(Me.Control, MarqueeControl) c.Stop() End Sub
Присоедините эти обработчики событий к соответствующим командам конструктора.
MarqueeControlRootDesigner
наследует DesignerVerbCollection из базового класса. Вы создадите два новых объекта DesignerVerb и добавите их в эту коллекцию в методе 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)))
Создание пользовательского элемента UITypeEditor
При создании собственного интерфейса времени разработки для пользователей часто требуется реализовать пользовательское взаимодействие с окном свойств. Чтобы добиться этого, можно создать элемент UITypeEditor.
В окне свойств элемента управления MarqueeBorder
предоставляется несколько свойств. Два из них (MarqueeSpinDirection
и MarqueeLightShape
) представлены перечислениями. Чтобы продемонстрировать применение редактора типов пользовательского интерфейса, свойство MarqueeLightShape
будет иметь связанный класс UITypeEditor.
Создание собственного редактора типов пользовательского интерфейса
Откройте исходный файл
MarqueeBorder
в редакторе кода.В определении класса
MarqueeBorder
объявите класс с именем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. 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
Объявите переменную экземпляра IWindowsFormsEditorService с именем
editorService
.private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
Переопределите метод GetEditStyle. Эта реализация возвращает DropDown, на основании чего среда разработки определяет, как отобразить
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
Переопределите метод EditValue. Эта реализация запрашивает объект IWindowsFormsEditorService в среде разработки. В случае успешного выполнения запроса создается
LightShapeSelectionControl
. Метод DropDownControl вызывается для запускаLightShapeEditor
. Возвращаемое этим вызовом значение передается в среду разработки.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
Создание элемента управления представлением для собственного элемента UITypeEditor
Свойство MarqueeLightShape
поддерживает две формы фонарей: Square
и Circle
. Вы создадите пользовательский элемент управления, который используется исключительно для графического отображения этих значений в окно свойств. Этот пользовательский элемент управления будет обеспечивать взаимодействие UITypeEditor с окном свойств.
Создание элемента управления представлением для собственного редактора типов пользовательского интерфейса
Добавьте новый элемент UserControl в проект
MarqueeControlLibrary
. Присвойте новому исходному файлу базовое имя LightShapeSelectionControl.Перетащите два элемента управления Panel с панели элементов на элемент
LightShapeSelectionControl
. Присвойте им именаsquarePanel
иcirclePanel
. Расположите их рядом друг с другом. Присвойте свойству Size обоих элементов управления Panel значение (60, 60). Присвойте свойству Location элемента управленияsquarePanel
значение (8, 10). Присвойте свойству Location элемента управленияcirclePanel
значение (80, 10). Наконец, присвойте свойству Size объектаLightShapeSelectionControl
значение (150, 80).Откройте исходный файл
LightShapeSelectionControl
в редакторе кода. В верхней части исходного файла импортируйте пространство имен System.Windows.Forms.Design:Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
Реализуйте обработчики события Click для элементов управления
squarePanel
иcirclePanel
. Эти методы вызывают метод CloseDropDown для завершения сеанса редактирования собственного элемента 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
Объявите переменную экземпляра IWindowsFormsEditorService с именем
editorService
.Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
Объявите переменную экземпляра
MarqueeLightShape
с именемlightShapeValue
.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
В конструкторе
LightShapeSelectionControl
присоедините обработчики события Click к событиям Click элементов управленияsquarePanel
иcirclePanel
. Также определите перегрузку конструктора, которая присваивает значениеMarqueeLightShape
из среды разработки полюlightShapeValue
.// 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
В методе Dispose отсоедините обработчики события 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 Sub
В обозревателе решений нажмите кнопку Показать все файлы. Откройте файл LightShapeSelectionControl.Designer.cs или LightShapeSelectionControl.Designer.vb и удалите заданное по умолчанию определение метода Dispose.
Реализуйте свойство
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
Переопределите метод OnPaint. Эта реализация будет отрисовывать закрашенный квадрат и круг. Она также будет выделять выбранное значение путем отрисовки границы вокруг одной из этих фигур.
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
Тестирование пользовательского элемента управления в конструкторе
На этом этапе можно выполнить сборку проекта MarqueeControlLibrary
. Чтобы протестировать реализацию, создайте элемент управления, наследуемый от класса MarqueeControl
, и используйте его в форме.
Создание пользовательской реализации элемента управления MarqueeControl
Откройте
DemoMarqueeControl
в конструкторе Windows Forms. При этом будет создан экземпляр типаDemoMarqueeControl
, который будет отображаться в экземпляре типаMarqueeControlRootDesigner
.На панели элементов откройте вкладку Компоненты MarqueeControlLibrary. Обратите внимание на доступные для выбора элементы управления
MarqueeBorder
иMarqueeText
.Перетащите экземпляр элемента управления
MarqueeBorder
в область конструктораDemoMarqueeControl
. Закрепите этот элемент управленияMarqueeBorder
в родительском элементе управления.Перетащите экземпляр элемента управления
MarqueeText
в область конструктораDemoMarqueeControl
.Постройте решение.
Щелкните правой кнопкой мыши элемент
DemoMarqueeControl
, а затем в контекстном меню выберите команду Запустить тест, чтобы запустить анимацию. Выберите команду Остановить тест, чтобы остановить анимацию.Откройте форму Form1 в конструкторе.
Поместите в форму два элемента управления Button. Присвойте им имена
startButton
иstopButton
, а затем измените значения свойств Text на Запустить и Остановить соответственно.Реализуйте обработчики события Click для обоих элементов управления Button.
На панели элементов откройте вкладку Компоненты MarqueeControlTest. Обратите внимание на доступный для выбора элемент управления
DemoMarqueeControl
.Перетащите экземпляр
DemoMarqueeControl
в область конструктора Form1.В обработчиках события Click вызовите методы
Start
иStop
дляDemoMarqueeControl
.Private Sub startButton_Click(sender As Object, e As System.EventArgs) Me.demoMarqueeControl1.Start() End Sub 'startButton_Click Private Sub stopButton_Click(sender As Object, e As System.EventArgs) Me.demoMarqueeControl1.Stop() End Sub 'stopButton_Click
private void startButton_Click(object sender, System.EventArgs e) { this.demoMarqueeControl1.Start(); } private void stopButton_Click(object sender, System.EventArgs e) { this.demoMarqueeControl1.Stop(); }
Настройте проект
MarqueeControlTest
как запускаемый проект и запустите его. Появится форма, в которой отображается ваш элементDemoMarqueeControl
. Нажмите кнопку Запустить, чтобы запустить анимацию. Текст начнет мигать, а по краям рамки будут двигаться фонари.
Следующие шаги
На примере MarqueeControlLibrary
демонстрируется простая реализация пользовательских элементов управления и связанных конструкторов. Этот пример можно усложнить несколькими способами:
Измените значения свойств элемента управления
DemoMarqueeControl
в конструкторе. Добавьте дополнительные элементы управленияMarqueBorder
и закрепите их в родительских экземплярах, чтобы создать эффект вложенного размещения. Поэкспериментируйте с различными значениямиUpdatePeriod
и связанных с фонарями свойств.Создайте собственные реализации
IMarqueeWidget
. Например, можно создать мигающую "неоновую вывеску" или анимированную вывеску с несколькими изображениями.Выполните дополнительную настройку интерфейса времени разработки. Попробуйте скрыть другие свойства в дополнение к Enabled и Visible, а также добавить новые свойства. Добавьте новые команды конструктора, чтобы упростить выполнение распространенных задач, таких как закрепление дочерних элементов управления.
Лицензируйте элемент
MarqueeControl
.Реализуйте управление сериализацией элементов управления и созданием кода для них. Дополнительные сведения см. в разделе Динамическое создание и компиляция исходного кода.
См. также
.NET Desktop feedback