Основными видами создания программного продукта являются:
В данном разделе мы познакомимся с созданием компонентов. Компонент, это программная единица, способная поддерживать технологию визуального программирования. Это не только класс, обладающий нужной функциональностью на этапе выполнения, но и абстракция, поддерживаемая инструментами оболочки на этапе проектирования. В отличие от стандартных библиотечных компонентов, созданных разработчиками среды проектирования, их называют пользовательскими. Пользовательский компонент можно перетаскивать из панели Toolbox на форму, настраивать его свойства и события точно также, как это делается со стандартными компонентами библиотеки .NET Framework.
Стандартная процедура создания пользовательского компонента состоит из следующих шагов:
DLLДалее мы опробуем большинство из этих этапов на примере создания трех
В зависимости от выбора базового типа различают следующие компоненты
user control ) - расширение визуальных элементов управления, в основе которых лежит класс Controlnonvisual component ) - custom control ) - создание совершенно нового визуального компонента непосредственно из класса ControlНиже приведен список базовых классов среды .NET Framework, используемых при создании компонентов
| Класс .NET Framework | Описание |
|---|---|
Object |
Для создания компонента с нуля |
System.ComponentModel.Component |
Для наследования функциональности времени разработки в среде IDE |
System.Windows.Forms.Control |
Для разработки визуальных компонентов |
System.Windows.Forms. |
Работает подобно форме в IDE для композиции нескольких элементов управления. Используется при создании сложных визуальных компонентов, состоящих из нескольких других компонентов |
| Любой библиотечный компонент | Можно расширять любой библиотечный компонент путем наследования |
Создадим компонент под названием FirstComponent, который будет потомком класса System.Windows.Forms.Control сборки System.Windows.Forms. В свою очередь, этот библиотечный класс Control наследует от класса System.ComponentModel.Component сборки System. Нам понадобятся следующие пространства имен:
| Пространство имен | Библиотечная сборка |
|---|---|
System.Windows.Forms |
System.Windows.Forms.dll |
System.ComponentModel |
System.dll |
System.Drawing |
System.Drawing.dll |
System |
mscorlib.dll |
System.Collections |
mscorlib.dll |
Выполните команду File/New/Project оболочки Visual Studio 2005 и заполните окно мастера так
Мастер создаст каталог решения MySolution для размещения нескольких взаимосвязанных проектов, в который поместит каталог проекта MyComponents и будет автоматически обертывать каждый новый класс проекта при его создании пространством имен MyComponents.
Для тренировки нам нужно настроить оболочку на автоматическое генерирование другого пространства имен для всех файлов проекта, которое бы уникально характеризовало нашу библиотеку компонентов. Для будущих компонентов мы выберем составное пространство имен MyCompany.MyComponents.
В панели Solution Explorer вызовите контекстное меню для корневого узла проекта MyComponents и выполните команду Properties. Для вкладки Application скорректируйте текстовое поле Default namespace как показано на рисунке
Удалите автоматически созданный оболочкой файл Class1.cs
В конструкторе файла щелкните на соответствующей ссылке или выполните команду контекстного меню, чтобы перейти в режим редактирования кода
Код заготовки компонента, созданный мастером, будет выглядеть так
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace MyCompany.MyComponents
{
public partial class FirstComponent : Component
{
public FirstComponent()
{
InitializeComponent();
}
public FirstComponent(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
Обратите внимание, что класс объявлен как partial (частичный), что позволит для удобства чтения разбить его описание на отдельные части, или даже разместить эти описания в отдельных файлах. Удостоверьтесь, что теперь оболочка автоматически генерирует установленное нами составное пространство имен MyCompany.MyComponents.
Выполните команду Project/Add Reference, перейдите на вкладку .NET и, удерживая клавишу Ctrl, добавьте к проекту ссылки на библиотечные сборки (сборка mscorlib.dll подключается автоматически):System.dll System.Windows.Forms.dll System.Drawing.dll
Раскройте в панели Solution Explorer узел References и удалите из проекта ссылки на ненужные сборки, автоматически сгенерированные мастером оболочки
Откройте панель Object Browser, установите раскрывающийся список Browse в значение .NET Framework, раскройте сборку System.Windows.Forms, в которой найдите класс System.Windows.Forms.Control. Убедитесь, что класс Control является потомком класса System.ComponentModel.Component
Это значит, что в своем коде мы можем наследовать класс FirstComponent не от базового класса Component, как это предполагал мастер создания заготовки компонента, а от визуального класса Control. В результате компонент FirstComponent будет иметь визуальное представление на форме как во время проектирования, так и во время выполнения.
Заполните файл FirstComponent.cs следующим кодомusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
namespace MyCompany.MyComponents
{
public partial class FirstComponent : Control
{
public FirstComponent()
{
InitializeComponent();
Init();
}
public FirstComponent(IContainer container)
{
container.Add(this);
InitializeComponent();
Init();
}
}
}
namespace MyCompany.MyComponents
{
// Определяем перечисление
public enum EnumType { Zero, One, Two, Three };
partial class FirstComponent
{
// Локальные поля
int integerProp;
string stringProp = "По умолчанию";
char charProp;
bool boolProp = false;
EnumType enumProp = EnumType.Zero;
SomeObject someObject;
// Общедоступные свойства для локальных полей
public int IntegerProp
{
get { return integerProp; }
set { integerProp = value; }
}
public string StringProp
{
get { return stringProp; }
set { stringProp = value; }
}
public char CharProp
{
get { return charProp; }
set { charProp = value; }
}
public bool BoolProp
{
get { return boolProp; }
set { boolProp = value; }
}
public EnumType EnumProp
{
get { return enumProp; }
set { enumProp = value; }
}
// Сложное свойство - объект
public SomeObject SomeObj
{
get { return someObject; }
set { someObject = value; }
}
void Init()
{
someObject = new SomeObject();
this.BackColor = System.Drawing.Color.Red;
}
}
}
namespace MyCompany.MyComponents
{
public class SomeObject : Object
{
int prop1;
string prop2;
// Свойства
public int Prop1
{
get { return prop1; }
set { prop1 = value; }
}
public string Prop2
{
get { return prop2; }
set { prop2 = value; }
}
// Инициализация полей через конструктор
public SomeObject()
{
prop1 = 2008;
prop2 = "Привет";
}
}
}
В приведенном начальном коде мы добавили в класс компонента публичные свойства разных типов, включая перечисление и экземпляр стороннего класса. Эти свойства обертывают локальные базовые поля класса. Причем, часть полей мы инициализировали начальными значениями.
Мы поменяли базовый класс Component на Control для того, чтобы сделать компонент визуальным. Базовые поля для свойств класса SomeObject мы инициализировали в конструкторе по умолчанию (для разнообразия). В методе Init() мы создали экземпляр класса SomeObject и инициализировали им внутреннюю ссылку, чтобы при создании самого компонента ссылка бы адресовалась к уже существующему объекту. Мы, также, установили цвет фона компонента для его видимости на вызывающей форме.
Вызов метода Init() поместили внутрь перегруженных версий конструктора класса компонента последним. Конструкторы компонента выполняются оболочкой и на этапе проектирования. Для контролируемого доступа к ссылке на объект извне предусмотрели внутри класса компонента общедоступное свойство. Класс компонента мы разбили на отдельные части, упаковав их в одно и то же пространство имен.
Теперь нужно посмотреть, что у нас на данном этапе получилось. Для этого нужно откомпилировать компонент до уровня DLL и поместить его на пробную форму.
Добавьте к решению MySolution новый проект ComponentTest типа приложения, выполнив команду меню File/Add/New Project и настроив мастер согласно рисунка
В панели Solution Explorer щелкните правой кнопкой мыши на узле проекта ComponentTest и выполните команду Set as StartUp Project, чтобы сделать новое приложение стартовымОбратите внимание, что после этой команды оболочка выделила узел проекта полужирным стилем (если он уже не был выделен). В решении только один проект может быть стартовым.
Перейдите в Мы видим, что пока панель содержит 9 свитков

Теперь нужно откомпилировать проект MyComponents. Для этого в панели Solution Explorer щелкните правой кнопкой мыши на корневом узле проекта и выполните команду Build

Перейдите в Теперь мы видим, что после компиляции в панели Toolbox оболочки появился новый свиток с именем проекта компонента, в котором находится создаваемый компонент FirstComponent. То есть, оболочка сразу стала реагировать на появление нового компонента

Поместите компонент FirstComponent на форму Form1, выделите его экземпляр и исследуйте свойства в панели Properties оболочкиМы видим, что в исполняемом файле программы оболочка автоматически создала экземпляр компонента с дежурным именем firstComponent1. Наряду со свойствами, наследуемыми компонентом от класса Control, в панели свойств отображаются все наши публичные свойства экземпляра компонента, такие, как IntegerProp, StringProp, CharProp, BoolProp, EnumProp. Они обертывают объекты простых MyCompany.MyComponents.SomeObject.
Это связано с тем, что панель Properties способна представлять и редактировать свойства только как строки. Свойства простых
Чтобы обеспечить корректное развертывание и редактирование свойств вложенного объекта, необходимо прикрепить к его описанию атрибут System.ComponentModel.TypeConverter,ссылающийся на библиотечный тип System.ComponentModel.ExpandableObjectConverter. Атрибут является дополнительной информацией для компилятора сделать сложное свойство компонента развертываемым. Прикрепленный атрибут является классом, по которому оболочка сама создаст объект, обеспечивающий развертывание и редактирование свойств вложенного объекта.
Перед описанием класса SomeObject в файле FirstComponent.cs вставьте соответствующую инструкцию прикрепления атрибута, чтобы заголовок класса SomeObject стал такимnamespace MyCompany.MyComponents
{
// Прикрепляем простой атрибут для возможности развертывания
// и редактирования свойств объекта в панели Properties оболочки
[TypeConverter(typeof(ExpandableObjectConverter))]
public class SomeObject : Object
{
int prop1;
string prop2;
// Свойства
public int Prop1
{
get { return prop1; }
set { prop1 = value; }
}
public string Prop2
{
get { return prop2; }
set { prop2 = value; }
}
// Инициализация полей через конструктор
public SomeObject()
{
prop1 = 2008;
prop2 = "Привет";
}
}
}
В панели Solution Explorer вызовите контекстное меню для узла проекта MyComponents и выполните команду Build, чтобы откомпилировать новую редакцию компонента
Перейдите на форму Form1, опять выделите экземпляр нашего компонента и исследуйте его свойство SomeObj Теперь сложное свойство SomeObj вложенного объекта стало разварачиваться и редактироваться. Но в свернутом состоянии оно все еще не отображает строку со значениями вложенных свойств, как это принято в библиотечных компонентах.
Чтобы исправить это, нужно переопределить методы класса ExpandableObjectConverter, которые он унаследовал от базового класса TypeConverter:
Для решения этой задачи нужно создать вспомогательный класс, который мы назовем SomeObjectConverter, производный от библиотечного класса ExpandableObjectConverter, и переопределить в нем перечисленные виртуальные методы.
Через контекстное меню панели Solution Explorer добавьте к проекту MyComponents новый файл с именем Converter.cs
Наполните файл Converter.cs следующим кодомusing System;
using System.ComponentModel;
namespace MyCompany.MyComponents
{
// Контролирующий класс, предоставляющий возможность
// развертывания и редактирования свойств вложенных объектов
public class SomeObjectConverter : ExpandableObjectConverter
{
// Проверяем возможность преобразования типов
public override bool CanConvertFrom
(ITypeDescriptorContext context, Type sourceType)
{
// Проверяем тип составной строки
if (sourceType == typeof(System.String))
return true;
else
return base.CanConvertFrom(context, sourceType);
}
// Преобразуем составную строку в значения свойств
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
string sourceStr = value as System.String;
// Если очистить строку, восстанавливаем исходный объект
if (sourceStr == "")
{
return new SomeObject();
}
else
{
// Расщепляем строку свойств
char[] separator ={ ';' };
string[] PropStrings = sourceStr.Split(separator);
// Проверяем число элементов строки и в случае несовпадения
// генерируем исключение, перехватываемое оболочкой
if (PropStrings.Length != 2)
throw new ArgumentException(
"Требуется формат: два значения с
разделителем ';'");
// Создаем новый объект и присваиваем свойствам новые значения
SomeObject obj = new SomeObject();
obj.Prop1 = Convert.ToInt32(PropStrings[0].Trim());
obj.Prop2 = PropStrings[1].Trim();
return obj;
}
}
// Преобразуем значения свойств в составную строку
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value, Type destinationType)
{
SomeObject obj = (SomeObject)value;
return obj.Prop1.ToString() + "; " + obj.Prop2;
}
}
}
Замените в объявлении класса SomeObject введенный ранее простой атрибут, ссылающийся на базовый класс ExpandableObjectConverter, новым атрибутом, ссылающимся на производный класс SomeObjectConverter namespace MyCompany.MyComponents
{
// Прикрепляем простой атрибут для возможности развертывания
// и редактирования свойств объекта в панели Properties оболочки
//[TypeConverter(typeof(ExpandableObjectConverter))]
// Прикрепляем атрибут с контролирующим классом для возможности
// редактирования свойств объекта в панели Properties оболочки
[TypeConverter(typeof(SomeObjectConverter))]
public class SomeObject : Object
{
int prop1;
string prop2;
// Свойства
public int Prop1
{
get { return prop1; }
set { prop1 = value; }
}
public string Prop2
{
get { return prop2; }
set { prop2 = value; }
}
// Инициализация полей через конструктор
public SomeObject()
{
prop1 = 2008;
prop2 = "Привет";
}
}
}
Вспомогательный класс SomeObjectConverter, будучи прикрепленным в качестве атрибута к классу SomeObject, преобразует содержимое свойств Prop1 и Prop2 в строку и обратно. Это действие выполняется классом с помощью его
В панели Solution Explorer вызовите контекстное меню для узла проекта MyComponents и выполните команду Build, чтобы откомпилировать новую редакцию компонента
Перейдите на форму Form1, опять выделите экземпляр нашего компонента и исследуйте его свойство SomeObjЗамечание: Если после произведенных дополнений к коду компонента поведение свойства SomeObj в панели Properties не изменилось, то перезагрузите саму оболочку Visual Studio 2005.
Теперь сложное свойство SomeObj вложенного объекта экземпляра компонента помимо прежней функциональности разворачиваться и редактироваться в панели Properties стало отображаться и редактироваться в составной строке самого свойства.
Обратите внимание, что значения введенных нами свойств компонента отображаются в панели Properties жирным шрифтом. При работе с библиотечными компонентами мы привыкли, что жирным шрифтом отображаются только измененные значения свойств, отличные от настроек по умолчанию. Это именно те значения, которые визуальный конструктор форм оболочки автоматически присваивает свойствам в методе InitializeComponent() секции #region.
Чтобы убедиться в этом, откройте в панели Solution Explorer файл Form1.Designer.cs тестового проекта ComponentTest:#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
MyCompany.MyComponents.SomeObject someObject1
= new MyCompany.MyComponents.SomeObject();
this.firstComponent1 = new MyCompany.MyComponents
.FirstComponent(this.components);
this.SuspendLayout();
//
// firstComponent1
//
this.firstComponent1.BackColor = System.Drawing.Color.Red;
this.firstComponent1.BoolProp = false;
this.firstComponent1.CharProp = '\0';
this.firstComponent1.EnumProp =
MyCompany.MyComponents.EnumType.Zero;
this.firstComponent1.IntegerProp = 0;
this.firstComponent1.Location = new System.Drawing.Point(233, 139);
this.firstComponent1.Name = "firstComponent1";
this.firstComponent1.Size = new System.Drawing.Size(75, 23);
someObject1.Prop1 = 2008;
someObject1.Prop2 = "Привет";
this.firstComponent1.SomeObj = someObject1;
this.firstComponent1.StringProp = "По умолчанию";
this.firstComponent1.TabIndex = 5;
this.firstComponent1.Text = "firstComponent1";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(415, 190);
this.Controls.Add(this.firstComponent1);
this.MaximizeBox = false;
this.Name = "Form1";
this.Text = "Испытание компонентов";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
Для того, чтобы заставить оболочку в отношении некоторых значений свойств не воспринимать их как измененные, нужно присоединить к этим свойствам атрибут [DefaultValue] с теми значениями, которые она должна считать заданными по умолчанию. В результате при компиляции в код компонента будет встроена дополнительная информация, какие значения свойств считать базовыми и пока они не изменены пользователем, специально не присваивать при создании экземпляра компонента на форме.
В файле FirstComponent.cs присоедините к некоторым свойствам атрибут [DefaultValue] информирования среды, какие значения считать базовымиusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
namespace MyCompany.MyComponents
{
public partial class FirstComponent : Control
{
public FirstComponent()
{
InitializeComponent();
Init();
}
public FirstComponent(IContainer container)
{
container.Add(this);
InitializeComponent();
Init();
}
}
}
namespace MyCompany.MyComponents
{
// Определяем перечисление
public enum EnumType { Zero, One, Two, Three };
partial class FirstComponent
{
// Локальные поля
int integerProp;
string stringProp = "По умолчанию";
char charProp;
bool boolProp = false;
EnumType enumProp = EnumType.Zero;
SomeObject someObject;
// Общедоступные свойства для локальных полей
public int IntegerProp
{
get { return integerProp; }
set { integerProp = value; }
}
[DefaultValue("По умолчанию")] //Атрибут значения по умолчанию
public string StringProp
{
get { return stringProp; }
set { stringProp = value; }
}
public char CharProp
{
get { return charProp; }
set { charProp = value; }
}
[DefaultValue(false)] // Атрибут значения по умолчанию
public bool BoolProp
{
get { return boolProp; }
set { boolProp = value; }
}
[DefaultValue(EnumType.Zero)] // Атрибут значения по умолчанию
public EnumType EnumProp
{
get { return enumProp; }
set { enumProp = value; }
}
// Сложное свойство - объект
public SomeObject SomeObj
{
get { return someObject; }
set { someObject = value; }
}
void Init()
{
someObject = new SomeObject();
this.BackColor = System.Drawing.Color.Red;
}
}
}
namespace MyCompany.MyComponents
{
// Присоединяем атрибут с контролирующим классом для возможности
// редактирования свойств объекта в панели Properties оболочки
[TypeConverter(typeof(SomeObjectConverter))]
//[TypeConverter(typeof(ExpandableObjectConverter))]
public class SomeObject : Object
{
int prop1;
string prop2;
// Свойства
[DefaultValue(2008)] // Атрибут значения по умолчанию
public int Prop1
{
get { return prop1; }
set { prop1 = value; }
}
[DefaultValue("Привет")] // Атрибут значения по умолчанию
public string Prop2
{
get { return prop2; }
set { prop2 = value; }
}
// Инициализация полей через конструктор
public SomeObject()
{
prop1 = 2008;
prop2 = "Привет";
}
}
}
Откомпилируйте проект MyComponents, удалите с формы Form1 компонент firstComponent1 и вновь поместите его на форму из панели Toolbox, чтобы оболочка перегенерировала код в секции #region Если теперь вновь посмотреть содержимое файла Form1.Designer.cs, то мы не увидим явного присвоения начальных значений свойствам экземпляра компонента, помеченным атрибутом [DefaultValue]. Явное присвоение появится только тогда, когда пользователь компонента задаст свойствам новые значения, отличные от установленных атрибутом.
В этом примере мы создадим невизуальный компонент Planets, инкапсулирующий в себе названия планет Солнечной системы, и добавим его в нашу библиотеку компонентов сборки MyComponents. Названия планет будут храниться во внутреннем массиве компонента, а доступ к ним будет осуществляться через
В панели Solution Explorer выделите проект MyComponents и выполните команду меню Project/Add Component, чтобы добавить файл Planets.cs нового компонента
Дополните содержимое файла, автоматически сгенерированное мастером, следующим кодомusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
namespace MyCompany.MyComponents
{
public partial class Planets : Component
{
public Planets()
{
InitializeComponent();
}
public Planets(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
namespace MyCompany.MyComponents
{
partial class Planets
{
// Планеты
private string[] PlanetNames ={ "Меркурий",
"Венера",
"Земля",
"Марс",
"Юпитер",
"Сатурн",
"Уран",
"Нептун",
"Плутон"
};
// Вернуть имя
private string GetPlanetName(int index)
{
// Нижняя и верхняя границы массива
int lowP = 0, highP = PlanetNames.Length - 1;
// Контролирует диапазон индекса планеты и возвращает
// ее название или генерирует исключение
if (index < lowP || index > highP)
{
MessageBox.Show(String.Format("Индекс
должен находиться в диапазоне {0}-{1}",
lowP, highP));
index = 0;
}
return PlanetNames[index];
}
// Вернуть индекс
private int GetPlanetPosition(string planetName)
{
// Сравниваем переданное имя планеты с массивом
// PlanetNames. При несовпадении возвращаем -1
int result=-1;
for(int i=0; i<PlanetNames.Length;i++)
if (String.Compare(planetName, PlanetNames[i], true) == 0)
{
result = i;
break;
}
return result;
}
// Свойство - индексатор: возвращает имя планеты по индексу
public string this[int index]
{
get {return GetPlanetName(index); }
}
// Свойство - индексатор: возвращает индекс планеты по имени
public int this[string planetName]
{
get { return GetPlanetPosition(planetName); }
}
// Свойство максимального размера массива
public int MaxIndex
{
get { return PlanetNames.Length - 1; }
}
}
}
В коде мы объявили массив с планетами и сразу инициализировали его. Создали два внутренних контролирующих метода, возвращающих название планеты по ее индексу и наоборот. Добавили два общедоступных свойства -
Теперь осталось перекомпилировать сборку MyComponents.dll, в которой в одном пространстве имен MyCompany.MyComponents будут находиться уже два наших компонента, и испытать новый невизуальный компонент.
В панели Solution Explorer вызовите контекстное меню для узла проекта MyComponets и выполните команду Rebuild, чтобы перекомпилировать проект с компонентами
Убедитесь, что в панели Toolbox появился новый компонент Planets, который теперь можно перетаскивать на форму также, как и обычный библиотечный компонент
В проекте ComponentTest настройте пользовательский интерфейс, как показано на рисунке и в таблице (в скобках приведены имена экземпляров компонентов)
| Элемент | Свойство | Значение |
|---|---|---|
Form1 |
MaximizeBox |
false |
Size |
324; 224 | |
Text |
Испытание компонентов | |
listPlanets |
FormattingEnabled |
true |
ItemHeight |
16 | |
Location |
12; 12 | |
Size |
109; 164 | |
indexPlanet |
Location |
160; 153 |
Size |
26; 22 | |
textBox1 |
BorderStyle |
None |
Font |
Microsoft |
|
Location |
138; 21 | |
Multiline |
true |
|
ReadOnly |
true |
|
Size |
166; 107 | |
Text |
Введите индекс планеты и щелкните на красном | |
TextAlign |
Center |
|
firstComponent1 |
Location |
201; 153 |
Size |
75; 23 |
В панели Properties перейдите на вкладку Events и создайте обработчики для элементов согласно таблице| Элемент | Событие | Имя обработчика |
|---|---|---|
listPlanets |
SelectedIndexChanged |
listPlanets_SelectedValueChanged |
indexPlanet |
|
indexPlanet_KeyPress |
firstComponent1 |
Click |
firstComponent1_Click |
Заполните файл Form1.cs следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace ComponentTest
{
public partial class Form1 : Form
{
// Конструктор формы
public Form1()
{
InitializeComponent();
// Заполнение списка планетами
for (int i = 0; i <= planets1.MaxIndex; i++)
{
listPlanets.Items.Add(String.Format(
"{0}) {1}", i, planets1[i]));
}
listPlanets.SelectedIndex = 0;
}
// Обработчики событий
private void firstComponent1_Click(object sender, EventArgs e)
{
// Контролируем пустой ввод
if (indexPlanet.Text == String.Empty) return;
int index = Convert.ToInt32(indexPlanet.Text);
// Контролируем максимальный индекс ввода
index = Math.Min(index, planets1.MaxIndex);
if (listPlanets.SelectedIndex != index)
listPlanets.SelectedIndex = index;
else
MessageBox.Show(String.Format("Вы выбрали
планету {0}", planets1[index]));
}
private void indexPlanet_KeyPress(object sender,
KeyPressEventArgs e)
{
// Фильтруем цифры, Backspace, Enter (Delete и стрелки по умолчанию)
if ((e.KeyChar < Convert.ToChar(Keys.D0) ||
e.KeyChar > Convert.ToChar(Keys.D9))
e.KeyChar != Convert.ToChar(Keys.Back)
e.KeyChar != Convert.ToChar(Keys.Enter))
e.Handled = true;
// Реакция на клавишу Enter
if (e.KeyChar == Convert.ToChar(Keys.Enter))
firstComponent1_Click(null, EventArgs.Empty);
}
bool loadFlag = true; // Локальное поле-флаг
private void listPlanets_SelectedValueChanged
(object sender, EventArgs e)
{
int index = listPlanets.SelectedIndex;
indexPlanet.Text = index.ToString();
if (loadFlag)
{
// При первом запуске не показывать
loadFlag = false;
return;
}
else
MessageBox.Show(String.Format("Вы выбрали
планету {0}", planets1[index]));
}
}
}
Откомпилируйте приложение текущего уровня готовности и испытайте его работуСобытие ( event ) можно определить как любое происшествие, вызванное вмешательством пользователя, операционной системы или логикой программы. Любое событие можно обнаружить и нужным образом на него отреагировать с помощью обработчиков события. Событие в пользовательском классе определяется с помощью делегата, задающего сигнатуру потенциальных обработчиков и содержащего список указателей на все подписавшиеся на это событие обработчики. Если событие имеет аргументы, то через них можно передавать любую информацию в обработчики этого события.
Событие может прослушивать любой блок кода программы, в котором используется экземпляр класса, генерирующего событие, если только этот код зарегистрирует у себя это событие и создаст обработчик. События являются удобным механизмом общения объектов между собой и создания нужных реакций на те или иные происшествия. Событие объявляется в пользовательском классе как специальное публичное свойство для множественной адресации потенциальных обработчиков.
В классе также создается одноименный событию защищенный виртуальный метод, обычно с префиксом On, для генерации события при наступлении определенного происшествия. Этот метод называется методом диспетчеризации события ( event- ). Метод диспетчеризации, перед генерацией события, должен проверять, существует ли хотя бы один зарегистрированный обработчик этого события, чтобы не адресоваться к нулевому обработчику. Он также устанавливает значения
Рассмотрим механизм работы событий на примере создания невизуального компонента AlarmClock (будильник), передающего оповещение по прошествии указанного времени. Хотя встроить подобный код внутрь самого приложения вовсе не сложно, создание такого компонента позволит отделить его от остального исходного кода и использовать многократно в других приложениях (а удачные компоненты можно и продавать). В качестве объекта, инициирующего генерацию нашего события, будем использовать экземпляр
Добавьте к проекту MyComponents новый файл с именем AlarmClock.cs типа Component
Переведите редактирование компонента AlarmClock в режим Design и двойным щелчком в панели Toolbox добавьте к компоненту экземпляр
Выделите объект timer1 и через панель Properties установите свойство Interval=1000
Переведите панель Properties в режим Events, установите для события Tick срабатывания Оболочка добавит в файл AlarmClock.cs заготовку обработчика TimerHandler события срабатывания
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
//-------------------------------------------------------------
// Часть 0 расположена в отдельном файле AlarmClock.Designer.cs
//-------------------------------------------------------------
// Часть 1
namespace MyCompany.MyComponents
{
public partial class AlarmClock : Component
{
public AlarmClock()
{
InitializeComponent();
}
public AlarmClock(IContainer container)
{
container.Add(this);
InitializeComponent();
}
// Обработчик события срабатывания системного таймера, созданного,
// настроенного и зарегистрированного через визуальный конструктор.
// Обработчик выполняется каждую секунду и проверяет, пришла ли пора
// генерировать наше событие, на которое подпишется клиент компонента
private void TimerHandler(object sender, EventArgs e)
{
}
}
}
Для удобства класс компонента разбьем на отдельные части по функциональной принадлежности, что никак не повлияет на логику выполнения программы. Для еще большего удобства эти части класса можно размещать в отдельных файлах проекта компонентов, например, AlarmClockPart1.cs, AlarmClockPart2.cs, и т.д. (на любителя...).
Выделите заготовку обработчика в отдельную часть, которую мы заполним позднее// Часть 4
namespace MyCompany.MyComponents
{
// Часть класса с определением обработчика события Tick
// системного таймера System.Windows.Forms.Timer timer1
partial class AlarmClock
{
// Обработчик события срабатывания системного таймера, созданного,
// настроенного и зарегистрированного через визуальный конструктор.
// Обработчик выполняется каждую секунду и проверяет, пришла ли пора
// генерировать наше событие, на которое подпишется клиент компонента
private void TimerHandler(object sender, EventArgs e)
{
}
}
}
Добавьте в файл AlarmClock.cs часть класса, содержащую определения свойств компонента для его настройки как в режиме проектирования, так и выполнения// Часть 2
namespace MyCompany.MyComponents
{
// Часть класса с определением полей и свойств
partial class AlarmClock
{
// Объявляем внутренние поля
bool alarmFired = false; // Состояние будильника (запущен/незапущен)
DateTime alarmTime = DateTime.Now; // Хранит время запуска будильника
// Вложенный класс аргументов, доступных в обработчике
// Расширяет библиотечный класс аргументов событий
public class AlarmEventArgs : EventArgs
{
// Поле
DateTime time;
// Свойство
public DateTime Time
{
get { return time; }
set { time = value; }
}
}
// Свойство возвращает текущее время компьютера
public DateTime CurrentTime
{
get { return DateTime.Now; }
}
// Свойство для чтения и установки времени запуска будильника
public DateTime AlarmTime
{
get { return alarmTime; }
set
{
if (value != alarmTime)
{
alarmTime = value;
alarmFired = false;
}
}
}
// Свойство для проверки состояния хода системного таймера,
// его остановки и запуска
public bool Enabled
{
get { return timer1.Enabled; }
set
{
timer1.Enabled = value;
if (value)
alarmFired = false;
}
}
}
}
В этом разделе мы добавим к классу
Добавьте в файл AlarmClock.cs часть класса, содержащую определение собственного события стандартным способом без использования базового поля// Часть 3
namespace MyCompany.MyComponents
{
// Часть класса с определением собственного события
// стандартным способом без использования базового поля
partial class AlarmClock
{
// Объявляем делегат для события. В аргументах делегата
// нужно указать полное имя вложенного класса
public delegate void AlarmHandler(object sender,
AlarmClock.AlarmEventArgs e);
// Объявляем событие с доступностью не ниже делегата
public event AlarmHandler Alarm;
// Метод диспетчеризации события. Виртуальный и защищенный
// для возможности переопределения в будущих потомках
protected virtual void OnAlarm(AlarmEventArgs args)
{
// Проверяем наличие зарегистрированных
// обработчиков и генерируем событие
if (Alarm != null)
Alarm(this, args);
}
}
}
Модифицируйте часть класса с обработчиком события Tick alarmTime срабатывания будильника// Часть 4
namespace MyCompany.MyComponents
{
// Часть класса с определением обработчика события Tick
// системного таймера System.Windows.Forms.Timer timer1
partial class AlarmClock
{
// Обработчик события срабатывания системного таймера, созданного,
// настроенного и зарегистрированного через визуальный конструктор.
// Обработчик выполняется каждую секунду и проверяет, пришла ли пора
// генерировать наше событие, на которое подпишется клиент компонента
private void TimerHandler(object sender, EventArgs e)
{
DateTime now; // Текущее системное время
AlarmEventArgs args;
// В режиме разработки не выполнять
if (!this.DesignMode)
{
now = DateTime.Now;
// Если будильник не запущен и пришла пора запускать
if (!alarmFired now >= alarmTime)
{
// Создаем объект для передачи аргументов в событии
args = new AlarmEventArgs();
// Заполняем объект текущим временем
args.Time = now;
// Вызываем метод диспетчеризации события Alarm
this.OnAlarm(args);
// Поднимаем флаг "Будильник запущен"
alarmFired = true;
}
}
}
}
}
Откомпилируйте библиотеку наших компонентов, выполнив команду Build/Build MyComponents
Перейдите в режим редактирования Form1.cs [Design] и убедитесь, что в панели Toolbox после компиляции появился новый компонент AlarmClock
Поместите новый компонент AlarmClock из панели Toolbox на форму Form1 приложения ComponentTest, выделите его экземпляр alarmClock1 и через панель Properties в режиме Events создайте обработчик для события Alarm
Обработчик, добавленный в класс Form1 приложения ComponentTest, заполните следующим кодомprivate void alarmClock1_Alarm(object sender,
AlarmClock.AlarmEventArgs e)
{
// Если оператор выключения будильника поставить после
// вызова модального диалогового окна, то генерация
// событий прекратиться только после закрытия
// очередного модального окна, поскольку только тогда
// управление дойдет до этого оператора
alarmClock1.Enabled = false;// Останавливаем системный таймер
int hour = alarmClock1.CurrentTime.Hour;
int minute = alarmClock1.CurrentTime.Minute;
int second = alarmClock1.CurrentTime.Second;
MessageBox.Show(String.Format("Сработал будильник!!!\n"
+ "Текущее время: {0} ч. {1} м. {2} с.",
hour, minute, second),
"Доцент Снетков В.М.");
}
Добавьте в конец конструктора формы Form1 код установки времени срабатывания будильника и запуска // Конструктор формы
public Form1()
{
InitializeComponent();
// Заполнение списка планетами
for (int i = 0; i <= planets1.MaxIndex; i++)
{
listPlanets.Items.Add(String.Format(
"{0}) {1}", i, planets1[i]));
}
listPlanets.SelectedIndex = 0;
// Устанавливаем время срабатывания будильника
// через 5 сек. после запуска системного таймера
alarmClock1.AlarmTime = DateTime.Now.AddSeconds(5d);
alarmClock1.Enabled = true;// Запускаем системный таймер
}
Запустите приложение и убедитесь, что обработчик нашего события Alarm срабатывает через 5 секунд после запуска
Рассмотрим другой способ работы с событиями, использующий базовое поле типа делегата. Для этого модифицируем наш компонент таким образом, чтобы будильник срабатывал два раза: по первому и второму событию. Но второе событие определим по иному.
Перейдите в режим редактирования компонента AlarmClock.cs [Design] и добавьте из панели Toolbox двойным щелчком кнопки мыши еще один
Выделите экземпляр таймера timer2 и в панели Properties установите свойство Interval=1000
Создайте для объекта timer2 обработчик события Tick с именем TimerHandlerExt, который переместите в отдельную часть класса AlarmClock в файле AlarmClock.cs. Заполните эту часть следующим кодом// Часть 5
namespace MyCompany.MyComponents
{
// Часть класса с определением обработчика события Tick
// системного таймера System.Windows.Forms.Timer timer2
partial class AlarmClock
{
// Объявляем внутренние поля
bool alarmFiredExt = false; // Состояние будильника (запущен/незапущен)
DateTime alarmTimeExt = DateTime.Now; // Хранит время запуска будильника
// Свойство для чтения и установки времени запуска будильника
public DateTime AlarmTimeExt
{
get { return alarmTimeExt; }
set
{
if (value != alarmTimeExt)
{
alarmTimeExt = value;
alarmFiredExt = false;
}
}
}
// Свойство для проверки состояния хода второго
// системного таймера, его остановки и запуска
public bool EnabledExt
{
get { return timer2.Enabled; }
set
{
timer2.Enabled = value;
if (value)
alarmFiredExt = false;
}
}
// Обработчик события срабатывания второго системного таймера
private void TimerHandlerExt(object sender, EventArgs e)
{
DateTime now; // Текущее системное время
AlarmEventArgs args;
// В режиме разработки не выполнять
if (!this.DesignMode)
{
now = DateTime.Now;
// Если будильник не запущен и пришла пора запускать
if (!alarmFiredExt now >= alarmTimeExt)
{
// Создаем объект для передачи аргументов в событии
args = new AlarmEventArgs();
// Заполняем объект текущим временем
args.Time = now;
// Вызываем метод диспетчеризации события AlarmExt
this.OnAlarmExt(args);
// Поднимаем флаг "Будильник запущен"
alarmFiredExt = true;
}
}
}
}
}
Создайте часть класса компонента, объявляющего // Часть 6 Extension
namespace MyCompany.MyComponents
{
// Часть класса с определением собственного события
// стандартным способом без использования базового поля
partial class AlarmClock
{
// Закрытое поле-ссылка на экземпляр делегата
// Для объявления поля решили использовать уже существующий
// делегат, но можно объявить и другой делегат с той же сигнатурой
private AlarmHandler m_AlarmExt;
// Объявляем пользовательское событие
// с возможностью контроля доступа к нему
public event AlarmHandler AlarmExt
{
// Эта функция будет вызвана при попытке
// добавления обработчика в список вызова события
add
{
// Здесь что-то можно проконтролировать, например, что
// произошла попытка добавить обработчик в список вызова события
Console.WriteLine("Добавлен обработчик в
список события AlarmExt");
// Расширяем список объекта-делегата, ссылающегося на обработчики события
m_AlarmExt += value;
}
// Эта функция будет вызвана при попытке
// изъятия обработчика из списка вызова события
remove
{
// Здесь что-то можно проконтролировать, например, что
// произошла попытка изъять обработчик из списка вызова события
Console.WriteLine("Изъят обработчик из
списка события AlarmExt");
// Удалим обработчик из списка объекта-делегата
m_AlarmExt -= value;
}
}
// Метод диспетчеризации события. Виртуальный и защищенный
// для возможности переопределения в будущих потомках
protected virtual void OnAlarmExt(AlarmEventArgs args)
{
// Проверяем наличие зарегистрированных обработчиков
// и генерируем событие через поле-делегат
if (m_AlarmExt != null)
m_AlarmExt(this, args);
}
}
}
Откомпилируйте библиотеку наших компонентов, выполнив команду Build/Build MyComponents
Перейдите в файл Form1.cs [Design] и добавьте в конструктор класса тестового приложения код настройки второго alarmClock1 // Конструктор формы
public Form1()
{
InitializeComponent();
// Заполнение списка планетами
for (int i = 0; i <= planets1.MaxIndex; i++)
{
listPlanets.Items.Add(String.Format(
"{0}) {1}", i, planets1[i]));
}
listPlanets.SelectedIndex = 0;
// Устанавливаем время срабатывания будильника
// через 5 сек. после запуска системного таймера
alarmClock1.AlarmTime = DateTime.Now.AddSeconds(5d);
alarmClock1.Enabled = true;// Запускаем системный таймер
// Настраиваем второй системный таймер компонента
alarmClock1.AlarmTimeExt = DateTime.Now.AddSeconds(10d);
alarmClock1.EnabledExt = true;// Запускаем системный таймер
}
На форме Form1 приложения ComponentTest выделите экземпляр alarmClock1 и через панель Properties в режиме Events создайте обработчик для события AlarmExt
Обработчик заполните следующим кодомprivate void alarmClock1_AlarmExt(object sender,
AlarmClock.AlarmEventArgs e)
{
alarmClock1.EnabledExt = false;// Останавливаем второй системный таймер
int hour = alarmClock1.CurrentTime.Hour;
int minute = alarmClock1.CurrentTime.Minute;
int second = alarmClock1.CurrentTime.Second;
MessageBox.Show(String.Format("Сработал будильник!!!\n"
+ "Текущее время: {0} ч. {1} м. {2} с.",
hour, minute, second), "Доцент Снетков В.М.");
// Для генерации сообщения в консольный вывод
alarmClock1.AlarmExt -= alarmClock1_AlarmExt;
}
В панели Solution Explorer вызовите контекстное меню для узла приложения ComponentTest и выполните команду Properties
В появившемся окне настроек оболочки выберите вкладку Application и в раскрывающемся списке Output type включите окно консольного вывода Console Application 
Запустите приложение и убедитесь, что оба способа определения Основными видами создания программного продукта являются:
В данном разделе мы познакомимся с созданием компонентов. Компонент, это программная единица, способная поддерживать технологию визуального программирования. Это не только класс, обладающий нужной функциональностью на этапе выполнения, но и абстракция, поддерживаемая инструментами оболочки на этапе проектирования. В отличие от стандартных библиотечных компонентов, созданных разработчиками среды проектирования, их называют пользовательскими. Пользовательский компонент можно перетаскивать из панели Toolbox на форму, настраивать его свойства и события точно также, как это делается со стандартными компонентами библиотеки .NET Framework.
Стандартная процедура создания пользовательского компонента состоит из следующих шагов:
DLLДалее мы опробуем большинство из этих этапов на примере создания трех
В зависимости от выбора базового типа различают следующие компоненты
user control ) - расширение визуальных элементов управления, в основе которых лежит класс Controlnonvisual component ) - custom control ) - создание совершенно нового визуального компонента непосредственно из класса ControlНиже приведен список базовых классов среды .NET Framework, используемых при создании компонентов
| Класс .NET Framework | Описание |
|---|---|
Object |
Для создания компонента с нуля |
System.ComponentModel.Component |
Для наследования функциональности времени разработки в среде IDE |
System.Windows.Forms.Control |
Для разработки визуальных компонентов |
System.Windows.Forms. |
Работает подобно форме в IDE для композиции нескольких элементов управления. Используется при создании сложных визуальных компонентов, состоящих из нескольких других компонентов |
| Любой библиотечный компонент | Можно расширять любой библиотечный компонент путем наследования |
Создадим компонент под названием FirstComponent, который будет потомком класса System.Windows.Forms.Control сборки System.Windows.Forms. В свою очередь, этот библиотечный класс Control наследует от класса System.ComponentModel.Component сборки System. Нам понадобятся следующие пространства имен:
| Пространство имен | Библиотечная сборка |
|---|---|
System.Windows.Forms |
System.Windows.Forms.dll |
System.ComponentModel |
System.dll |
System.Drawing |
System.Drawing.dll |
System |
mscorlib.dll |
System.Collections |
mscorlib.dll |
Выполните команду File/New/Project оболочки Visual Studio 2005 и заполните окно мастера так
Мастер создаст каталог решения MySolution для размещения нескольких взаимосвязанных проектов, в который поместит каталог проекта MyComponents и будет автоматически обертывать каждый новый класс проекта при его создании пространством имен MyComponents.
Для тренировки нам нужно настроить оболочку на автоматическое генерирование другого пространства имен для всех файлов проекта, которое бы уникально характеризовало нашу библиотеку компонентов. Для будущих компонентов мы выберем составное пространство имен MyCompany.MyComponents.
В панели Solution Explorer вызовите контекстное меню для корневого узла проекта MyComponents и выполните команду Properties. Для вкладки Application скорректируйте текстовое поле Default namespace как показано на рисунке
Удалите автоматически созданный оболочкой файл Class1.cs
В конструкторе файла щелкните на соответствующей ссылке или выполните команду контекстного меню, чтобы перейти в режим редактирования кода
Код заготовки компонента, созданный мастером, будет выглядеть так
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace MyCompany.MyComponents
{
public partial class FirstComponent : Component
{
public FirstComponent()
{
InitializeComponent();
}
public FirstComponent(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
Обратите внимание, что класс объявлен как partial (частичный), что позволит для удобства чтения разбить его описание на отдельные части, или даже разместить эти описания в отдельных файлах. Удостоверьтесь, что теперь оболочка автоматически генерирует установленное нами составное пространство имен MyCompany.MyComponents.
Выполните команду Project/Add Reference, перейдите на вкладку .NET и, удерживая клавишу Ctrl, добавьте к проекту ссылки на библиотечные сборки (сборка mscorlib.dll подключается автоматически):System.dll System.Windows.Forms.dll System.Drawing.dll
Раскройте в панели Solution Explorer узел References и удалите из проекта ссылки на ненужные сборки, автоматически сгенерированные мастером оболочки
Откройте панель Object Browser, установите раскрывающийся список Browse в значение .NET Framework, раскройте сборку System.Windows.Forms, в которой найдите класс System.Windows.Forms.Control. Убедитесь, что класс Control является потомком класса System.ComponentModel.Component 
Это значит, что в своем коде мы можем наследовать класс FirstComponent не от базового класса Component, как это предполагал мастер создания заготовки компонента, а от визуального класса Control. В результате компонент FirstComponent будет иметь визуальное представление на форме как во время проектирования, так и во время выполнения.
Заполните файл FirstComponent.cs следующим кодомusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
namespace MyCompany.MyComponents
{
public partial class FirstComponent : Control
{
public FirstComponent()
{
InitializeComponent();
Init();
}
public FirstComponent(IContainer container)
{
container.Add(this);
InitializeComponent();
Init();
}
}
}
namespace MyCompany.MyComponents
{
// Определяем перечисление
public enum EnumType { Zero, One, Two, Three };
partial class FirstComponent
{
// Локальные поля
int integerProp;
string stringProp = "По умолчанию";
char charProp;
bool boolProp = false;
EnumType enumProp = EnumType.Zero;
SomeObject someObject;
// Общедоступные свойства для локальных полей
public int IntegerProp
{
get { return integerProp; }
set { integerProp = value; }
}
public string StringProp
{
get { return stringProp; }
set { stringProp = value; }
}
public char CharProp
{
get { return charProp; }
set { charProp = value; }
}
public bool BoolProp
{
get { return boolProp; }
set { boolProp = value; }
}
public EnumType EnumProp
{
get { return enumProp; }
set { enumProp = value; }
}
// Сложное свойство - объект
public SomeObject SomeObj
{
get { return someObject; }
set { someObject = value; }
}
void Init()
{
someObject = new SomeObject();
this.BackColor = System.Drawing.Color.Red;
}
}
}
namespace MyCompany.MyComponents
{
public class SomeObject : Object
{
int prop1;
string prop2;
// Свойства
public int Prop1
{
get { return prop1; }
set { prop1 = value; }
}
public string Prop2
{
get { return prop2; }
set { prop2 = value; }
}
// Инициализация полей через конструктор
public SomeObject()
{
prop1 = 2008;
prop2 = "Привет";
}
}
}
В приведенном начальном коде мы добавили в класс компонента публичные свойства разных типов, включая перечисление и экземпляр стороннего класса. Эти свойства обертывают локальные базовые поля класса. Причем, часть полей мы инициализировали начальными значениями.
Мы поменяли базовый класс Component на Control для того, чтобы сделать компонент визуальным. Базовые поля для свойств класса SomeObject мы инициализировали в конструкторе по умолчанию (для разнообразия). В методе Init() мы создали экземпляр класса SomeObject и инициализировали им внутреннюю ссылку, чтобы при создании самого компонента ссылка бы адресовалась к уже существующему объекту. Мы, также, установили цвет фона компонента для его видимости на вызывающей форме.
Вызов метода Init() поместили внутрь перегруженных версий конструктора класса компонента последним. Конструкторы компонента выполняются оболочкой и на этапе проектирования. Для контролируемого доступа к ссылке на объект извне предусмотрели внутри класса компонента общедоступное свойство. Класс компонента мы разбили на отдельные части, упаковав их в одно и то же пространство имен.
Теперь нужно посмотреть, что у нас на данном этапе получилось. Для этого нужно откомпилировать компонент до уровня DLL и поместить его на пробную форму.
Добавьте к решению MySolution новый проект ComponentTest типа приложения, выполнив команду меню File/Add/New Project и настроив мастер согласно рисунка
В панели Solution Explorer щелкните правой кнопкой мыши на узле проекта ComponentTest и выполните команду Set as StartUp Project, чтобы сделать новое приложение стартовымОбратите внимание, что после этой команды оболочка выделила узел проекта полужирным стилем (если он уже не был выделен). В решении только один проект может быть стартовым.
Перейдите в Мы видим, что пока панель содержит 9 свитков

Теперь нужно откомпилировать проект MyComponents. Для этого в панели Solution Explorer щелкните правой кнопкой мыши на корневом узле проекта и выполните команду Build

Перейдите в Теперь мы видим, что после компиляции в панели Toolbox оболочки появился новый свиток с именем проекта компонента, в котором находится создаваемый компонент FirstComponent. То есть, оболочка сразу стала реагировать на появление нового компонента

Поместите компонент FirstComponent на форму Form1, выделите его экземпляр и исследуйте свойства в панели Properties оболочкиМы видим, что в исполняемом файле программы оболочка автоматически создала экземпляр компонента с дежурным именем firstComponent1. Наряду со свойствами, наследуемыми компонентом от класса Control, в панели свойств отображаются все наши публичные свойства экземпляра компонента, такие, как IntegerProp, StringProp, CharProp, BoolProp, EnumProp. Они обертывают объекты простых MyCompany.MyComponents.SomeObject.
Это связано с тем, что панель Properties способна представлять и редактировать свойства только как строки. Свойства простых
Чтобы обеспечить корректное развертывание и редактирование свойств вложенного объекта, необходимо прикрепить к его описанию атрибут System.ComponentModel.TypeConverter,ссылающийся на библиотечный тип System.ComponentModel.ExpandableObjectConverter. Атрибут является дополнительной информацией для компилятора сделать сложное свойство компонента развертываемым. Прикрепленный атрибут является классом, по которому оболочка сама создаст объект, обеспечивающий развертывание и редактирование свойств вложенного объекта.
Перед описанием класса SomeObject в файле FirstComponent.cs вставьте соответствующую инструкцию прикрепления атрибута, чтобы заголовок класса SomeObject стал такимnamespace MyCompany.MyComponents
{
// Прикрепляем простой атрибут для возможности развертывания
// и редактирования свойств объекта в панели Properties оболочки
[TypeConverter(typeof(ExpandableObjectConverter))]
public class SomeObject : Object
{
int prop1;
string prop2;
// Свойства
public int Prop1
{
get { return prop1; }
set { prop1 = value; }
}
public string Prop2
{
get { return prop2; }
set { prop2 = value; }
}
// Инициализация полей через конструктор
public SomeObject()
{
prop1 = 2008;
prop2 = "Привет";
}
}
}
В панели Solution Explorer вызовите контекстное меню для узла проекта MyComponents и выполните команду Build, чтобы откомпилировать новую редакцию компонента
Перейдите на форму Form1, опять выделите экземпляр нашего компонента и исследуйте его свойство SomeObj Теперь сложное свойство SomeObj вложенного объекта стало разварачиваться и редактироваться. Но в свернутом состоянии оно все еще не отображает строку со значениями вложенных свойств, как это принято в библиотечных компонентах.
Чтобы исправить это, нужно переопределить методы класса ExpandableObjectConverter, которые он унаследовал от базового класса TypeConverter:
Для решения этой задачи нужно создать вспомогательный класс, который мы назовем SomeObjectConverter, производный от библиотечного класса ExpandableObjectConverter, и переопределить в нем перечисленные виртуальные методы.
Через контекстное меню панели Solution Explorer добавьте к проекту MyComponents новый файл с именем Converter.cs
Наполните файл Converter.cs следующим кодомusing System;
using System.ComponentModel;
namespace MyCompany.MyComponents
{
// Контролирующий класс, предоставляющий возможность
// развертывания и редактирования свойств вложенных объектов
public class SomeObjectConverter : ExpandableObjectConverter
{
// Проверяем возможность преобразования типов
public override bool CanConvertFrom
(ITypeDescriptorContext context, Type sourceType)
{
// Проверяем тип составной строки
if (sourceType == typeof(System.String))
return true;
else
return base.CanConvertFrom(context, sourceType);
}
// Преобразуем составную строку в значения свойств
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
string sourceStr = value as System.String;
// Если очистить строку, восстанавливаем исходный объект
if (sourceStr == "")
{
return new SomeObject();
}
else
{
// Расщепляем строку свойств
char[] separator ={ ';' };
string[] PropStrings = sourceStr.Split(separator);
// Проверяем число элементов строки и в случае несовпадения
// генерируем исключение, перехватываемое оболочкой
if (PropStrings.Length != 2)
throw new ArgumentException(
"Требуется формат: два значения с
разделителем ';'");
// Создаем новый объект и присваиваем свойствам новые значения
SomeObject obj = new SomeObject();
obj.Prop1 = Convert.ToInt32(PropStrings[0].Trim());
obj.Prop2 = PropStrings[1].Trim();
return obj;
}
}
// Преобразуем значения свойств в составную строку
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value, Type destinationType)
{
SomeObject obj = (SomeObject)value;
return obj.Prop1.ToString() + "; " + obj.Prop2;
}
}
}
Замените в объявлении класса SomeObject введенный ранее простой атрибут, ссылающийся на базовый класс ExpandableObjectConverter, новым атрибутом, ссылающимся на производный класс SomeObjectConverter namespace MyCompany.MyComponents
{
// Прикрепляем простой атрибут для возможности развертывания
// и редактирования свойств объекта в панели Properties оболочки
//[TypeConverter(typeof(ExpandableObjectConverter))]
// Прикрепляем атрибут с контролирующим классом для возможности
// редактирования свойств объекта в панели Properties оболочки
[TypeConverter(typeof(SomeObjectConverter))]
public class SomeObject : Object
{
int prop1;
string prop2;
// Свойства
public int Prop1
{
get { return prop1; }
set { prop1 = value; }
}
public string Prop2
{
get { return prop2; }
set { prop2 = value; }
}
// Инициализация полей через конструктор
public SomeObject()
{
prop1 = 2008;
prop2 = "Привет";
}
}
}
Вспомогательный класс SomeObjectConverter, будучи прикрепленным в качестве атрибута к классу SomeObject, преобразует содержимое свойств Prop1 и Prop2 в строку и обратно. Это действие выполняется классом с помощью его
В панели Solution Explorer вызовите контекстное меню для узла проекта MyComponents и выполните команду Build, чтобы откомпилировать новую редакцию компонента
Перейдите на форму Form1, опять выделите экземпляр нашего компонента и исследуйте его свойство SomeObjЗамечание: Если после произведенных дополнений к коду компонента поведение свойства SomeObj в панели Properties не изменилось, то перезагрузите саму оболочку Visual Studio 2005.
Теперь сложное свойство SomeObj вложенного объекта экземпляра компонента помимо прежней функциональности разворачиваться и редактироваться в панели Properties стало отображаться и редактироваться в составной строке самого свойства.

Обратите внимание, что значения введенных нами свойств компонента отображаются в панели Properties жирным шрифтом. При работе с библиотечными компонентами мы привыкли, что жирным шрифтом отображаются только измененные значения свойств, отличные от настроек по умолчанию. Это именно те значения, которые визуальный конструктор форм оболочки автоматически присваивает свойствам в методе InitializeComponent() секции #region.
Чтобы убедиться в этом, откройте в панели Solution Explorer файл Form1.Designer.cs тестового проекта ComponentTest:#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
MyCompany.MyComponents.SomeObject someObject1
= new MyCompany.MyComponents.SomeObject();
this.firstComponent1 = new MyCompany.MyComponents
.FirstComponent(this.components);
this.SuspendLayout();
//
// firstComponent1
//
this.firstComponent1.BackColor = System.Drawing.Color.Red;
this.firstComponent1.BoolProp = false;
this.firstComponent1.CharProp = '\0';
this.firstComponent1.EnumProp =
MyCompany.MyComponents.EnumType.Zero;
this.firstComponent1.IntegerProp = 0;
this.firstComponent1.Location = new System.Drawing.Point(233, 139);
this.firstComponent1.Name = "firstComponent1";
this.firstComponent1.Size = new System.Drawing.Size(75, 23);
someObject1.Prop1 = 2008;
someObject1.Prop2 = "Привет";
this.firstComponent1.SomeObj = someObject1;
this.firstComponent1.StringProp = "По умолчанию";
this.firstComponent1.TabIndex = 5;
this.firstComponent1.Text = "firstComponent1";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(415, 190);
this.Controls.Add(this.firstComponent1);
this.MaximizeBox = false;
this.Name = "Form1";
this.Text = "Испытание компонентов";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
Для того, чтобы заставить оболочку в отношении некоторых значений свойств не воспринимать их как измененные, нужно присоединить к этим свойствам атрибут [DefaultValue] с теми значениями, которые она должна считать заданными по умолчанию. В результате при компиляции в код компонента будет встроена дополнительная информация, какие значения свойств считать базовыми и пока они не изменены пользователем, специально не присваивать при создании экземпляра компонента на форме.
В файле FirstComponent.cs присоедините к некоторым свойствам атрибут [DefaultValue] информирования среды, какие значения считать базовымиusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
namespace MyCompany.MyComponents
{
public partial class FirstComponent : Control
{
public FirstComponent()
{
InitializeComponent();
Init();
}
public FirstComponent(IContainer container)
{
container.Add(this);
InitializeComponent();
Init();
}
}
}
namespace MyCompany.MyComponents
{
// Определяем перечисление
public enum EnumType { Zero, One, Two, Three };
partial class FirstComponent
{
// Локальные поля
int integerProp;
string stringProp = "По умолчанию";
char charProp;
bool boolProp = false;
EnumType enumProp = EnumType.Zero;
SomeObject someObject;
// Общедоступные свойства для локальных полей
public int IntegerProp
{
get { return integerProp; }
set { integerProp = value; }
}
[DefaultValue("По умолчанию")] //Атрибут значения по умолчанию
public string StringProp
{
get { return stringProp; }
set { stringProp = value; }
}
public char CharProp
{
get { return charProp; }
set { charProp = value; }
}
[DefaultValue(false)] // Атрибут значения по умолчанию
public bool BoolProp
{
get { return boolProp; }
set { boolProp = value; }
}
[DefaultValue(EnumType.Zero)] // Атрибут значения по умолчанию
public EnumType EnumProp
{
get { return enumProp; }
set { enumProp = value; }
}
// Сложное свойство - объект
public SomeObject SomeObj
{
get { return someObject; }
set { someObject = value; }
}
void Init()
{
someObject = new SomeObject();
this.BackColor = System.Drawing.Color.Red;
}
}
}
namespace MyCompany.MyComponents
{
// Присоединяем атрибут с контролирующим классом для возможности
// редактирования свойств объекта в панели Properties оболочки
[TypeConverter(typeof(SomeObjectConverter))]
//[TypeConverter(typeof(ExpandableObjectConverter))]
public class SomeObject : Object
{
int prop1;
string prop2;
// Свойства
[DefaultValue(2008)] // Атрибут значения по умолчанию
public int Prop1
{
get { return prop1; }
set { prop1 = value; }
}
[DefaultValue("Привет")] // Атрибут значения по умолчанию
public string Prop2
{
get { return prop2; }
set { prop2 = value; }
}
// Инициализация полей через конструктор
public SomeObject()
{
prop1 = 2008;
prop2 = "Привет";
}
}
}
Откомпилируйте проект MyComponents, удалите с формы Form1 компонент firstComponent1 и вновь поместите его на форму из панели Toolbox, чтобы оболочка перегенерировала код в секции #region Если теперь вновь посмотреть содержимое файла Form1.Designer.cs, то мы не увидим явного присвоения начальных значений свойствам экземпляра компонента, помеченным атрибутом [DefaultValue]. Явное присвоение появится только тогда, когда пользователь компонента задаст свойствам новые значения, отличные от установленных атрибутом.
В этом примере мы создадим невизуальный компонент Planets, инкапсулирующий в себе названия планет Солнечной системы, и добавим его в нашу библиотеку компонентов сборки MyComponents. Названия планет будут храниться во внутреннем массиве компонента, а доступ к ним будет осуществляться через
В панели Solution Explorer выделите проект MyComponents и выполните команду меню Project/Add Component, чтобы добавить файл Planets.cs нового компонента
Дополните содержимое файла, автоматически сгенерированное мастером, следующим кодомusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
namespace MyCompany.MyComponents
{
public partial class Planets : Component
{
public Planets()
{
InitializeComponent();
}
public Planets(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
namespace MyCompany.MyComponents
{
partial class Planets
{
// Планеты
private string[] PlanetNames ={ "Меркурий",
"Венера",
"Земля",
"Марс",
"Юпитер",
"Сатурн",
"Уран",
"Нептун",
"Плутон"
};
// Вернуть имя
private string GetPlanetName(int index)
{
// Нижняя и верхняя границы массива
int lowP = 0, highP = PlanetNames.Length - 1;
// Контролирует диапазон индекса планеты и возвращает
// ее название или генерирует исключение
if (index < lowP || index > highP)
{
MessageBox.Show(String.Format("Индекс
должен находиться в диапазоне {0}-{1}",
lowP, highP));
index = 0;
}
return PlanetNames[index];
}
// Вернуть индекс
private int GetPlanetPosition(string planetName)
{
// Сравниваем переданное имя планеты с массивом
// PlanetNames. При несовпадении возвращаем -1
int result=-1;
for(int i=0; i<PlanetNames.Length;i++)
if (String.Compare(planetName, PlanetNames[i], true) == 0)
{
result = i;
break;
}
return result;
}
// Свойство - индексатор: возвращает имя планеты по индексу
public string this[int index]
{
get {return GetPlanetName(index); }
}
// Свойство - индексатор: возвращает индекс планеты по имени
public int this[string planetName]
{
get { return GetPlanetPosition(planetName); }
}
// Свойство максимального размера массива
public int MaxIndex
{
get { return PlanetNames.Length - 1; }
}
}
}
В коде мы объявили массив с планетами и сразу инициализировали его. Создали два внутренних контролирующих метода, возвращающих название планеты по ее индексу и наоборот. Добавили два общедоступных свойства -
Теперь осталось перекомпилировать сборку MyComponents.dll, в которой в одном пространстве имен MyCompany.MyComponents будут находиться уже два наших компонента, и испытать новый невизуальный компонент.
В панели Solution Explorer вызовите контекстное меню для узла проекта MyComponets и выполните команду Rebuild, чтобы перекомпилировать проект с компонентами
Убедитесь, что в панели Toolbox появился новый компонент Planets, который теперь можно перетаскивать на форму также, как и обычный библиотечный компонент
В проекте ComponentTest настройте пользовательский интерфейс, как показано на рисунке и в таблице (в скобках приведены имена экземпляров компонентов)
| Элемент | Свойство | Значение |
|---|---|---|
Form1 |
MaximizeBox |
false |
Size |
324; 224 | |
Text |
Испытание компонентов | |
listPlanets |
FormattingEnabled |
true |
ItemHeight |
16 | |
Location |
12; 12 | |
Size |
109; 164 | |
indexPlanet |
Location |
160; 153 |
Size |
26; 22 | |
textBox1 |
BorderStyle |
None |
Font |
Microsoft |
|
Location |
138; 21 | |
Multiline |
true |
|
ReadOnly |
true |
|
Size |
166; 107 | |
Text |
Введите индекс планеты и щелкните на красном | |
TextAlign |
Center |
|
firstComponent1 |
Location |
201; 153 |
Size |
75; 23 |
В панели Properties перейдите на вкладку Events и создайте обработчики для элементов согласно таблице| Элемент | Событие | Имя обработчика |
|---|---|---|
listPlanets |
SelectedIndexChanged |
listPlanets_SelectedValueChanged |
indexPlanet |
|
indexPlanet_KeyPress |
firstComponent1 |
Click |
firstComponent1_Click |
Заполните файл Form1.cs следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace ComponentTest
{
public partial class Form1 : Form
{
// Конструктор формы
public Form1()
{
InitializeComponent();
// Заполнение списка планетами
for (int i = 0; i <= planets1.MaxIndex; i++)
{
listPlanets.Items.Add(String.Format(
"{0}) {1}", i, planets1[i]));
}
listPlanets.SelectedIndex = 0;
}
// Обработчики событий
private void firstComponent1_Click(object sender, EventArgs e)
{
// Контролируем пустой ввод
if (indexPlanet.Text == String.Empty) return;
int index = Convert.ToInt32(indexPlanet.Text);
// Контролируем максимальный индекс ввода
index = Math.Min(index, planets1.MaxIndex);
if (listPlanets.SelectedIndex != index)
listPlanets.SelectedIndex = index;
else
MessageBox.Show(String.Format("Вы выбрали
планету {0}", planets1[index]));
}
private void indexPlanet_KeyPress(object sender,
KeyPressEventArgs e)
{
// Фильтруем цифры, Backspace, Enter (Delete и стрелки по умолчанию)
if ((e.KeyChar < Convert.ToChar(Keys.D0) ||
e.KeyChar > Convert.ToChar(Keys.D9))
e.KeyChar != Convert.ToChar(Keys.Back)
e.KeyChar != Convert.ToChar(Keys.Enter))
e.Handled = true;
// Реакция на клавишу Enter
if (e.KeyChar == Convert.ToChar(Keys.Enter))
firstComponent1_Click(null, EventArgs.Empty);
}
bool loadFlag = true; // Локальное поле-флаг
private void listPlanets_SelectedValueChanged
(object sender, EventArgs e)
{
int index = listPlanets.SelectedIndex;
indexPlanet.Text = index.ToString();
if (loadFlag)
{
// При первом запуске не показывать
loadFlag = false;
return;
}
else
MessageBox.Show(String.Format("Вы выбрали
планету {0}", planets1[index]));
}
}
}
Откомпилируйте приложение текущего уровня готовности и испытайте его работуСобытие ( event ) можно определить как любое происшествие, вызванное вмешательством пользователя, операционной системы или логикой программы. Любое событие можно обнаружить и нужным образом на него отреагировать с помощью обработчиков события. Событие в пользовательском классе определяется с помощью делегата, задающего сигнатуру потенциальных обработчиков и содержащего список указателей на все подписавшиеся на это событие обработчики. Если событие имеет аргументы, то через них можно передавать любую информацию в обработчики этого события.
Событие может прослушивать любой блок кода программы, в котором используется экземпляр класса, генерирующего событие, если только этот код зарегистрирует у себя это событие и создаст обработчик. События являются удобным механизмом общения объектов между собой и создания нужных реакций на те или иные происшествия. Событие объявляется в пользовательском классе как специальное публичное свойство для множественной адресации потенциальных обработчиков.
В классе также создается одноименный событию защищенный виртуальный метод, обычно с префиксом On, для генерации события при наступлении определенного происшествия. Этот метод называется методом диспетчеризации события ( event- ). Метод диспетчеризации, перед генерацией события, должен проверять, существует ли хотя бы один зарегистрированный обработчик этого события, чтобы не адресоваться к нулевому обработчику. Он также устанавливает значения
Рассмотрим механизм работы событий на примере создания невизуального компонента AlarmClock (будильник), передающего оповещение по прошествии указанного времени. Хотя встроить подобный код внутрь самого приложения вовсе не сложно, создание такого компонента позволит отделить его от остального исходного кода и использовать многократно в других приложениях (а удачные компоненты можно и продавать). В качестве объекта, инициирующего генерацию нашего события, будем использовать экземпляр
Добавьте к проекту MyComponents новый файл с именем AlarmClock.cs типа Component
Переведите редактирование компонента AlarmClock в режим Design и двойным щелчком в панели Toolbox добавьте к компоненту экземпляр
Выделите объект timer1 и через панель Properties установите свойство Interval=1000
Переведите панель Properties в режим Events, установите для события Tick срабатывания Оболочка добавит в файл AlarmClock.cs заготовку обработчика TimerHandler события срабатывания
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
//-------------------------------------------------------------
// Часть 0 расположена в отдельном файле AlarmClock.Designer.cs
//-------------------------------------------------------------
// Часть 1
namespace MyCompany.MyComponents
{
public partial class AlarmClock : Component
{
public AlarmClock()
{
InitializeComponent();
}
public AlarmClock(IContainer container)
{
container.Add(this);
InitializeComponent();
}
// Обработчик события срабатывания системного таймера, созданного,
// настроенного и зарегистрированного через визуальный конструктор.
// Обработчик выполняется каждую секунду и проверяет, пришла ли пора
// генерировать наше событие, на которое подпишется клиент компонента
private void TimerHandler(object sender, EventArgs e)
{
}
}
}
Для удобства класс компонента разбьем на отдельные части по функциональной принадлежности, что никак не повлияет на логику выполнения программы. Для еще большего удобства эти части класса можно размещать в отдельных файлах проекта компонентов, например, AlarmClockPart1.cs, AlarmClockPart2.cs, и т.д. (на любителя...).
Выделите заготовку обработчика в отдельную часть, которую мы заполним позднее// Часть 4
namespace MyCompany.MyComponents
{
// Часть класса с определением обработчика события Tick
// системного таймера System.Windows.Forms.Timer timer1
partial class AlarmClock
{
// Обработчик события срабатывания системного таймера, созданного,
// настроенного и зарегистрированного через визуальный конструктор.
// Обработчик выполняется каждую секунду и проверяет, пришла ли пора
// генерировать наше событие, на которое подпишется клиент компонента
private void TimerHandler(object sender, EventArgs e)
{
}
}
}
Добавьте в файл AlarmClock.cs часть класса, содержащую определения свойств компонента для его настройки как в режиме проектирования, так и выполнения// Часть 2
namespace MyCompany.MyComponents
{
// Часть класса с определением полей и свойств
partial class AlarmClock
{
// Объявляем внутренние поля
bool alarmFired = false; // Состояние будильника (запущен/незапущен)
DateTime alarmTime = DateTime.Now; // Хранит время запуска будильника
// Вложенный класс аргументов, доступных в обработчике
// Расширяет библиотечный класс аргументов событий
public class AlarmEventArgs : EventArgs
{
// Поле
DateTime time;
// Свойство
public DateTime Time
{
get { return time; }
set { time = value; }
}
}
// Свойство возвращает текущее время компьютера
public DateTime CurrentTime
{
get { return DateTime.Now; }
}
// Свойство для чтения и установки времени запуска будильника
public DateTime AlarmTime
{
get { return alarmTime; }
set
{
if (value != alarmTime)
{
alarmTime = value;
alarmFired = false;
}
}
}
// Свойство для проверки состояния хода системного таймера,
// его остановки и запуска
public bool Enabled
{
get { return timer1.Enabled; }
set
{
timer1.Enabled = value;
if (value)
alarmFired = false;
}
}
}
}
В этом разделе мы добавим к классу
Добавьте в файл AlarmClock.cs часть класса, содержащую определение собственного события стандартным способом без использования базового поля// Часть 3
namespace MyCompany.MyComponents
{
// Часть класса с определением собственного события
// стандартным способом без использования базового поля
partial class AlarmClock
{
// Объявляем делегат для события. В аргументах делегата
// нужно указать полное имя вложенного класса
public delegate void AlarmHandler(object sender,
AlarmClock.AlarmEventArgs e);
// Объявляем событие с доступностью не ниже делегата
public event AlarmHandler Alarm;
// Метод диспетчеризации события. Виртуальный и защищенный
// для возможности переопределения в будущих потомках
protected virtual void OnAlarm(AlarmEventArgs args)
{
// Проверяем наличие зарегистрированных
// обработчиков и генерируем событие
if (Alarm != null)
Alarm(this, args);
}
}
}
Модифицируйте часть класса с обработчиком события Tick alarmTime срабатывания будильника// Часть 4
namespace MyCompany.MyComponents
{
// Часть класса с определением обработчика события Tick
// системного таймера System.Windows.Forms.Timer timer1
partial class AlarmClock
{
// Обработчик события срабатывания системного таймера, созданного,
// настроенного и зарегистрированного через визуальный конструктор.
// Обработчик выполняется каждую секунду и проверяет, пришла ли пора
// генерировать наше событие, на которое подпишется клиент компонента
private void TimerHandler(object sender, EventArgs e)
{
DateTime now; // Текущее системное время
AlarmEventArgs args;
// В режиме разработки не выполнять
if (!this.DesignMode)
{
now = DateTime.Now;
// Если будильник не запущен и пришла пора запускать
if (!alarmFired now >= alarmTime)
{
// Создаем объект для передачи аргументов в событии
args = new AlarmEventArgs();
// Заполняем объект текущим временем
args.Time = now;
// Вызываем метод диспетчеризации события Alarm
this.OnAlarm(args);
// Поднимаем флаг "Будильник запущен"
alarmFired = true;
}
}
}
}
}
Откомпилируйте библиотеку наших компонентов, выполнив команду Build/Build MyComponents
Перейдите в режим редактирования Form1.cs [Design] и убедитесь, что в панели Toolbox после компиляции появился новый компонент AlarmClock
Поместите новый компонент AlarmClock из панели Toolbox на форму Form1 приложения ComponentTest, выделите его экземпляр alarmClock1 и через панель Properties в режиме Events создайте обработчик для события Alarm
Обработчик, добавленный в класс Form1 приложения ComponentTest, заполните следующим кодомprivate void alarmClock1_Alarm(object sender,
AlarmClock.AlarmEventArgs e)
{
// Если оператор выключения будильника поставить после
// вызова модального диалогового окна, то генерация
// событий прекратиться только после закрытия
// очередного модального окна, поскольку только тогда
// управление дойдет до этого оператора
alarmClock1.Enabled = false;// Останавливаем системный таймер
int hour = alarmClock1.CurrentTime.Hour;
int minute = alarmClock1.CurrentTime.Minute;
int second = alarmClock1.CurrentTime.Second;
MessageBox.Show(String.Format("Сработал будильник!!!\n"
+ "Текущее время: {0} ч. {1} м. {2} с.",
hour, minute, second),
"Доцент Снетков В.М.");
}
Добавьте в конец конструктора формы Form1 код установки времени срабатывания будильника и запуска // Конструктор формы
public Form1()
{
InitializeComponent();
// Заполнение списка планетами
for (int i = 0; i <= planets1.MaxIndex; i++)
{
listPlanets.Items.Add(String.Format(
"{0}) {1}", i, planets1[i]));
}
listPlanets.SelectedIndex = 0;
// Устанавливаем время срабатывания будильника
// через 5 сек. после запуска системного таймера
alarmClock1.AlarmTime = DateTime.Now.AddSeconds(5d);
alarmClock1.Enabled = true;// Запускаем системный таймер
}
Запустите приложение и убедитесь, что обработчик нашего события Alarm срабатывает через 5 секунд после запуска 
Рассмотрим другой способ работы с событиями, использующий базовое поле типа делегата. Для этого модифицируем наш компонент таким образом, чтобы будильник срабатывал два раза: по первому и второму событию. Но второе событие определим по иному.
Перейдите в режим редактирования компонента AlarmClock.cs [Design] и добавьте из панели Toolbox двойным щелчком кнопки мыши еще один
Выделите экземпляр таймера timer2 и в панели Properties установите свойство Interval=1000
Создайте для объекта timer2 обработчик события Tick с именем TimerHandlerExt, который переместите в отдельную часть класса AlarmClock в файле AlarmClock.cs. Заполните эту часть следующим кодом// Часть 5
namespace MyCompany.MyComponents
{
// Часть класса с определением обработчика события Tick
// системного таймера System.Windows.Forms.Timer timer2
partial class AlarmClock
{
// Объявляем внутренние поля
bool alarmFiredExt = false; // Состояние будильника (запущен/незапущен)
DateTime alarmTimeExt = DateTime.Now; // Хранит время запуска будильника
// Свойство для чтения и установки времени запуска будильника
public DateTime AlarmTimeExt
{
get { return alarmTimeExt; }
set
{
if (value != alarmTimeExt)
{
alarmTimeExt = value;
alarmFiredExt = false;
}
}
}
// Свойство для проверки состояния хода второго
// системного таймера, его остановки и запуска
public bool EnabledExt
{
get { return timer2.Enabled; }
set
{
timer2.Enabled = value;
if (value)
alarmFiredExt = false;
}
}
// Обработчик события срабатывания второго системного таймера
private void TimerHandlerExt(object sender, EventArgs e)
{
DateTime now; // Текущее системное время
AlarmEventArgs args;
// В режиме разработки не выполнять
if (!this.DesignMode)
{
now = DateTime.Now;
// Если будильник не запущен и пришла пора запускать
if (!alarmFiredExt now >= alarmTimeExt)
{
// Создаем объект для передачи аргументов в событии
args = new AlarmEventArgs();
// Заполняем объект текущим временем
args.Time = now;
// Вызываем метод диспетчеризации события AlarmExt
this.OnAlarmExt(args);
// Поднимаем флаг "Будильник запущен"
alarmFiredExt = true;
}
}
}
}
}
Создайте часть класса компонента, объявляющего // Часть 6 Extension
namespace MyCompany.MyComponents
{
// Часть класса с определением собственного события
// стандартным способом без использования базового поля
partial class AlarmClock
{
// Закрытое поле-ссылка на экземпляр делегата
// Для объявления поля решили использовать уже существующий
// делегат, но можно объявить и другой делегат с той же сигнатурой
private AlarmHandler m_AlarmExt;
// Объявляем пользовательское событие
// с возможностью контроля доступа к нему
public event AlarmHandler AlarmExt
{
// Эта функция будет вызвана при попытке
// добавления обработчика в список вызова события
add
{
// Здесь что-то можно проконтролировать, например, что
// произошла попытка добавить обработчик в список вызова события
Console.WriteLine("Добавлен обработчик в
список события AlarmExt");
// Расширяем список объекта-делегата, ссылающегося на обработчики события
m_AlarmExt += value;
}
// Эта функция будет вызвана при попытке
// изъятия обработчика из списка вызова события
remove
{
// Здесь что-то можно проконтролировать, например, что
// произошла попытка изъять обработчик из списка вызова события
Console.WriteLine("Изъят обработчик из
списка события AlarmExt");
// Удалим обработчик из списка объекта-делегата
m_AlarmExt -= value;
}
}
// Метод диспетчеризации события. Виртуальный и защищенный
// для возможности переопределения в будущих потомках
protected virtual void OnAlarmExt(AlarmEventArgs args)
{
// Проверяем наличие зарегистрированных обработчиков
// и генерируем событие через поле-делегат
if (m_AlarmExt != null)
m_AlarmExt(this, args);
}
}
}
Откомпилируйте библиотеку наших компонентов, выполнив команду Build/Build MyComponents
Перейдите в файл Form1.cs [Design] и добавьте в конструктор класса тестового приложения код настройки второго alarmClock1 // Конструктор формы
public Form1()
{
InitializeComponent();
// Заполнение списка планетами
for (int i = 0; i <= planets1.MaxIndex; i++)
{
listPlanets.Items.Add(String.Format(
"{0}) {1}", i, planets1[i]));
}
listPlanets.SelectedIndex = 0;
// Устанавливаем время срабатывания будильника
// через 5 сек. после запуска системного таймера
alarmClock1.AlarmTime = DateTime.Now.AddSeconds(5d);
alarmClock1.Enabled = true;// Запускаем системный таймер
// Настраиваем второй системный таймер компонента
alarmClock1.AlarmTimeExt = DateTime.Now.AddSeconds(10d);
alarmClock1.EnabledExt = true;// Запускаем системный таймер
}
На форме Form1 приложения ComponentTest выделите экземпляр alarmClock1 и через панель Properties в режиме Events создайте обработчик для события AlarmExt
Обработчик заполните следующим кодомprivate void alarmClock1_AlarmExt(object sender,
AlarmClock.AlarmEventArgs e)
{
alarmClock1.EnabledExt = false;// Останавливаем второй системный таймер
int hour = alarmClock1.CurrentTime.Hour;
int minute = alarmClock1.CurrentTime.Minute;
int second = alarmClock1.CurrentTime.Second;
MessageBox.Show(String.Format("Сработал будильник!!!\n"
+ "Текущее время: {0} ч. {1} м. {2} с.",
hour, minute, second), "Доцент Снетков В.М.");
// Для генерации сообщения в консольный вывод
alarmClock1.AlarmExt -= alarmClock1_AlarmExt;
}
В панели Solution Explorer вызовите контекстное меню для узла приложения ComponentTest и выполните команду Properties
В появившемся окне настроек оболочки выберите вкладку Application и в раскрывающемся списке Output type включите окно консольного вывода Console Application 
Запустите приложение и убедитесь, что оба способа определения Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.