Все оконные приложения .NET используют класс Form, который размещается в пространстве имен System.Windows.Forms одноименной библиотеки System.Windows.Forms.dll. Приложения, также, часто используют ряд графических возможностей, размещенных в классах библиотечной сборки System.Drawing.dll. Нужна также сборка System.dll.
Конечно, оболочка имеет удобные мастера для создания приложений различных типов, но для дальнейшего изучения возможностей языка C# при создании оконных приложений начнем построение кода с нуля.
Выполните команду меню File/New/Project и выберите из диалогового окна мастера шаблон Empty Project (пустой проект), а также определите имя проекта MyApp и его размещение в файловой системе компьютера. Проследите, чтобы флаг Create directory for solution был сброшен, иначе будет 
Обратите внимание, что панель Solution Explorer не отображает ни одного файла

Обратите, также, внимание, что панель Object Browser уже содержит ссылку на базовую сборку mscorlib.dll ( Microsoft Common Object Runtime Library или Multilanguage Standard Common Object Runtime Library ). Она автоматически подключается к проекту, поскольку содержит базовые типы, необходимые для всех приложений. Наш проект тоже присутствует в этой панели, как будующая сборка, но пока сырая.
Командой Project/Add Reference вызовите диалоговое окно оболочки и добавьте к проекту ссылки на библиотечные сборки System.dll, System.Windows.Forms.dll и System.Drawing.dll
Добавьте к проекту пустой файл с именем Program.cs командой Proiect/Add New Item
Заполните файл следующим кодомclass EntryPoint
{
public static void Main()
{
// Создание графического окна
System.Windows.Forms.Form frm =
new System.Windows.Forms.Form();
// Задержка консольного окна
System.Console.ReadLine();
}
}
Мы видим, что помещать код в свое пространство имен необязательно и указывать библиотечные пространства имен тоже необязательно. В первом случае для больших проектов это может привести к повторению имен типов, во втором - приходится писать полные имена при использовании библиотечных классов.
Запустите приложение - появится только пустое консольное окноКонсольное окно создается и отображается автоматически, а вот графическое окно мы только создали, но не отобразили.
Исправьте код файла Program.cs такclass EntryPoint
{
public static void Main()
{
// Создание и отображение графического окна
System.Windows.Forms.Form frm =
new System.Windows.Forms.Form();
frm.Text = "Мертвое окно";
frm.Show();
// Задержка консольного окна
System.Console.ReadLine();
}
}
При запуске приложения мы увидим созданное окно, но оно не будет реагировать на действия пользователя. Более того, чтобы его увидеть, необходимо специально задерживать консольное окно.

Чтобы это исправить, нужно включить окно в цикл сообщений Windows, чтобы ему передавались от системы типовые сообщения, которые передаются любому графическому окну, имеющему фокус ввода.
Исправьте код файла Program.cs такclass EntryPoint
{
public static void Main()
{
// Создание и отображение графического окна
System.Windows.Forms.Form frm =
new System.Windows.Forms.Form();
frm.Text = "Живое окно";
System.Windows.Forms.Application.Run(frm);
}
}
Теперь мы получили полноценное окно Windows, которое реагирует на действия пользователя, передаваемые ему системой. Специально отображать это окно не нужно. Теперь оно держит приложение до тех пор, пока не будет закрыто либо графическое, либо консольное окно.
Иметь одновременно с графическим окном консольное очень выгодно на этапе отладки. В консольное окно можно выводить значения переменных также, как без графического окна. Но конечному пользователю нужно поставлять приложение без консольного окна.
Отключите консольное окно, вызвав окно свойств проекта командой Project/MyApp Properties и установив опцию Windows Application в списке Output type вкладки Application
Теперь при запуске приложения будет появляться только графическое окно

Это окно имеет по умолчанию стандартный размер 300x300 пикселов, но может настраиваться кодом программы перед его помещением в цикл сообщений системы.
Можно в цикл сообщений поместить сразу ссылку на экземпляр класса Form, возвращаемую оператором new при его создании. Но в этом случае код программы уже не будет иметь доступа к свойствам этого объекта. В приведенном ниже примере создается и настраивается объект на одно окно, а передается в цикл сообщений совершенно другое окно, созданное на лету.
using System;
using System.Windows.Forms;
using System.Drawing;
class EntryPoint
{
public static void Main()
{
// Создание и отображение графического окна
Form frm = new Form();
frm.Text = "Первое окно";
Application.Run(new Form());// Новое окно
}
}
Теперь на новое окно из кода никак влиять нельзя.
Правильным подходом является создание пользовательского класса, наследника библиотечного класса Form. В таком классе можно установить все необходимые настройки и обработчики событий, а уж затем в цикл сообщений передавать создаваемый на лету объект этого класса. Настройки можно разместить в конструкторе по умолчанию, который автоматически срабатывает в момент создания объекта. Такой подход избавит функцию Main() от несвойственного ей кода и сделает программу более структурированной (все на своем месте).
После установки
Любое окно Windows Forms может включать в себя другие элементы управления: кнопки, флажки, радиокнопки, раскрывающиеся и простые списки и т.д. В этом случае элементы управления называются дочерними для графического окна, а само графическое окно будет являться родителем этих элементов.
Добавьте к приложению новый класс с именем MyForm, выполнив команду Project/Add Class
Будем использовать пространство имен MyApp проекта, все равно оболочка не отвяжется и при каждом новом создании класса будет генерировать пространство имен. По умолчанию генерируется пространство имен, взятое из имени проекта, если только его не изменить в настройках оболочки

Разделите код из файла Program.cs на два файла так
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует классу графического окна
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
this.Text = "Живое окно";
this.BackColor = Color.AliceBlue;
// Подписываемся на событие с помощью делегата
this.Paint += new PaintEventHandler(MyPainter);
// this.Paint += MyPainter; // Альтернативный синтаксис
// подключения обработчика
// Шрифт
this.Font = new Font("Arial", 16, FontStyle.Bold);
// Ширина окна
this.Width = this.Height * 2;
// В центре экрана при запуске
this.StartPosition = FormStartPosition.CenterScreen;
}
void MyPainter(object sender, PaintEventArgs e)
{
Form frm = (Form)sender;// Привели типы
// Form frm = sender as Form; // Альтернативный синтаксис
// приведения типов
Graphics gr = e.Graphics;// Контекст экрана
// Создаем и настраиваем точку привязки текстового блока
StringFormat strfrm = new StringFormat();
strfrm.Alignment = StringAlignment.Center;// Центр по горизонтали
strfrm.LineAlignment = StringAlignment.Center;// Центр по вертикали
gr.DrawString("Привет студентам кафедры ПМ и АСУ",
frm.Font, // Применяется шрифт окна
new SolidBrush(Color.Red), // Цвет шрифта
frm.ClientSize.Width / 2, // Центр клиентской области по горизонтали
frm.ClientSize.Height / 2, // Центр клиентской области по вертикали
strfrm); // Передаем точку привязки текстового блока
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyForm());// Новое окно
}
}
}
Результат выполнения будет таким

В конструкторе класса MyForm есть строка подключения обработчика к событию Paint, которое генерируется всякий раз при частичной или полной утрате окна из-за его свертывания, увода за пределы экрана или заслонения другим окном. При возвращении окна на экран автоматически восстанавливается только его заголовок, а о восстановлении клиентской области (содержимого окна) разработчик приложения должен позаботиться сам. Создатели многооконной операционной системы приняли такой порядок специально, чтобы не перегружать ресурсы хранением множества экранов. Они только извещают приложение событием Paint о том, что окно было разрушено и его пора восстановить.
События (сообщения) являются наилучшим способом общения системы и приложения, а также объектов приложения между собой. Когда система или объект генерируют событие (сообщение), оно становится в очередь и принимается одним или несколькими объектами, которые зарегистрировали это событие у себя. Процесс регистрации, т.е. подключение к событию обработчиков называется подпиской на событие. На одно и то же событие могут подписаться несколько объектов, равно как один и тот же объект может подписаться на несколько событий.
Подписавшийся на событие объект ждет его появления и реагирует выполнением кода, помещенного в обработчик. Обработчик размещается в классе как его специальный метод, сигнатура которого определяется делегатом события. Делегат события является прототипом всех обработчиков, подписавшихся на данное событие.
В качестве примера подпишем наше графическое окно на несколько обработчиков:
Измените код приложения следующим образомusing System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует классу графического окна
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Подписка событий";
this.BackColor = SystemColors.Window;
this.StartPosition = FormStartPosition.CenterScreen;
// Подписка на события окна
this.Paint += new PaintEventHandler(MyPaint);
// На событие Click не подписываемся,
// но обработчик создадим
// this.Click += new EventHandler(MyClick);
// Альтернативный синтаксис подписки
this.DoubleClick += MyDoubleClick;
this.Resize += MyResize;
}
// Объявили и инициализировали внутреннее поле
Random rnd = new Random();
void MyPaint(object sender, PaintEventArgs e)
{
MessageBox.Show("Окно разрушено!", "Класс MyForm");
Graphics gr = e.Graphics;// Ссылка на контекст экрана
// Рисуем разрушенную часть
// клиентской области случайным цветом
gr.Clear(Color.FromArgb(rnd.Next(255), // Red
rnd.Next(255), // Green
rnd.Next(255))); // Blue
}
// Обработчик события создан,
// но класс на него не подписался
void MyClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!", "Класс MyForm");
}
void MyDoubleClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен двойной щелчок!", "Класс MyForm");
}
void MyResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!", "Класс MyForm");
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyForm());// Новое окно
}
}
}
Запустите приложение и получите ошибку компиляции с сообщением, что приложение имеет больше чем одну точку входа: " MyApp.exe' has more than one entry point "
В панели Solution Explorer вызовите для узла приложения (корневой узел) контекстное меню и выберите опцию Properties
В окне свойств выберите вкладку Application и в поле Startup object установите явно точку входа, находящуюся в классе MyForm
Запустите приложение и поманипулируйте с графическим окном как конечный пользовательМожно увидеть, что объект формы перехватывает сообщения Windows, на которые мы его подписали, и реагирует на них обработчиками. Для одного события мы обработчик создали, но на само событие не подписались, поэтому окно как объект его не перехватывает.
Наследование является центральным механизмом объектно-ориентированного программирования. Оно позволяет присоединить код уже разработанного класса к новому и уточнить его. Производный ( Derived ) класс наследует все данные и методы базового ( Base ) класса. В коде производного класса остаются доступными те данные и методы, которые в базовом классе объявлены общедоступными ( public ) или защищенными ( protected ). А если эти методы объявлены в базовом классе как виртуальные (с ключевым словом virtual ), то их можно перегружать (используя ключевое слово override ) в производном классе, сохраняя при этом прежнюю доступность, имя и сигнатуру.
Определим в коде нашего примера производный класс, который будет наследовать базовый класс и переопределять обработчики подписанных событий. Для этого объявим в базовом классе обработчики общедоступными и виртуальными. Измененный код будет таким
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Подписка событий";
this.BackColor = SystemColors.Window;
this.StartPosition = FormStartPosition.CenterScreen;
// Подписка на события окна
this.Paint += new PaintEventHandler(MyPaint);
this.Click += new EventHandler(MyClick);
// Альтернативный синтаксис подписки
this.Resize += MyResize;
}
// Объявили и инициализировали внутреннее поле
Random rnd = new Random();
public virtual void MyPaint(object sender, PaintEventArgs e)
{
MessageBox.Show("Окно разрушено!",
"Класс MyForm");
Graphics gr = e.Graphics;// Ссылка на контекст экрана
// Рисуем разрушенную часть
// клиентской области случайным цветом
gr.Clear(Color.FromArgb(rnd.Next(255), // Red
rnd.Next(255), // Green
rnd.Next(255))); // Blue
}
// Обработчик события создан,
// но класс на него не подписался
public virtual void MyClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyForm");
}
public virtual void MyResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyForm");
}
}
// Наследует пользовательскому (нашему) классу
class MyDerivedForm : MyForm
{
public override void MyPaint(object sender, PaintEventArgs e)
{
MessageBox.Show("Окно разрушено!",
"Класс MyDerivedForm");
// Захотели вызвать метод базового класса
base.MyPaint(sender, e);
}
public override void MyClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyDerivedForm");
}
public override void MyResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyDerivedForm");
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyDerivedForm());
}
}
}
Установите в свойствах проекта точку входа по умолчанию ( Not set ) и запустите приложениеВ производном классе мы не подписывались на события - подписка унаследована из базового класса. Мы лишь перегрузили унаследованные обработчики на новые. Старые обработчики можно вызвать из базового класса, адресуясь с помощью ключевого слова base.
Подпишемся в производном классе на те же самые события и определим для них новые обработчики. Все это выполним в конструкторе производного класса. Обработчики базового класса переименуем. Новый код будет таким
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Подписка событий";
this.BackColor = SystemColors.Window;
this.StartPosition = FormStartPosition.CenterScreen;
// Подписка на события окна
this.Click += new EventHandler(OnClick);
// Альтернативный синтаксис подписки
this.Resize += OnResize;
}
// Обработчик события создан,
// но класс на него не подписался
public virtual void OnClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyForm");
}
public virtual void OnResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyForm");
}
}
// Наследует пользовательскому (нашему) классу
class MyDerivedForm : MyForm
{
public MyDerivedForm()
{
// Новая подписка на те же события
this.Click += MyClick;
this.Resize += MyResize;
}
public void MyClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyDerivedForm");
}
public void MyResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyDerivedForm");
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyDerivedForm());
}
}
}
Теперь два класса, соединенные
Если сделать обработчики базового класса пустыми, то при возникновении соответствующего события их действия будут никакими, а проявят себя только подписанные обработчики производного класса. И наоборот, если не подписываться на события в производном классе, а перегрузить в нем пустые обработчики тех же событий, подписанные в базовом классе, то опять сработают обработчики производного класса.
Таким образом у нас есть два пути реагировать на события в производном классе: либо явно подписаться на унаследованные от базового класса события, либо перегрузить виртуальные обработчики тех же событий, подписанных в базовом классе. Какой путь выбирать, решать вам, но перегрузка кажется предпочтительней. Зачем создавать новую подписку, если есть уже старая.
При создании графического окна мы использовали свой класс, наследующий от библиотечного класса Form. Последний, в свою очередь, наследует от класса Control. Вот как представлена эта цепочка наследования в панели Object Browser оболочки

Среди множества событий, определенных в классе Control, есть и используемые нами. Все события класса Control подписаны в нем и для каждого определен защищенный виртуальный обработчик, который имеет

Если мы разрабатываем оконный класс, использующий в качестве базового класс Form, то мы получаем вместе с этим классом и все наследуемые им от класса Control события и их защищенные виртуальные обработчики. Поэтому можно либо заново подписаться на эти события и определить свои обработчики, либо перегрузить унаследованные библиотечные обработчики. Далее мы чаще будем использовать второй способ.
Переделаем наш пример, перегрузив в нашем классе наследуемые виртуальные обработчики событий. Вот как это будет выглядеть
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Перегрузка обработчиков";
this.BackColor = SystemColors.Window;
this.StartPosition = FormStartPosition.CenterScreen;
}
// Обработчик события создан,
// но класс на него не подписался
protected override void OnClick(EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyForm");
}
protected override void OnResize(EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyForm");
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyForm());
}
}
}
Код получился намного короче и яснее.
Будем называть наш класс, наследующий от Form, пользовательским окном. Разместим в окне элемент управления типа кнопки, которая порождается классом Button. Все элементы управления являются визуальными и наследуют от класса Control. В следующей программе показано, как создать объект Button, поместить его на поверхность формы и подключить обработчик к событию Click этой кнопки. Продемонстрированы разные способы настройки и управления кнопками.
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Форма с кнопками";
// Создание и настройка первой кнопки
Button btn = new Button(); // Создаем первую объект-кнопку
btn.Text = "Кнопка 1"; // Надпись на первой кнопке
btn.AutoSize = true; // Подстройка размеров под надпись
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = (this.ClientSize.Height - btn.Height) / 2;
btn.Location = new Point(left, top); // Координаты кнопки в
// клиентской области формы
btn.Click += ButtonOnClick;// Подключение обработчика к событию Click
btn.Parent = this; // Присоединяем к форме первую кнопку
// и освобождаем ссылочную переменную
// Создание и настройка второй кнопки
btn = new Button(); // Используем освободившуюся адресную переменную
btn.Text = "Кнопка 2";
btn.AutoSize = true;
top += (this.ClientSize.Height - top) / 2; // Расположим ниже
btn.Location = new Point(left, top);
btn.Click += ButtonOnClick; // Присоединяем тот же обработчик
btn.Enabled = false; // Сначала недоступна
this.Controls.Add(btn); // Другой способ присоединения к форме
btn = null; // Освобождаем ссылку
}
// Флаг состояния доступности (закрытое поле класса)
private bool enableButton = true;
void ButtonOnClick(object sender, EventArgs e)
{
if (enableButton)
MessageBox.Show("Произошел щелчок на первой кнопке!");
else
MessageBox.Show("Произошел щелчок на второй кнопке!");
// Перебираем коллекцию из двух кнопок и меняем доступность
foreach (Control ctrl in this.Controls)
ctrl.Enabled = !ctrl.Enabled;
enableButton = !enableButton;
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
}
Использованный в функции Main() метод Application.EnableVisualStyles() класса позволяет задать современный внешний вид для некоторых элементов управления. Созданная форма с кнопками будет такой

Размещенные на форме кнопки являются ее дочерними объектами и размещаются в коллекции Controls. Учитывая, что в нашем примере дочерними элементами являются только две кнопки, мы не принимали специальные меры для их распознавания. Но в более сложных случаях элементам нужно присваивать имена в их свойстве Name и распознавать уже по значению этого свойства. Для поиска конкретного объекта Control годится как простой перебор всех элементов коллекции Controls формы, так и метод System.Windows.Forms.Control.ControlCollection.Find(string, bool).
Все дочерние элементы формы располагаются в Z-порядке ( Z -последовательности), определяющим их видимость на экране, и могут перекрывать друг друга. Начальный Z -порядок определается порядком добавления элементов к форме и противоположен индексам элементов в коллекции формы: более поздние элементы отображаются верхними. Для изменения Z -порядка в процессе выполнения программы используются методы BringToFront() и SendToBack() класса Control.
В объектно-ориентированном программировании существует два способа передачи кода одного класса другому: это композиция и наследование. Композицией называется создание экземпляра одного класса внутри другого с последующим использованием объекта, а наследование передает код базового класса в производный неявно, сливая вместе два или целую цепочку классов.
Наследование применяют обычно к формам, а композицию - при размещении на форме библиотечных элементов управления. Но библиотечные элементы управления также можно использовать как базовые при наследовании, расширяя их производным классом.
Для примера создадим класс, расширяющий библиотечный класс Button, в котором определим такое поведение кнопки, что если надпись на ней не превышает заданную длину, то она остается недоступной на форме. Для изменения надписи кнопки предусмотрим текстовое поле ввода типа TextBox.
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Локальные поля формы
TextBox txtbox;
ExtensionButton btn;
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Форма с \"умной\" кнопкой";
// Создание и настройка объекта TextBox
txtbox = new TextBox();
txtbox.Text = "Кнопка";
txtbox.CharacterCasing = CharacterCasing.Upper;
txtbox.Width = 150;
int left = (this.ClientSize.Width - txtbox.Width) / 2;
int top = (this.ClientSize.Height - txtbox.Height) / 3;
txtbox.Location = new Point(left, top);
txtbox.Focus();
txtbox.Parent = this;
// Подписка объекта TextBox на событие
txtbox.TextChanged += new EventHandler(txtbox_TextChanged);
// Создание и настройка объекта умной кнопки
btn = new ExtensionButton(); // Создаем умную кнопку
btn.StrTextBox = txtbox.Text; // Начальная надпись на кнопке
btn.AutoSize = true; // Подстройка размеров под надпись
left = (this.ClientSize.Width - btn.Width) / 2;
top += (this.ClientSize.Height - top) / 2;
btn.Location = new Point(left, top);
btn.Parent = this; // Присоединяем кнопку к форме
}
void txtbox_TextChanged(object sender, EventArgs e)
{
// Меняем значение надписи на кнопке
// с одновременным контролем доступности
btn.StrTextBox = txtbox.Text;
}
}
// Класс расширения кнопки
class ExtensionButton : Button
{
// Локальное поле-константа
private const int MIN_DISABLED = 5;
// Публичное свойство
public string StrTextBox
{
set
{
this.Text = value;
bool enabled = value != null
value.Length > MIN_DISABLED;
this.Enabled = enabled;
}
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
}
Результат выполнения программы будет таким
Проектируя приложение, разработчик не знает, на экранах с каким разрешением оно будет работать. Если не принять специальных мер, то при установке малых разрешений окно будет казаться большим, а при установке больших - маленьким относительно экрана. Для автоматического масштабирования окон приложения в зависимости от разрешения экрана в классе Form имеются два свойства: AutoScaleDimensions и AutoScaleMode, наследуемые от класса ContainerControl, которые и решают эту задачу.
Свойства должны задаваться точно в указанном ниже порядке:
AutoScaleDimensions = new Size(4, 8); AutoScaleMode = AutoScaleMode.Font;
или
AutoScaleDimensions = new SizeF(96, 96); AutoScaleMode = AutoScaleMode.Dpi
или
AutoScaleDimensions = new SizeF(120, 120); AutoScaleMode = AutoScaleMode.Dpi
Перечисление AutoScaleMode имеет значения: Dpi, Font, Inherit, None. Значение Font устанавливает привязку к размерам стандартного значения шрифта Windows, значение Dpi - к разрешению экрана.
Для правильного масштабирования приведенный код нужно располагать ближе к концу конструктора, когда уже задан размер клиентской области формы, созданы все элементы управления и назначены дочерними для формы.
Диалоговые окна служат для получения приложением необходимой информации от пользователя. Чаще всего диалоговые окна вызываются командой меню, заканчивающейся многоточием, или кнопками панели управления. Различают модальные и немодальные диалоговые окна.
Кроме элементов управления для сбора информации окна содержат кнопки OK и Cancel, которые позволяют принять информацию или отказаться от нее. При нажатии любой из этих кнопок диалоговое окно закрывается, но продолжает существовать, пока представляющий его программный объект не выйдет из области видимости и не потеряет адресацию.
На случай использования формы для разработки диалогового окна в ней предусмотрены методы Show() и ShowDialog(), а также свойство DialogResult. Свойство с таким же именем DialogResult есть и у кнопок на случай их использования в диалоговых окнах. Если в обработчике события Click кнопки предусмотреть присвоение его свойству DialogResult одного из значений одноименного перечисления, то свойству DialogResult формы, а также ее возвращаемому значению, будет присвоено это значение.
Среди множества свойств, наследуемых и своих, класс Form имеет свойства AcceptButton и CancelButton, которым можно присвоить ссылки на объекты кнопок, по умолчанию предназначенных на реагирование нажатий клавиш Enter и Esc соответственно. Если упомянутые свойства для формы установлены, то нажатие клавиши Esc равносильно щелчку на соответствующей кнопке Cancel. Нажатие клавиши Enter равносильно щелчку на кнопке OK при условии, что другая кнопка в это время не имеет фокуса ввода.
И еще разок, когда диалоговое окно закроется, оно продолжает существовать и сохраняет свое состояние до тех пор, пока не будет потеряно при выходе своей ссылочной переменной за пределы области видимости. После этого мы уже не сможем адресоваться к такому окну и оно достанется сборщику мусора как брошенная память. Заголовок диалогового окна должен соответствовать запрашиваемой от пользователя информации.
Сконструируем модальное диалоговое окно, на котором разместим флажок и две кнопки. Если флажок будет включен, то изменим цвет клиентской области основного окна приложения на случайный серый, а при выключенном состоянии флажка - в случайный цветной. Следует отметить, что флажок является самодостаточным элементом и самостоятельно управляет изменением своего состояния.
Вот код примера конструирования нашего диалогового окна, иллюстрирующий все сказанное.
Добавьте к приложению пустой файл с именем ModalDialogBox.cs, выполнив команду Project/Add New Item
Заполните файл следующим кодомusing System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class ModalDialogBox : Form
{
// Локальное поле ссылки на флажок
CheckBox cbGrayShades;
// Конструктор
public ModalDialogBox()
{
// Настройка диалогового окна
// Заголовок назначения окна
this.Text = "Цвет формы";
// Неизменяемый размер
this.FormBorderStyle = FormBorderStyle.FixedDialog;
// Отключим все системные кнопки в заголовке окна
this.ControlBox = false; // Значок слева и кнопка закрытия справа
this.MinimizeBox = false;// Кнопка свертывания
this.MaximizeBox = false;// Кнопка развертывания
// Не отображать кнопку в панели задач
this.ShowInTaskbar = false; // Нет внизу экрана
// Появление формы при каждом запуске
// определяется нами свойством Location
this.StartPosition = FormStartPosition.Manual;
// Размер клиентской области окна
this.ClientSize = new Size(144, 56);
// Позиция диалогового окна
this.Location =
ActiveForm.Location // Координаты вызывающего окна
// Добавляем сдвиг вправо-вниз относительно вызывающего окна
+ SystemInformation.CaptionButtonSize // Размер надписи кнопки
+ SystemInformation.FrameBorderSize; // Толщина рамки
// Создаем и настраиваем флажок
// Создали объект
cbGrayShades = new CheckBox();
// Назначили родителя
cbGrayShades.Parent = this;
// Смысловая надпись для включенного состояния
cbGrayShades.Text = "Только случайный серый";
// Привязка левого-верхнего угла относительно родителя
int left, top; // Вспомогательные переменные
left = 16; top = 8;
cbGrayShades.Location = new Point(left, top);
// Ширина - высота в пикселах
cbGrayShades.Size = new Size(this.ClientSize.Width - left - 4, 12);
// Создаем и настраиваем две кнопки
Button btn = new Button();
btn.Parent = this;
btn.Text = "OK";
btn.Location = new Point(16, 32);// Копируем значение структуры
btn.Size = new Size(48, 14);// Копируем значение структуры
btn.DialogResult = DialogResult.OK;// Пометили признаком "ПРИНЯТЬ"
this.AcceptButton = btn; // Закрепили за клавишей Enter, когда в фокусе
btn = new Button(); // Используем ту же ссылочную переменную
btn.Parent = this;
btn.Text = "Cancel";
btn.Location = new Point(80, 32);// Правее первой кнопки
btn.Size = new Size(48, 14);// Тот же размер
btn.DialogResult = DialogResult.Cancel;// Пометили признаком "ОТМЕНИТЬ"
this.CancelButton = btn; // Закрепили за клавишей Esc
// Автоматическое масштабирование под опорный шрифт
this.AutoScaleDimensions = new Size(4, 8);
this.AutoScaleMode = AutoScaleMode.Font;
}
// Для тренировки обертываем открытое свойство
// состояния флажка своим открытым свойством
public bool GrayShades
{
get { return cbGrayShades.Checked; }
set { cbGrayShades.Checked = value; }
}
}
}
Хотя свойство ControlBox ссылается на значок системного меню слева в строке заголовка, но скрывается заодно и кнопка закрытия окна. Если ControlBox задать значение true, то при значении FormBorderStyle = FormBorderStyle.FixedDialog появится только кнопка закрытия.
Откорректируйте файл MyForm.cs реализации основного окна приложения такusing System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class MyForm : Form // Наследуем библиотечному классу
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "С модальным окном";
this.StartPosition = FormStartPosition.CenterScreen;
// Создание и настройка объекта кнопки
Button btn = new Button(); // Кнопка вызова диалогового окна
btn.Parent = this; // Назначаем родителя
btn.Text = "Изменить цвет";// Надпись на кнопке
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = this.ClientSize.Height - 2 * btn.Height;
btn.Location = new Point(left, top); // Координаты привязки
btn.AutoSize = true;// Подгонять размер под надпись
// Подписываемся на событие
btn.Click += ButtonOnClick;
}
// Обработчик создает диалоговое окно
void ButtonOnClick(object sender, EventArgs e)
{
// Создаем объект модального окна,
// локальный внутри обработчика
// Он будет заново создаваться при каждом вызове
ModalDialogBox dlg = new ModalDialogBox();
if (dlg.ShowDialog() == DialogResult.OK)// Надо изменить цвет
{
Random rnd = new Random();
int iShade = rnd.Next(255);
// Проверяем выбор пользователя по состоянию флажка
// Форма еще существует, хоть и скрыта после завершения
if (dlg.GrayShades)
{
this.BackColor = Color.FromArgb( // Только случайный серый
iShade,
iShade,
iShade);
}
else
{
this.BackColor = Color.FromArgb( // Случайный цветной
iShade,// Уже сгенерировано, экономим силы
rnd.Next(255),
rnd.Next(255));
}
}
}
}
}
Файл создания объекта формы приложения останется прежним и будет таким
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
}
Делегаты являются типом, реализующим механизм динамического вызова функций с разными именами, но одинаковой сигнатурой и типом возвращаемого значения. Это еще один подход к полиморфизму в объектно-ориентированном программировании. Делегат аналогичен указателю на функцию в C / C++.
Делегат объявляется как тип в области видимости пространства имен с помощью ключевого слова delegate по следующему синтаксису
delegate тип_возврата имя_типа_делегата(список_параметров);
Внутри вызывающего кода создается
имя_типа_делегата объект = new конструктор_делегата(имя_метода);
После создания экземпляра делегата для конкретного метода вызовы метода можно выполнять через объект делегата
объект_делегата(список_параметров);
вместо
имя_метода(список_параметров);
Все делегаты представляют собой типы, которые неявным образом выводятся из класса System.Delegate, но напрямую так не объявляются.
Приведем поясняющий пример. Предположим, что нужно создать класс для выполнения операций сложения и вычитания двух целых чисел. Вот код этого класса
using System;
using System.Windows.Forms;
namespace MyApp
{
// Объявление своего типа класса
class MyClass
{
string str;
public void Add(int x, int y)
{
str = String.Format("{0} + {1} = {2}", x, y, x + y);
MessageBox.Show(str, "Сложение целых чисел");
}
public void Subtract(int x, int y)
{
str = String.Format("{0} - {1} = {2}", x, y, x - y);
MessageBox.Show(str, "Вычитание целых чисел");
}
}
}
Теперь создадим вызывающую сторону кода класса, в которой предусмотрим
Вот этот код
// Удалили сокращения пространств имен
// Все используемые типы находятся в проекте
namespace MyApp
{
// Объявление своего типа делегата
delegate void Arithmetics(int a, int b);
class EntryPoint
{
static void Main()
{
// Объявляем ссылочную переменную типа делегата
Arithmetics obDelegate;
// Объявляем ссылочную переменную типа класса
MyClass ob;
// Создаем объект класса с методами
ob = new MyClass();
// Заготавливаем передаваемые методам аргументы
int x = 5, y = 10;
// Прямой вызов методов объекта класса
ob.Add(x, y);
ob.Subtract(x, y);
// Косвенный (через делегата) вызов методов объекта класса
// Адресуемся к первому методу объекта класса
obDelegate = new Arithmetics(ob.Add);
obDelegate(x, y); // Вызываем метод через объект делегата
// Адресуемся ко второму методу объекта класса
obDelegate = new Arithmetics(ob.Subtract);
obDelegate(x, y); // Вызываем метод через объект делегата
// Использование делегата для организации цепочки вызовов
obDelegate = new Arithmetics(ob.Add);
obDelegate += new Arithmetics(ob.Subtract);
obDelegate(x, y); // Вызов методов
}
}
}
При запуске приведенного кода трижды повторяется результат сложения и вычитания двух целых чисел, который инициируется вызовами методов напрямую, одиночными вызовами через делегата и множественным вызовом через делегата. Множественная адресации возможна только для функций, возвращающих тип void, одиночная адресация возможна для любого возвращаемого типа.
Способность делегатов к множественной адресации однотипных функций (одинаковой сигнатуры) является наиболее ценным их качеством. Но рассмотренный способ применения делегатов используется не очень часто. Большее распространение получило применение делегатов для создания событий. События являются современным механизмом общения классов и объектов приложения между собой. Один объект может сгенерировать событие и передать вместе с ним требуемые значения аргументов. Другие объекты, которые подписались на это событие и ждут его появления, реагируют на него вызовом соответствующих обработчиков.
Сгенерированное объектом событие помещается в канал сообщений, созданный для приложения операционной системой, и предлагается для обработки всем подписавшимся на него объектам. Подписка объекта на событие означает существование в объекте обработчика этого события. Хотя может быть и такое, что обработчик как метод создан в объекте, но не подключен на прослушивание соответствующего события, т.е. не зарегистрирован в канале сообщений.
Поскольку событие представляет собой косвенный вызов методов (обработчиков) всех подписавшихся на него объектов и может передавать из сгенерировавшего его объекта фактические значения аргументов, то обработчики всех подписавшихся объектов должны иметь одинаковую сигнатуру. Вот здесь-то и нашли широкое применение делегаты, с помощью которых и определяется как сигнатура самого события, так и сигнатура подписавшихся на него обработчиков.
В отличие от делегатов, которые являются типами и объявляются непосредственно в пространстве имен, события являются членами классов и объявляются внутри классов как
Приведем пример. Пусть гаишник стоит на дороге, измеряет скорость, оценивает водителя и по рации передает свое решение на пост писарю и кассиру. Писарь заполняет протокол на штраф или премию, а кассир берет или выдает деньги. Каждый из них самостоятельно определяет, кому что делать, в зависимости от поступившего сообщения. Бедному водителю остается только бодро рапортовать.
Вот код примера
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Объявление с помощью делегата прототипов событий и обработчиков
delegate void Premium(Driver driver, int speed);
delegate void Penalty(Driver driver, int speed);
// Гаишник
class Gai
{
// Объявление событий и критерия оценки
public static event Premium premium;
public static event Penalty penalty;
const int SPEED_BORDER = 60; // Критерий оценки
// Измерить скорость и сгенерировать событие
public static void MeasureSpeed(Driver driver, int speed)
{
// Критерий выполнен и обработчик существует
if (speed <= SPEED_BORDER premium != null)
premium(driver, speed);
else if(penalty != null)
penalty(driver, speed);
}
}
// Писарь
//class Писарь
class Clerk
{
// Вспомогательное поле
string message;
// Конструктор для автоматической подписки
//public Писарь()
public Clerk()
{
// Пописка на события
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
}
void Gai_penalty(Driver driver, int speed)
{
message = "Оштрафовать водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Писарь");
}
void Gai_premium(Driver driver, int speed)
{
message = "Поощрить водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Писарь");
}
}
// Кассир
class Cashier
{
// Вспомогательное поле
string message;
// Конструктор для автоматической подписки
public Cashier()
{
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
}
void Gai_penalty(Driver driver, int speed)
{
message = "Взымаю штраф с водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Кассир");
}
void Gai_premium(Driver driver, int speed)
{
message = "Выдаю премию водителю ";
message += driver.Name + "у за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Кассир");
}
}
// Водитель
class Driver
{
public string Name;
public Driver(string Name)
{
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
this.Name = Name;
}
void Gai_penalty(Driver driver, int speed)
{
if (driver == this)
MessageBox.Show("Признателен за штраф!!!", "Водитель " + Name);
}
void Gai_premium(Driver driver, int speed)
{
if (driver == this)
MessageBox.Show("Благодарю за премию!!!", "Водитель " + Name);
}
}
// Для запуска
class ExecuteClass
{
public ExecuteClass()
{
// Создаем объекты
//Писарь писарь = new Писарь();
// Если событие принимают сразу несколько объектов,
// то порядок обработки событий определяется
// порядком создания принимающих объектов!!!
Clerk clerk = new Clerk();
Cashier cashier = new Cashier();
Driver driver1 = new Driver("Иванов");
Driver driver2 = new Driver("Петров");
Driver driver3 = new Driver("Сидоров");
// Измерить скорость и передать объектам
// готовое решение с дополнительной информацией
Gai.MeasureSpeed(driver1, 50);
Gai.MeasureSpeed(driver2, 70);
Gai.MeasureSpeed(driver3, 90);
}
}
}
// Удалили сокращения пространств имен
// Все используемые типы находятся в проекте
namespace MyApp
{
class EntryPoint
{
static void Main()
{
new ExecuteClass();
}
}
}
Обратите внимание на закомментированное имя типа "Писарь" и ссылку "писарь". Их можно вполне использовать как идентификаторы переменных и пользовательских типов. Visual Studio 2005 это позволяет, но сообщество программистов не приветствует - непрофессионально!
В приведенном коде использован распространенный способ вызова методов разных объектов с помощью событий. Объект, сгенерировавший событие, не заботится о том, будет ли оно обработано другими объектами. Он просто констатирует возникшую ситуацию выбросом сообщения. Если другой объект заинтересован в получении этого сообщения, он должен на него подписаться с регистрацией обработчика. Простая, но очень эффективная схема взаимодействия объектов.
Здесь есть одно обстоятельство. Если объект сгенерировал событие, но к нему не было присоединено ни одного обработчика, то будет сгенерировано исключение. Поэтому мы сделали предварительную проверку ссылки события на нулевое значения.
Теперь, когда мы разобрались с механизмом работы
Вот код программы
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class ModelessDialogBox : Form
{
// Локальное поле ссылки на флажок
CheckBox cbGrayShades;
// Объявляем события для главной формы
public event EventHandler Change;
public event EventHandler DialogClosed;
// Конструктор
public ModelessDialogBox()
{
// Настройка диалогового окна
// Заголовок назначения окна
this.Text = "Цвет формы";
// Неизменяемый размер
this.FormBorderStyle = FormBorderStyle.FixedDialog;
// Отключим все системные кнопки в заголовке окна
this.MinimizeBox = false;// Кнопка свертывания
this.MaximizeBox = false;// Кнопка развертывания
// Не отображать кнопку в панели задач
this.ShowInTaskbar = false; // Нет внизу экрана
// Появление формы при каждом запуске
// определяется нами свойством Location
this.StartPosition = FormStartPosition.Manual;
// Размер клиентской области окна
this.ClientSize = new Size(144, 56);
// Позиция диалогового окна
this.Location =
ActiveForm.Location // Координаты вызывающего окна
// Добавляем сдвиг вправо-вниз относительно вызывающего окна
+ SystemInformation.CaptionButtonSize // Размер надписи кнопки
+ SystemInformation.FrameBorderSize; // Толщина рамки
// Создаем и настраиваем флажок
// Создали объект
cbGrayShades = new CheckBox();
// Назначили родителя
cbGrayShades.Parent = this;
// Смысловая надпись для включенного состояния
cbGrayShades.Text = "Только случайный серый";
// Привязка левого-верхнего угла относительно родителя
int left, top; // Вспомогательные переменные
left = 16; top = 8;
cbGrayShades.Location = new Point(left, top);
// Ширина - высота в пикселах
cbGrayShades.Size = new Size(this.ClientSize.Width - left - 4, 12);
// Создаем и настраиваем две кнопки
Button btn = new Button();
btn.Parent = this;
btn.Text = "OK";
btn.Size = new Size(48, 14);// Размеры кнопки
left = (this.ClientSize.Width - btn.Width) / 2;
btn.Location = new Point(left, 32);// Позиционирование в окне
btn.Click += ButtonOnClick; // Изменить цвет
this.AcceptButton = btn; // Закрепили за клавишей Enter, когда в фокусе
// Автоматическое масштабирование под опорный шрифт
this.AutoScaleDimensions = new Size(4, 8);
this.AutoScaleMode = AutoScaleMode.Font;
}
void ButtonOnClick(object sender, EventArgs e)
{
// Послать сообщение об изменении цвета
if (this.Change != null)
Change(this, new EventArgs());
//Change(this, e);
}
// Для тренировки обертываем открытое свойство
// состояния флажка своим открытым свойством
public bool GrayShades
{
get { return cbGrayShades.Checked; }
set { cbGrayShades.Checked = value; }
}
// Послать сообщение главной форме при закрытии
protected override void OnClosed(EventArgs e)
{
// Аргументы нам не нужны, создаем однотипные затычки
this.DialogClosed(new Object(), new EventArgs());
// Пусть закроется как надо!
base.OnClosed(e);
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class GeneralForm : Form // Наследуем библиотечному классу
{
ModelessDialogBox dlg = null;// Храним ссылку на окно
Random rnd = new Random();// Поле
// Конструктор с настройками
public GeneralForm()
{
// Начальная настройка окна
this.Text = "С немодальным окном";
this.StartPosition = FormStartPosition.CenterScreen;
// Создание и настройка объекта кнопки
Button btn = new Button(); // Кнопка вызова диалогового окна
btn.Parent = this; // Назначаем родителя
btn.Text = "Изменить цвет";// Надпись на кнопке
btn.AutoSize = true;// Подгонять размер под надпись
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = this.ClientSize.Height - 2 * btn.Height;
btn.Location = new Point(left, top); // Координаты привязки
// Подписываемся на событие
btn.Click += ButtonOnClick;
// Автоматическое масштабирование под конкретное разрешение
this.AutoScaleDimensions = new Size(96, 96);
this.AutoScaleMode = AutoScaleMode.Dpi;
}
// Обработчик создает диалоговое окно
void ButtonOnClick(object sender, EventArgs e)
{
// Создать диалоговое окно
if (dlg == null)
dlg = new ModelessDialogBox();
// Назначить владельца и расположить поверх окна приложения
dlg.Owner = this;
// Слушать события диалогового окна
dlg.Change += new EventHandler(DialogOnChange);
dlg.DialogClosed += DialogOnClosed;
// Отобразить диалоговое окно
dlg.Show();
}
void DialogOnChange(object sender, EventArgs e)
{
// Приводим тип переданной ссылки,
// адресующей объект диалогового окна
dlg = (ModelessDialogBox)sender;
// Изменяем цвет главной формы
if (dlg.GrayShades)
{
int iShade = rnd.Next(255);
this.BackColor = Color.FromArgb( // Только случайный серый
iShade,
iShade,
iShade);
}
else
{
this.BackColor = Color.FromArgb( // Случайный цветной
rnd.Next(255),
rnd.Next(255),
rnd.Next(255));
}
}
void DialogOnClosed(object sender, EventArgs e)
{
dlg = null;
}
}
}
using System.Windows.Forms;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new GeneralForm());
}
}
}
Динамически подключаемые библиотеки ( DLL - Dynamic Link Library ) являются наилучшим способом упаковки отлаженного и откомпилированного кода, который может вызываться приложением по мере необходимости. Сейчас в Visual Studio 2005 это сделать очень просто, стоит лишь установить командой Project/Properties/Application опцию Output type=Class Lybrary в свойствах проекта.
В прошлом упражнении мы создали немодальное диалоговое окно с именем класса ModelessDialogBox, еще раньше был создан код модального диалогового окна ModalDialogBox. Оба окна применялись для изменения цвета главной формы приложения. Попробуем упаковать эти классы в библиотечную динамическую сборку MyLibrary в пространстве имен DialogBox. Попутно рассмотрим работу оболочки, когда решение содержит несколько проектов.
С помощью команды меню File/New/Project вызовите окно мастера создания новых проектов и заполните его так
Мастер создаст необходимые вспомогательные файлы решения и проекта и разместит их в цепочке каталогов

В панели Solution Explorer вызовите контекстное меню для узла проекта MyLibrary и выберите опцию Properties
Настройте вкладку Application как показано на снимке
Теперь при добавлении файлов в проект оболочка по умолчанию будет упаковывать код в пространство имен DialogBox и при компиляции создавать сборку DLL с именем MyLibrary.
Командой Project/Add Reference вызовите диалоговое окно оболочки и добавьте к проекту ссылки на библиотечные сборки System.dll, System.Windows.Forms.dll и System.Drawing.dll
Добавьте к проекту два пустых файла с любым именем командой Proiect/Add New Item
Скопируйте в них коды классов модального и немодального диалоговых окон, разработанные нами ранее, измените Объявление классов общедоступными требуется для того, чтобы они были видимы коду, расположенному в других сборках. Собственно, для библиотечных классов, которые предполагается использовать из внешних сборок, это требование характерно всегда.
using System;
using System.Windows.Forms;
using System.Drawing;
namespace DialogBox
{
public class ModalDialogBox : Form
{
// Локальное поле ссылки на флажок
CheckBox cbGrayShades;
// Конструктор
public ModalDialogBox()
{
// Настройка диалогового окна
// Заголовок назначения окна
this.Text = "Цвет формы";
// Неизменяемый размер
this.FormBorderStyle = FormBorderStyle.FixedDialog;
// Отключим все системные кнопки в заголовке окна
this.ControlBox = false; // Значок слева и кнопка закрытия справа
this.MinimizeBox = false;// Кнопка свертывания
this.MaximizeBox = false;// Кнопка развертывания
// Не отображать кнопку в панели задач
this.ShowInTaskbar = false; // Нет внизу экрана
// Появление формы при каждом запуске
// определяется нами свойством Location
this.StartPosition = FormStartPosition.Manual;
// Размер клиентской области окна
this.ClientSize = new Size(144, 56);
// Позиция диалогового окна
this.Location =
ActiveForm.Location // Координаты вызывающего окна
// Добавляем сдвиг вправо-вниз относительно вызывающего окна
+ SystemInformation.CaptionButtonSize // Размер надписи кнопки
+ SystemInformation.FrameBorderSize; // Толщина рамки
// Создаем и настраиваем флажок
// Создали объект
cbGrayShades = new CheckBox();
// Назначили родителя
cbGrayShades.Parent = this;
// Смысловая надпись для включенного состояния
cbGrayShades.Text = "Только случайный серый";
// Привязка левого-верхнего угла относительно родителя
int left, top; // Вспомогательные переменные
left = 16; top = 8;
cbGrayShades.Location = new Point(left, top);
// Ширина - высота в пикселах
cbGrayShades.Size = new Size(this.ClientSize.Width - left - 4, 12);
// Создаем и настраиваем две кнопки
Button btn = new Button();
btn.Parent = this;
btn.Text = "OK";
btn.Location = new Point(16, 32);// Копируем значение структуры
btn.Size = new Size(48, 14);// Копируем значение структуры
btn.DialogResult = DialogResult.OK;// Пометили признаком "ПРИНЯТЬ"
this.AcceptButton = btn; // Закрепили за клавишей Enter, когда в фокусе
btn = new Button(); // Используем ту же ссылочную переменную
btn.Parent = this;
btn.Text = "Cancel";
btn.Location = new Point(80, 32);// Правее первой кнопки
btn.Size = new Size(48, 14);// Тот же размер
btn.DialogResult = DialogResult.Cancel;// Пометили признаком "ОТМЕНИТЬ"
this.CancelButton = btn; // Закрепили за клавишей Esc
// Автоматическое масштабирование под опорный шрифт
this.AutoScaleDimensions = new Size(4, 8);
this.AutoScaleMode = AutoScaleMode.Font;
}
// Для тренировки обертываем открытое свойство
// состояния флажка своим открытым свойством
public bool GrayShades
{
get { return cbGrayShades.Checked; }
set { cbGrayShades.Checked = value; }
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
namespace DialogBox
{
public class ModelessDialogBox : Form
{
// Локальное поле ссылки на флажок
CheckBox cbGrayShades;
// Объявляем события для главной формы
public event EventHandler Change;
public event EventHandler DialogClosed;
// Конструктор
public ModelessDialogBox()
{
// Настройка диалогового окна
// Заголовок назначения окна
this.Text = "Цвет формы";
// Неизменяемый размер
this.FormBorderStyle = FormBorderStyle.FixedDialog;
// Отключим все системные кнопки в заголовке окна
this.MinimizeBox = false;// Кнопка свертывания
this.MaximizeBox = false;// Кнопка развертывания
// Не отображать кнопку в панели задач
this.ShowInTaskbar = false; // Нет внизу экрана
// Появление формы при каждом запуске
// определяется нами свойством Location
this.StartPosition = FormStartPosition.Manual;
// Размер клиентской области окна
this.ClientSize = new Size(144, 56);
// Позиция диалогового окна
this.Location =
ActiveForm.Location // Координаты вызывающего окна
// Добавляем сдвиг вправо-вниз относительно вызывающего окна
+ SystemInformation.CaptionButtonSize // Размер надписи кнопки
+ SystemInformation.FrameBorderSize; // Толщина рамки
// Создаем и настраиваем флажок
// Создали объект
cbGrayShades = new CheckBox();
// Назначили родителя
cbGrayShades.Parent = this;
// Смысловая надпись для включенного состояния
cbGrayShades.Text = "Только случайный серый";
// Привязка левого-верхнего угла относительно родителя
int left, top; // Вспомогательные переменные
left = 16; top = 8;
cbGrayShades.Location = new Point(left, top);
// Ширина - высота в пикселах
cbGrayShades.Size = new Size(this.ClientSize.Width - left - 4, 12);
// Создаем и настраиваем две кнопки
Button btn = new Button();
btn.Parent = this;
btn.Text = "OK";
btn.Size = new Size(48, 14);// Размеры кнопки
left = (this.ClientSize.Width - btn.Width) / 2;
btn.Location = new Point(left, 32);// Позиционирование в окне
btn.Click += ButtonOnClick; // Изменить цвет
this.AcceptButton = btn; // Закрепили за клавишей Enter, когда в фокусе
// Автоматическое масштабирование под опорный шрифт
this.AutoScaleDimensions = new Size(4, 8);
this.AutoScaleMode = AutoScaleMode.Font;
}
void ButtonOnClick(object sender, EventArgs e)
{
// Послать сообщение об изменении цвета
if (this.Change != null)
Change(this, new EventArgs());
//Change(this, e);
}
// Для тренировки обертываем открытое свойство
// состояния флажка своим открытым свойством
public bool GrayShades
{
get { return cbGrayShades.Checked; }
set { cbGrayShades.Checked = value; }
}
// Послать сообщение главной форме при закрытии
protected override void OnClosed(EventArgs e)
{
// Аргументы нам не нужны, создаем однотипные затычки
this.DialogClosed(new Object(), new EventArgs());
// Пусть закроется как надо!
base.OnClosed(e);
}
}
}
Установите опцию Solution Configurations в значение Release (окончательный вариант) и выполните команду Build/Build MyLibrary
В результате выполненных действий мы получили полноценную библиотечную сборку с именем MyLibrary.dll, которая находится в каталоге MyLibrary/bin/Release текущего проекта и хранит в себе два класса. Теперь нужно проверить ее работоспособность. Это делается также просто, как и с классами стандартной библиотеки .NET Framework.
Один из возможных вариантов - это удалить наши файлы с исходным кодом классов. Это можно сделать в панели Solution Explorer двумя способами:
В первом случае файлы будут исключены из проекта но останутся существовать физически, во втором - будут уничтожены.
Но мы сделаем по другому: добавим в решение еще пару проектов для тестирования модального и немодального окон соответственно.
Выполните команду меню File/Add/New Project и добавьте к решению два новых пустых проекта с именем TestModal и TestModeless
После этого

Выделите в панели Solution Explorer последовательно каждый новый проект и через контекстное меню командой Add/New Item добавьте по два новых пустых файла с именами GeneralForm.cs и Program.cs
После этого

Заполните файлы приведенным ниже кодом, который мы ранее уже применяли для тестирования модального и немодального окна, при этом добавьте директиву using на пространство имен нашей библиотечной сборкиusing System;
using System.Windows.Forms;
using System.Drawing;
using DialogBox;
namespace MyApp
{
class GeneralForm : Form // Наследуем библиотечному классу
{
// Конструктор с настройками
public GeneralForm()
{
// Начальная настройка окна
this.Text = "С модальным окном";
this.StartPosition = FormStartPosition.CenterScreen;
// Создание и настройка объекта кнопки
Button btn = new Button(); // Кнопка вызова диалогового окна
btn.Parent = this; // Назначаем родителя
btn.Text = "Изменить цвет";// Надпись на кнопке
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = this.ClientSize.Height - 2 * btn.Height;
btn.Location = new Point(left, top); // Координаты привязки
btn.AutoSize = true;// Подгонять размер под надпись
// Подписываемся на событие
btn.Click += ButtonOnClick;
}
// Обработчик создает диалоговое окно
void ButtonOnClick(object sender, EventArgs e)
{
// Создаем объект модального окна,
// локальный внутри обработчика
// Он будет заново создаваться при каждом вызове
ModalDialogBox dlg = new ModalDialogBox();
if (dlg.ShowDialog() == DialogResult.OK)// Надо изменить цвет
{
Random rnd = new Random();
int iShade = rnd.Next(255);
// Проверяем выбор пользователя по состоянию флажка
// Форма еще существует, хоть и скрыта после завершения
if (dlg.GrayShades)
{
this.BackColor = Color.FromArgb( // Только случайный серый
iShade,
iShade,
iShade);
}
else
{
this.BackColor = Color.FromArgb( // Случайный цветной
iShade,// Уже сгенерировано, экономим силы
rnd.Next(255),
rnd.Next(255));
}
}
}
}
}
using System.Windows.Forms;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new GeneralForm());
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
using DialogBox;
namespace MyApp
{
class GeneralForm : Form // Наследуем библиотечному классу
{
ModelessDialogBox dlg = null;// Храним ссылку на окно
Random rnd = new Random();// Поле
// Конструктор с настройками
public GeneralForm()
{
// Начальная настройка окна
this.Text = "С немодальным окном";
this.StartPosition = FormStartPosition.CenterScreen;
// Создание и настройка объекта кнопки
Button btn = new Button(); // Кнопка вызова диалогового окна
btn.Parent = this; // Назначаем родителя
btn.Text = "Изменить цвет";// Надпись на кнопке
btn.AutoSize = true;// Подгонять размер под надпись
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = this.ClientSize.Height - 2 * btn.Height;
btn.Location = new Point(left, top); // Координаты привязки
// Подписываемся на событие
btn.Click += ButtonOnClick;
// Автоматическое масштабирование под конкретное разрешение
this.AutoScaleDimensions = new Size(96, 96);
this.AutoScaleMode = AutoScaleMode.Dpi;
}
// Обработчик создает диалоговое окно
void ButtonOnClick(object sender, EventArgs e)
{
// Создать диалоговое окно
if (dlg == null)
dlg = new ModelessDialogBox();
// Назначить владельца и расположить поверх окна приложения
dlg.Owner = this;
// Слушать события диалогового окна
dlg.Change += new EventHandler(DialogOnChange);
dlg.DialogClosed += DialogOnClosed;
// Отобразить диалоговое окно
dlg.Show();
}
void DialogOnChange(object sender, EventArgs e)
{
// Приводим тип переданной ссылки,
// адресующей объект диалогового окна
dlg = (ModelessDialogBox)sender;
// Изменяем цвет главной формы
if (dlg.GrayShades)
{
int iShade = rnd.Next(255);
this.BackColor = Color.FromArgb( // Только случайный серый
iShade,
iShade,
iShade);
}
else
{
this.BackColor = Color.FromArgb( // Случайный цветной
rnd.Next(255),
rnd.Next(255),
rnd.Next(255));
}
}
void DialogOnClosed(object sender, EventArgs e)
{
dlg = null;
}
}
}
using System.Windows.Forms;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new GeneralForm());
}
}
}
Выделите по очереди каждый тестовый проект в панели Solution Explorer и к каждому из них командой Project/Add Reference присоедините ссылки на библиотечный сборки
Подобным же образом, но через вкладку Browse, присоедините к тестовым проектам ссылку на нашу библиотеку
После этого

В раскрывающемся списке Solution Configurations
выполните опцию Configuration Manager

и настройте диалоговое окно Configuration Manager так

В окне Configuration Manager мы установили, в каком режиме будут создаваться сборки проектов, входящих в наше решение.
При одновременной отладке библиотечной сборки и тестовых проектов необходимо, чтобы используемая в тестах сборка скомпилировалась раньше самих тестов. Этот порядок можно установить в окне зависимостей проектов Project Dependencies.
В панели Solution Explorer вызовите контекстное меню для узла решения и выполните опцию Project Dependencies (зависимости проекта). В появившемся диалоговом окне выберите вкладку Dependencies и установив поочередно в раскрывающемся списке Projects тестовые проекты отметьте для них проект с библиотекой MyLibrary, от которого эти тестовые проекты зависят
Переключитесь на вкладку Build Order, чтобы проверить порядок построения проектов оболочкой
Теперь библиотека будет создаваться раньше, чем использоваться.
Если решение состоит из нескольких проектов, оболочка Visual Studio 2005 будет компилировать их все, но запустит на выполнение только один проект. Этот стартовый проект выделяется в панели Solution Explorer жирным заголовком. Любой проект решения можно сделать стартовым, если его выделить и выполнить команду Project/Set as StartUp Project (или через контекстное меню узла проекта). То же самое можно сделать с помощью диалогового окна, вызвав его одноименной командой через контекстное меню узла решения.

Учитывая, что библиотечную сборку MyLibrary мы уже скомпилировали ранее, давайте вообще исключим ее из решения, сделав недоступной для оболочки.
В панели Solution Explorer для узла проекта MyLibrary выполните команду контекстного меню Unload ProjectПри добавлении к решению пустого проекта оболочка автоматически присваивает ему тип консольного приложения, т.е. при каждом запуске проекта на выполнение автоматически создается консольное окно. Мы будем тестировать библиотеку в оконном режиме Windows, поэтому не нуждаемся еще и в консольном окне. Отключим его для каждого тестового проекта.
В панели Solution Explorer последовательно для проектов TestModal и TestModeless выполните опцию Properties контекстное меню и во вкладке Application установите раскрывающийся список Output type в значение Windows Application
В панели Solution Explorer выделите узел проекта TestModal и, выполнив команду Project/Set as StartUp Project (или через контекстное меню узла проекта), назначьте его стартовым. Запустите решение и получите результат
В панели Solution Explorer выделите узел проекта TestModeless и, выполнив команду Project/Set as StartUp Project (или через контекстное меню узла проекта), назначьте его стартовым. Запустите решение и получите результат
Обратите внимание, что при компиляции тестовых проектов, использующих DLL -библиотеку MyLibrary, оболочка автоматически скопировала файл с MyLibrary.dll в один каталог с исполнимой сборкой соответствующего теста. Необходимые для работы исполнимого файла DLL -библиотеки должны находиться с ним в одном каталоге или в системном каталоге, где их и будет искать операционная система, если не найдет в текущем.
Все оконные приложения .NET используют класс Form, который размещается в пространстве имен System.Windows.Forms одноименной библиотеки System.Windows.Forms.dll. Приложения, также, часто используют ряд графических возможностей, размещенных в классах библиотечной сборки System.Drawing.dll. Нужна также сборка System.dll.
Конечно, оболочка имеет удобные мастера для создания приложений различных типов, но для дальнейшего изучения возможностей языка C# при создании оконных приложений начнем построение кода с нуля.
Выполните команду меню File/New/Project и выберите из диалогового окна мастера шаблон Empty Project (пустой проект), а также определите имя проекта MyApp и его размещение в файловой системе компьютера. Проследите, чтобы флаг Create directory for solution был сброшен, иначе будет 
Обратите внимание, что панель Solution Explorer не отображает ни одного файла

Обратите, также, внимание, что панель Object Browser уже содержит ссылку на базовую сборку mscorlib.dll ( Microsoft Common Object Runtime Library или Multilanguage Standard Common Object Runtime Library ). Она автоматически подключается к проекту, поскольку содержит базовые типы, необходимые для всех приложений. Наш проект тоже присутствует в этой панели, как будующая сборка, но пока сырая.
Командой Project/Add Reference вызовите диалоговое окно оболочки и добавьте к проекту ссылки на библиотечные сборки System.dll, System.Windows.Forms.dll и System.Drawing.dll
Добавьте к проекту пустой файл с именем Program.cs командой Proiect/Add New Item
Заполните файл следующим кодомclass EntryPoint
{
public static void Main()
{
// Создание графического окна
System.Windows.Forms.Form frm =
new System.Windows.Forms.Form();
// Задержка консольного окна
System.Console.ReadLine();
}
}
Мы видим, что помещать код в свое пространство имен необязательно и указывать библиотечные пространства имен тоже необязательно. В первом случае для больших проектов это может привести к повторению имен типов, во втором - приходится писать полные имена при использовании библиотечных классов.
Запустите приложение - появится только пустое консольное окноКонсольное окно создается и отображается автоматически, а вот графическое окно мы только создали, но не отобразили.
Исправьте код файла Program.cs такclass EntryPoint
{
public static void Main()
{
// Создание и отображение графического окна
System.Windows.Forms.Form frm =
new System.Windows.Forms.Form();
frm.Text = "Мертвое окно";
frm.Show();
// Задержка консольного окна
System.Console.ReadLine();
}
}
При запуске приложения мы увидим созданное окно, но оно не будет реагировать на действия пользователя. Более того, чтобы его увидеть, необходимо специально задерживать консольное окно.

Чтобы это исправить, нужно включить окно в цикл сообщений Windows, чтобы ему передавались от системы типовые сообщения, которые передаются любому графическому окну, имеющему фокус ввода.
Исправьте код файла Program.cs такclass EntryPoint
{
public static void Main()
{
// Создание и отображение графического окна
System.Windows.Forms.Form frm =
new System.Windows.Forms.Form();
frm.Text = "Живое окно";
System.Windows.Forms.Application.Run(frm);
}
}
Теперь мы получили полноценное окно Windows, которое реагирует на действия пользователя, передаваемые ему системой. Специально отображать это окно не нужно. Теперь оно держит приложение до тех пор, пока не будет закрыто либо графическое, либо консольное окно.
Иметь одновременно с графическим окном консольное очень выгодно на этапе отладки. В консольное окно можно выводить значения переменных также, как без графического окна. Но конечному пользователю нужно поставлять приложение без консольного окна.
Отключите консольное окно, вызвав окно свойств проекта командой Project/MyApp Properties и установив опцию Windows Application в списке Output type вкладки Application
Теперь при запуске приложения будет появляться только графическое окно

Это окно имеет по умолчанию стандартный размер 300x300 пикселов, но может настраиваться кодом программы перед его помещением в цикл сообщений системы.
Можно в цикл сообщений поместить сразу ссылку на экземпляр класса Form, возвращаемую оператором new при его создании. Но в этом случае код программы уже не будет иметь доступа к свойствам этого объекта. В приведенном ниже примере создается и настраивается объект на одно окно, а передается в цикл сообщений совершенно другое окно, созданное на лету.
using System;
using System.Windows.Forms;
using System.Drawing;
class EntryPoint
{
public static void Main()
{
// Создание и отображение графического окна
Form frm = new Form();
frm.Text = "Первое окно";
Application.Run(new Form());// Новое окно
}
}
Теперь на новое окно из кода никак влиять нельзя.
Правильным подходом является создание пользовательского класса, наследника библиотечного класса Form. В таком классе можно установить все необходимые настройки и обработчики событий, а уж затем в цикл сообщений передавать создаваемый на лету объект этого класса. Настройки можно разместить в конструкторе по умолчанию, который автоматически срабатывает в момент создания объекта. Такой подход избавит функцию Main() от несвойственного ей кода и сделает программу более структурированной (все на своем месте).
После установки
Любое окно Windows Forms может включать в себя другие элементы управления: кнопки, флажки, радиокнопки, раскрывающиеся и простые списки и т.д. В этом случае элементы управления называются дочерними для графического окна, а само графическое окно будет являться родителем этих элементов.
Добавьте к приложению новый класс с именем MyForm, выполнив команду Project/Add Class
Будем использовать пространство имен MyApp проекта, все равно оболочка не отвяжется и при каждом новом создании класса будет генерировать пространство имен. По умолчанию генерируется пространство имен, взятое из имени проекта, если только его не изменить в настройках оболочки

Разделите код из файла Program.cs на два файла так
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует классу графического окна
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
this.Text = "Живое окно";
this.BackColor = Color.AliceBlue;
// Подписываемся на событие с помощью делегата
this.Paint += new PaintEventHandler(MyPainter);
// this.Paint += MyPainter; // Альтернативный синтаксис
// подключения обработчика
// Шрифт
this.Font = new Font("Arial", 16, FontStyle.Bold);
// Ширина окна
this.Width = this.Height * 2;
// В центре экрана при запуске
this.StartPosition = FormStartPosition.CenterScreen;
}
void MyPainter(object sender, PaintEventArgs e)
{
Form frm = (Form)sender;// Привели типы
// Form frm = sender as Form; // Альтернативный синтаксис
// приведения типов
Graphics gr = e.Graphics;// Контекст экрана
// Создаем и настраиваем точку привязки текстового блока
StringFormat strfrm = new StringFormat();
strfrm.Alignment = StringAlignment.Center;// Центр по горизонтали
strfrm.LineAlignment = StringAlignment.Center;// Центр по вертикали
gr.DrawString("Привет студентам кафедры ПМ и АСУ",
frm.Font, // Применяется шрифт окна
new SolidBrush(Color.Red), // Цвет шрифта
frm.ClientSize.Width / 2, // Центр клиентской области по горизонтали
frm.ClientSize.Height / 2, // Центр клиентской области по вертикали
strfrm); // Передаем точку привязки текстового блока
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyForm());// Новое окно
}
}
}
Результат выполнения будет таким

В конструкторе класса MyForm есть строка подключения обработчика к событию Paint, которое генерируется всякий раз при частичной или полной утрате окна из-за его свертывания, увода за пределы экрана или заслонения другим окном. При возвращении окна на экран автоматически восстанавливается только его заголовок, а о восстановлении клиентской области (содержимого окна) разработчик приложения должен позаботиться сам. Создатели многооконной операционной системы приняли такой порядок специально, чтобы не перегружать ресурсы хранением множества экранов. Они только извещают приложение событием Paint о том, что окно было разрушено и его пора восстановить.
События (сообщения) являются наилучшим способом общения системы и приложения, а также объектов приложения между собой. Когда система или объект генерируют событие (сообщение), оно становится в очередь и принимается одним или несколькими объектами, которые зарегистрировали это событие у себя. Процесс регистрации, т.е. подключение к событию обработчиков называется подпиской на событие. На одно и то же событие могут подписаться несколько объектов, равно как один и тот же объект может подписаться на несколько событий.
Подписавшийся на событие объект ждет его появления и реагирует выполнением кода, помещенного в обработчик. Обработчик размещается в классе как его специальный метод, сигнатура которого определяется делегатом события. Делегат события является прототипом всех обработчиков, подписавшихся на данное событие.
В качестве примера подпишем наше графическое окно на несколько обработчиков:
Измените код приложения следующим образомusing System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует классу графического окна
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Подписка событий";
this.BackColor = SystemColors.Window;
this.StartPosition = FormStartPosition.CenterScreen;
// Подписка на события окна
this.Paint += new PaintEventHandler(MyPaint);
// На событие Click не подписываемся,
// но обработчик создадим
// this.Click += new EventHandler(MyClick);
// Альтернативный синтаксис подписки
this.DoubleClick += MyDoubleClick;
this.Resize += MyResize;
}
// Объявили и инициализировали внутреннее поле
Random rnd = new Random();
void MyPaint(object sender, PaintEventArgs e)
{
MessageBox.Show("Окно разрушено!", "Класс MyForm");
Graphics gr = e.Graphics;// Ссылка на контекст экрана
// Рисуем разрушенную часть
// клиентской области случайным цветом
gr.Clear(Color.FromArgb(rnd.Next(255), // Red
rnd.Next(255), // Green
rnd.Next(255))); // Blue
}
// Обработчик события создан,
// но класс на него не подписался
void MyClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!", "Класс MyForm");
}
void MyDoubleClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен двойной щелчок!", "Класс MyForm");
}
void MyResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!", "Класс MyForm");
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyForm());// Новое окно
}
}
}
Запустите приложение и получите ошибку компиляции с сообщением, что приложение имеет больше чем одну точку входа: " MyApp.exe' has more than one entry point "
В панели Solution Explorer вызовите для узла приложения (корневой узел) контекстное меню и выберите опцию Properties
В окне свойств выберите вкладку Application и в поле Startup object установите явно точку входа, находящуюся в классе MyForm
Запустите приложение и поманипулируйте с графическим окном как конечный пользовательМожно увидеть, что объект формы перехватывает сообщения Windows, на которые мы его подписали, и реагирует на них обработчиками. Для одного события мы обработчик создали, но на само событие не подписались, поэтому окно как объект его не перехватывает.
Наследование является центральным механизмом объектно-ориентированного программирования. Оно позволяет присоединить код уже разработанного класса к новому и уточнить его. Производный ( Derived ) класс наследует все данные и методы базового ( Base ) класса. В коде производного класса остаются доступными те данные и методы, которые в базовом классе объявлены общедоступными ( public ) или защищенными ( protected ). А если эти методы объявлены в базовом классе как виртуальные (с ключевым словом virtual ), то их можно перегружать (используя ключевое слово override ) в производном классе, сохраняя при этом прежнюю доступность, имя и сигнатуру.
Определим в коде нашего примера производный класс, который будет наследовать базовый класс и переопределять обработчики подписанных событий. Для этого объявим в базовом классе обработчики общедоступными и виртуальными. Измененный код будет таким
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Подписка событий";
this.BackColor = SystemColors.Window;
this.StartPosition = FormStartPosition.CenterScreen;
// Подписка на события окна
this.Paint += new PaintEventHandler(MyPaint);
this.Click += new EventHandler(MyClick);
// Альтернативный синтаксис подписки
this.Resize += MyResize;
}
// Объявили и инициализировали внутреннее поле
Random rnd = new Random();
public virtual void MyPaint(object sender, PaintEventArgs e)
{
MessageBox.Show("Окно разрушено!",
"Класс MyForm");
Graphics gr = e.Graphics;// Ссылка на контекст экрана
// Рисуем разрушенную часть
// клиентской области случайным цветом
gr.Clear(Color.FromArgb(rnd.Next(255), // Red
rnd.Next(255), // Green
rnd.Next(255))); // Blue
}
// Обработчик события создан,
// но класс на него не подписался
public virtual void MyClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyForm");
}
public virtual void MyResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyForm");
}
}
// Наследует пользовательскому (нашему) классу
class MyDerivedForm : MyForm
{
public override void MyPaint(object sender, PaintEventArgs e)
{
MessageBox.Show("Окно разрушено!",
"Класс MyDerivedForm");
// Захотели вызвать метод базового класса
base.MyPaint(sender, e);
}
public override void MyClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyDerivedForm");
}
public override void MyResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyDerivedForm");
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyDerivedForm());
}
}
}
Установите в свойствах проекта точку входа по умолчанию ( Not set ) и запустите приложениеВ производном классе мы не подписывались на события - подписка унаследована из базового класса. Мы лишь перегрузили унаследованные обработчики на новые. Старые обработчики можно вызвать из базового класса, адресуясь с помощью ключевого слова base.
Подпишемся в производном классе на те же самые события и определим для них новые обработчики. Все это выполним в конструкторе производного класса. Обработчики базового класса переименуем. Новый код будет таким
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Подписка событий";
this.BackColor = SystemColors.Window;
this.StartPosition = FormStartPosition.CenterScreen;
// Подписка на события окна
this.Click += new EventHandler(OnClick);
// Альтернативный синтаксис подписки
this.Resize += OnResize;
}
// Обработчик события создан,
// но класс на него не подписался
public virtual void OnClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyForm");
}
public virtual void OnResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyForm");
}
}
// Наследует пользовательскому (нашему) классу
class MyDerivedForm : MyForm
{
public MyDerivedForm()
{
// Новая подписка на те же события
this.Click += MyClick;
this.Resize += MyResize;
}
public void MyClick(object sender, EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyDerivedForm");
}
public void MyResize(object sender, EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyDerivedForm");
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyDerivedForm());
}
}
}
Теперь два класса, соединенные
Если сделать обработчики базового класса пустыми, то при возникновении соответствующего события их действия будут никакими, а проявят себя только подписанные обработчики производного класса. И наоборот, если не подписываться на события в производном классе, а перегрузить в нем пустые обработчики тех же событий, подписанные в базовом классе, то опять сработают обработчики производного класса.
Таким образом у нас есть два пути реагировать на события в производном классе: либо явно подписаться на унаследованные от базового класса события, либо перегрузить виртуальные обработчики тех же событий, подписанных в базовом классе. Какой путь выбирать, решать вам, но перегрузка кажется предпочтительней. Зачем создавать новую подписку, если есть уже старая.
При создании графического окна мы использовали свой класс, наследующий от библиотечного класса Form. Последний, в свою очередь, наследует от класса Control. Вот как представлена эта цепочка наследования в панели Object Browser оболочки

Среди множества событий, определенных в классе Control, есть и используемые нами. Все события класса Control подписаны в нем и для каждого определен защищенный виртуальный обработчик, который имеет

Если мы разрабатываем оконный класс, использующий в качестве базового класс Form, то мы получаем вместе с этим классом и все наследуемые им от класса Control события и их защищенные виртуальные обработчики. Поэтому можно либо заново подписаться на эти события и определить свои обработчики, либо перегрузить унаследованные библиотечные обработчики. Далее мы чаще будем использовать второй способ.
Переделаем наш пример, перегрузив в нашем классе наследуемые виртуальные обработчики событий. Вот как это будет выглядеть
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Перегрузка обработчиков";
this.BackColor = SystemColors.Window;
this.StartPosition = FormStartPosition.CenterScreen;
}
// Обработчик события создан,
// но класс на него не подписался
protected override void OnClick(EventArgs e)
{
MessageBox.Show("Выполнен одиночный щелчок!",
"Класс MyForm");
}
protected override void OnResize(EventArgs e)
{
MessageBox.Show("Размеры окна изменены!",
"Класс MyForm");
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.Run(new MyForm());
}
}
}
Код получился намного короче и яснее.
Будем называть наш класс, наследующий от Form, пользовательским окном. Разместим в окне элемент управления типа кнопки, которая порождается классом Button. Все элементы управления являются визуальными и наследуют от класса Control. В следующей программе показано, как создать объект Button, поместить его на поверхность формы и подключить обработчик к событию Click этой кнопки. Продемонстрированы разные способы настройки и управления кнопками.
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Форма с кнопками";
// Создание и настройка первой кнопки
Button btn = new Button(); // Создаем первую объект-кнопку
btn.Text = "Кнопка 1"; // Надпись на первой кнопке
btn.AutoSize = true; // Подстройка размеров под надпись
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = (this.ClientSize.Height - btn.Height) / 2;
btn.Location = new Point(left, top); // Координаты кнопки в
// клиентской области формы
btn.Click += ButtonOnClick;// Подключение обработчика к событию Click
btn.Parent = this; // Присоединяем к форме первую кнопку
// и освобождаем ссылочную переменную
// Создание и настройка второй кнопки
btn = new Button(); // Используем освободившуюся адресную переменную
btn.Text = "Кнопка 2";
btn.AutoSize = true;
top += (this.ClientSize.Height - top) / 2; // Расположим ниже
btn.Location = new Point(left, top);
btn.Click += ButtonOnClick; // Присоединяем тот же обработчик
btn.Enabled = false; // Сначала недоступна
this.Controls.Add(btn); // Другой способ присоединения к форме
btn = null; // Освобождаем ссылку
}
// Флаг состояния доступности (закрытое поле класса)
private bool enableButton = true;
void ButtonOnClick(object sender, EventArgs e)
{
if (enableButton)
MessageBox.Show("Произошел щелчок на первой кнопке!");
else
MessageBox.Show("Произошел щелчок на второй кнопке!");
// Перебираем коллекцию из двух кнопок и меняем доступность
foreach (Control ctrl in this.Controls)
ctrl.Enabled = !ctrl.Enabled;
enableButton = !enableButton;
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
}
Использованный в функции Main() метод Application.EnableVisualStyles() класса позволяет задать современный внешний вид для некоторых элементов управления. Созданная форма с кнопками будет такой

Размещенные на форме кнопки являются ее дочерними объектами и размещаются в коллекции Controls. Учитывая, что в нашем примере дочерними элементами являются только две кнопки, мы не принимали специальные меры для их распознавания. Но в более сложных случаях элементам нужно присваивать имена в их свойстве Name и распознавать уже по значению этого свойства. Для поиска конкретного объекта Control годится как простой перебор всех элементов коллекции Controls формы, так и метод System.Windows.Forms.Control.ControlCollection.Find(string, bool).
Все дочерние элементы формы располагаются в Z-порядке ( Z -последовательности), определяющим их видимость на экране, и могут перекрывать друг друга. Начальный Z -порядок определается порядком добавления элементов к форме и противоположен индексам элементов в коллекции формы: более поздние элементы отображаются верхними. Для изменения Z -порядка в процессе выполнения программы используются методы BringToFront() и SendToBack() класса Control.
В объектно-ориентированном программировании существует два способа передачи кода одного класса другому: это композиция и наследование. Композицией называется создание экземпляра одного класса внутри другого с последующим использованием объекта, а наследование передает код базового класса в производный неявно, сливая вместе два или целую цепочку классов.
Наследование применяют обычно к формам, а композицию - при размещении на форме библиотечных элементов управления. Но библиотечные элементы управления также можно использовать как базовые при наследовании, расширяя их производным классом.
Для примера создадим класс, расширяющий библиотечный класс Button, в котором определим такое поведение кнопки, что если надпись на ней не превышает заданную длину, то она остается недоступной на форме. Для изменения надписи кнопки предусмотрим текстовое поле ввода типа TextBox.
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Наследует библиотечному классу
class MyForm : Form
{
// Локальные поля формы
TextBox txtbox;
ExtensionButton btn;
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "Форма с \"умной\" кнопкой";
// Создание и настройка объекта TextBox
txtbox = new TextBox();
txtbox.Text = "Кнопка";
txtbox.CharacterCasing = CharacterCasing.Upper;
txtbox.Width = 150;
int left = (this.ClientSize.Width - txtbox.Width) / 2;
int top = (this.ClientSize.Height - txtbox.Height) / 3;
txtbox.Location = new Point(left, top);
txtbox.Focus();
txtbox.Parent = this;
// Подписка объекта TextBox на событие
txtbox.TextChanged += new EventHandler(txtbox_TextChanged);
// Создание и настройка объекта умной кнопки
btn = new ExtensionButton(); // Создаем умную кнопку
btn.StrTextBox = txtbox.Text; // Начальная надпись на кнопке
btn.AutoSize = true; // Подстройка размеров под надпись
left = (this.ClientSize.Width - btn.Width) / 2;
top += (this.ClientSize.Height - top) / 2;
btn.Location = new Point(left, top);
btn.Parent = this; // Присоединяем кнопку к форме
}
void txtbox_TextChanged(object sender, EventArgs e)
{
// Меняем значение надписи на кнопке
// с одновременным контролем доступности
btn.StrTextBox = txtbox.Text;
}
}
// Класс расширения кнопки
class ExtensionButton : Button
{
// Локальное поле-константа
private const int MIN_DISABLED = 5;
// Публичное свойство
public string StrTextBox
{
set
{
this.Text = value;
bool enabled = value != null
value.Length > MIN_DISABLED;
this.Enabled = enabled;
}
}
}
}
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
}
Результат выполнения программы будет таким
Проектируя приложение, разработчик не знает, на экранах с каким разрешением оно будет работать. Если не принять специальных мер, то при установке малых разрешений окно будет казаться большим, а при установке больших - маленьким относительно экрана. Для автоматического масштабирования окон приложения в зависимости от разрешения экрана в классе Form имеются два свойства: AutoScaleDimensions и AutoScaleMode, наследуемые от класса ContainerControl, которые и решают эту задачу.
Свойства должны задаваться точно в указанном ниже порядке:
AutoScaleDimensions = new Size(4, 8); AutoScaleMode = AutoScaleMode.Font;
или
AutoScaleDimensions = new SizeF(96, 96); AutoScaleMode = AutoScaleMode.Dpi
или
AutoScaleDimensions = new SizeF(120, 120); AutoScaleMode = AutoScaleMode.Dpi
Перечисление AutoScaleMode имеет значения: Dpi, Font, Inherit, None. Значение Font устанавливает привязку к размерам стандартного значения шрифта Windows, значение Dpi - к разрешению экрана.
Для правильного масштабирования приведенный код нужно располагать ближе к концу конструктора, когда уже задан размер клиентской области формы, созданы все элементы управления и назначены дочерними для формы.
Диалоговые окна служат для получения приложением необходимой информации от пользователя. Чаще всего диалоговые окна вызываются командой меню, заканчивающейся многоточием, или кнопками панели управления. Различают модальные и немодальные диалоговые окна.
Кроме элементов управления для сбора информации окна содержат кнопки OK и Cancel, которые позволяют принять информацию или отказаться от нее. При нажатии любой из этих кнопок диалоговое окно закрывается, но продолжает существовать, пока представляющий его программный объект не выйдет из области видимости и не потеряет адресацию.
На случай использования формы для разработки диалогового окна в ней предусмотрены методы Show() и ShowDialog(), а также свойство DialogResult. Свойство с таким же именем DialogResult есть и у кнопок на случай их использования в диалоговых окнах. Если в обработчике события Click кнопки предусмотреть присвоение его свойству DialogResult одного из значений одноименного перечисления, то свойству DialogResult формы, а также ее возвращаемому значению, будет присвоено это значение.
Среди множества свойств, наследуемых и своих, класс Form имеет свойства AcceptButton и CancelButton, которым можно присвоить ссылки на объекты кнопок, по умолчанию предназначенных на реагирование нажатий клавиш Enter и Esc соответственно. Если упомянутые свойства для формы установлены, то нажатие клавиши Esc равносильно щелчку на соответствующей кнопке Cancel. Нажатие клавиши Enter равносильно щелчку на кнопке OK при условии, что другая кнопка в это время не имеет фокуса ввода.
И еще разок, когда диалоговое окно закроется, оно продолжает существовать и сохраняет свое состояние до тех пор, пока не будет потеряно при выходе своей ссылочной переменной за пределы области видимости. После этого мы уже не сможем адресоваться к такому окну и оно достанется сборщику мусора как брошенная память. Заголовок диалогового окна должен соответствовать запрашиваемой от пользователя информации.
Сконструируем модальное диалоговое окно, на котором разместим флажок и две кнопки. Если флажок будет включен, то изменим цвет клиентской области основного окна приложения на случайный серый, а при выключенном состоянии флажка - в случайный цветной. Следует отметить, что флажок является самодостаточным элементом и самостоятельно управляет изменением своего состояния.
Вот код примера конструирования нашего диалогового окна, иллюстрирующий все сказанное.
Добавьте к приложению пустой файл с именем ModalDialogBox.cs, выполнив команду Project/Add New Item
Заполните файл следующим кодомusing System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class ModalDialogBox : Form
{
// Локальное поле ссылки на флажок
CheckBox cbGrayShades;
// Конструктор
public ModalDialogBox()
{
// Настройка диалогового окна
// Заголовок назначения окна
this.Text = "Цвет формы";
// Неизменяемый размер
this.FormBorderStyle = FormBorderStyle.FixedDialog;
// Отключим все системные кнопки в заголовке окна
this.ControlBox = false; // Значок слева и кнопка закрытия справа
this.MinimizeBox = false;// Кнопка свертывания
this.MaximizeBox = false;// Кнопка развертывания
// Не отображать кнопку в панели задач
this.ShowInTaskbar = false; // Нет внизу экрана
// Появление формы при каждом запуске
// определяется нами свойством Location
this.StartPosition = FormStartPosition.Manual;
// Размер клиентской области окна
this.ClientSize = new Size(144, 56);
// Позиция диалогового окна
this.Location =
ActiveForm.Location // Координаты вызывающего окна
// Добавляем сдвиг вправо-вниз относительно вызывающего окна
+ SystemInformation.CaptionButtonSize // Размер надписи кнопки
+ SystemInformation.FrameBorderSize; // Толщина рамки
// Создаем и настраиваем флажок
// Создали объект
cbGrayShades = new CheckBox();
// Назначили родителя
cbGrayShades.Parent = this;
// Смысловая надпись для включенного состояния
cbGrayShades.Text = "Только случайный серый";
// Привязка левого-верхнего угла относительно родителя
int left, top; // Вспомогательные переменные
left = 16; top = 8;
cbGrayShades.Location = new Point(left, top);
// Ширина - высота в пикселах
cbGrayShades.Size = new Size(this.ClientSize.Width - left - 4, 12);
// Создаем и настраиваем две кнопки
Button btn = new Button();
btn.Parent = this;
btn.Text = "OK";
btn.Location = new Point(16, 32);// Копируем значение структуры
btn.Size = new Size(48, 14);// Копируем значение структуры
btn.DialogResult = DialogResult.OK;// Пометили признаком "ПРИНЯТЬ"
this.AcceptButton = btn; // Закрепили за клавишей Enter, когда в фокусе
btn = new Button(); // Используем ту же ссылочную переменную
btn.Parent = this;
btn.Text = "Cancel";
btn.Location = new Point(80, 32);// Правее первой кнопки
btn.Size = new Size(48, 14);// Тот же размер
btn.DialogResult = DialogResult.Cancel;// Пометили признаком "ОТМЕНИТЬ"
this.CancelButton = btn; // Закрепили за клавишей Esc
// Автоматическое масштабирование под опорный шрифт
this.AutoScaleDimensions = new Size(4, 8);
this.AutoScaleMode = AutoScaleMode.Font;
}
// Для тренировки обертываем открытое свойство
// состояния флажка своим открытым свойством
public bool GrayShades
{
get { return cbGrayShades.Checked; }
set { cbGrayShades.Checked = value; }
}
}
}
Хотя свойство ControlBox ссылается на значок системного меню слева в строке заголовка, но скрывается заодно и кнопка закрытия окна. Если ControlBox задать значение true, то при значении FormBorderStyle = FormBorderStyle.FixedDialog появится только кнопка закрытия.
Откорректируйте файл MyForm.cs реализации основного окна приложения такusing System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class MyForm : Form // Наследуем библиотечному классу
{
// Конструктор с настройками
public MyForm()
{
// Начальная настройка окна
this.Text = "С модальным окном";
this.StartPosition = FormStartPosition.CenterScreen;
// Создание и настройка объекта кнопки
Button btn = new Button(); // Кнопка вызова диалогового окна
btn.Parent = this; // Назначаем родителя
btn.Text = "Изменить цвет";// Надпись на кнопке
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = this.ClientSize.Height - 2 * btn.Height;
btn.Location = new Point(left, top); // Координаты привязки
btn.AutoSize = true;// Подгонять размер под надпись
// Подписываемся на событие
btn.Click += ButtonOnClick;
}
// Обработчик создает диалоговое окно
void ButtonOnClick(object sender, EventArgs e)
{
// Создаем объект модального окна,
// локальный внутри обработчика
// Он будет заново создаваться при каждом вызове
ModalDialogBox dlg = new ModalDialogBox();
if (dlg.ShowDialog() == DialogResult.OK)// Надо изменить цвет
{
Random rnd = new Random();
int iShade = rnd.Next(255);
// Проверяем выбор пользователя по состоянию флажка
// Форма еще существует, хоть и скрыта после завершения
if (dlg.GrayShades)
{
this.BackColor = Color.FromArgb( // Только случайный серый
iShade,
iShade,
iShade);
}
else
{
this.BackColor = Color.FromArgb( // Случайный цветной
iShade,// Уже сгенерировано, экономим силы
rnd.Next(255),
rnd.Next(255));
}
}
}
}
}
Файл создания объекта формы приложения останется прежним и будет таким
using System.Windows.Forms;
// Лишние пространства имен удалили
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
}
Делегаты являются типом, реализующим механизм динамического вызова функций с разными именами, но одинаковой сигнатурой и типом возвращаемого значения. Это еще один подход к полиморфизму в объектно-ориентированном программировании. Делегат аналогичен указателю на функцию в C / C++.
Делегат объявляется как тип в области видимости пространства имен с помощью ключевого слова delegate по следующему синтаксису
delegate тип_возврата имя_типа_делегата(список_параметров);
Внутри вызывающего кода создается
имя_типа_делегата объект = new конструктор_делегата(имя_метода);
После создания экземпляра делегата для конкретного метода вызовы метода можно выполнять через объект делегата
объект_делегата(список_параметров);
вместо
имя_метода(список_параметров);
Все делегаты представляют собой типы, которые неявным образом выводятся из класса System.Delegate, но напрямую так не объявляются.
Приведем поясняющий пример. Предположим, что нужно создать класс для выполнения операций сложения и вычитания двух целых чисел. Вот код этого класса
using System;
using System.Windows.Forms;
namespace MyApp
{
// Объявление своего типа класса
class MyClass
{
string str;
public void Add(int x, int y)
{
str = String.Format("{0} + {1} = {2}", x, y, x + y);
MessageBox.Show(str, "Сложение целых чисел");
}
public void Subtract(int x, int y)
{
str = String.Format("{0} - {1} = {2}", x, y, x - y);
MessageBox.Show(str, "Вычитание целых чисел");
}
}
}
Теперь создадим вызывающую сторону кода класса, в которой предусмотрим
Вот этот код
// Удалили сокращения пространств имен
// Все используемые типы находятся в проекте
namespace MyApp
{
// Объявление своего типа делегата
delegate void Arithmetics(int a, int b);
class EntryPoint
{
static void Main()
{
// Объявляем ссылочную переменную типа делегата
Arithmetics obDelegate;
// Объявляем ссылочную переменную типа класса
MyClass ob;
// Создаем объект класса с методами
ob = new MyClass();
// Заготавливаем передаваемые методам аргументы
int x = 5, y = 10;
// Прямой вызов методов объекта класса
ob.Add(x, y);
ob.Subtract(x, y);
// Косвенный (через делегата) вызов методов объекта класса
// Адресуемся к первому методу объекта класса
obDelegate = new Arithmetics(ob.Add);
obDelegate(x, y); // Вызываем метод через объект делегата
// Адресуемся ко второму методу объекта класса
obDelegate = new Arithmetics(ob.Subtract);
obDelegate(x, y); // Вызываем метод через объект делегата
// Использование делегата для организации цепочки вызовов
obDelegate = new Arithmetics(ob.Add);
obDelegate += new Arithmetics(ob.Subtract);
obDelegate(x, y); // Вызов методов
}
}
}
При запуске приведенного кода трижды повторяется результат сложения и вычитания двух целых чисел, который инициируется вызовами методов напрямую, одиночными вызовами через делегата и множественным вызовом через делегата. Множественная адресации возможна только для функций, возвращающих тип void, одиночная адресация возможна для любого возвращаемого типа.
Способность делегатов к множественной адресации однотипных функций (одинаковой сигнатуры) является наиболее ценным их качеством. Но рассмотренный способ применения делегатов используется не очень часто. Большее распространение получило применение делегатов для создания событий. События являются современным механизмом общения классов и объектов приложения между собой. Один объект может сгенерировать событие и передать вместе с ним требуемые значения аргументов. Другие объекты, которые подписались на это событие и ждут его появления, реагируют на него вызовом соответствующих обработчиков.
Сгенерированное объектом событие помещается в канал сообщений, созданный для приложения операционной системой, и предлагается для обработки всем подписавшимся на него объектам. Подписка объекта на событие означает существование в объекте обработчика этого события. Хотя может быть и такое, что обработчик как метод создан в объекте, но не подключен на прослушивание соответствующего события, т.е. не зарегистрирован в канале сообщений.
Поскольку событие представляет собой косвенный вызов методов (обработчиков) всех подписавшихся на него объектов и может передавать из сгенерировавшего его объекта фактические значения аргументов, то обработчики всех подписавшихся объектов должны иметь одинаковую сигнатуру. Вот здесь-то и нашли широкое применение делегаты, с помощью которых и определяется как сигнатура самого события, так и сигнатура подписавшихся на него обработчиков.
В отличие от делегатов, которые являются типами и объявляются непосредственно в пространстве имен, события являются членами классов и объявляются внутри классов как
Приведем пример. Пусть гаишник стоит на дороге, измеряет скорость, оценивает водителя и по рации передает свое решение на пост писарю и кассиру. Писарь заполняет протокол на штраф или премию, а кассир берет или выдает деньги. Каждый из них самостоятельно определяет, кому что делать, в зависимости от поступившего сообщения. Бедному водителю остается только бодро рапортовать.
Вот код примера
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Объявление с помощью делегата прототипов событий и обработчиков
delegate void Premium(Driver driver, int speed);
delegate void Penalty(Driver driver, int speed);
// Гаишник
class Gai
{
// Объявление событий и критерия оценки
public static event Premium premium;
public static event Penalty penalty;
const int SPEED_BORDER = 60; // Критерий оценки
// Измерить скорость и сгенерировать событие
public static void MeasureSpeed(Driver driver, int speed)
{
// Критерий выполнен и обработчик существует
if (speed <= SPEED_BORDER premium != null)
premium(driver, speed);
else if(penalty != null)
penalty(driver, speed);
}
}
// Писарь
//class Писарь
class Clerk
{
// Вспомогательное поле
string message;
// Конструктор для автоматической подписки
//public Писарь()
public Clerk()
{
// Пописка на события
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
}
void Gai_penalty(Driver driver, int speed)
{
message = "Оштрафовать водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Писарь");
}
void Gai_premium(Driver driver, int speed)
{
message = "Поощрить водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Писарь");
}
}
// Кассир
class Cashier
{
// Вспомогательное поле
string message;
// Конструктор для автоматической подписки
public Cashier()
{
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
}
void Gai_penalty(Driver driver, int speed)
{
message = "Взымаю штраф с водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Кассир");
}
void Gai_premium(Driver driver, int speed)
{
message = "Выдаю премию водителю ";
message += driver.Name + "у за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Кассир");
}
}
// Водитель
class Driver
{
public string Name;
public Driver(string Name)
{
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
this.Name = Name;
}
void Gai_penalty(Driver driver, int speed)
{
if (driver == this)
MessageBox.Show("Признателен за штраф!!!", "Водитель " + Name);
}
void Gai_premium(Driver driver, int speed)
{
if (driver == this)
MessageBox.Show("Благодарю за премию!!!", "Водитель " + Name);
}
}
// Для запуска
class ExecuteClass
{
public ExecuteClass()
{
// Создаем объекты
//Писарь писарь = new Писарь();
// Если событие принимают сразу несколько объектов,
// то порядок обработки событий определяется
// порядком создания принимающих объектов!!!
Clerk clerk = new Clerk();
Cashier cashier = new Cashier();
Driver driver1 = new Driver("Иванов");
Driver driver2 = new Driver("Петров");
Driver driver3 = new Driver("Сидоров");
// Измерить скорость и передать объектам
// готовое решение с дополнительной информацией
Gai.MeasureSpeed(driver1, 50);
Gai.MeasureSpeed(driver2, 70);
Gai.MeasureSpeed(driver3, 90);
}
}
}
// Удалили сокращения пространств имен
// Все используемые типы находятся в проекте
namespace MyApp
{
class EntryPoint
{
static void Main()
{
new ExecuteClass();
}
}
}
Обратите внимание на закомментированное имя типа "Писарь" и ссылку "писарь". Их можно вполне использовать как идентификаторы переменных и пользовательских типов. Visual Studio 2005 это позволяет, но сообщество программистов не приветствует - непрофессионально!
В приведенном коде использован распространенный способ вызова методов разных объектов с помощью событий. Объект, сгенерировавший событие, не заботится о том, будет ли оно обработано другими объектами. Он просто констатирует возникшую ситуацию выбросом сообщения. Если другой объект заинтересован в получении этого сообщения, он должен на него подписаться с регистрацией обработчика. Простая, но очень эффективная схема взаимодействия объектов.
Здесь есть одно обстоятельство. Если объект сгенерировал событие, но к нему не было присоединено ни одного обработчика, то будет сгенерировано исключение. Поэтому мы сделали предварительную проверку ссылки события на нулевое значения.
Теперь, когда мы разобрались с механизмом работы
Вот код программы
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class ModelessDialogBox : Form
{
// Локальное поле ссылки на флажок
CheckBox cbGrayShades;
// Объявляем события для главной формы
public event EventHandler Change;
public event EventHandler DialogClosed;
// Конструктор
public ModelessDialogBox()
{
// Настройка диалогового окна
// Заголовок назначения окна
this.Text = "Цвет формы";
// Неизменяемый размер
this.FormBorderStyle = FormBorderStyle.FixedDialog;
// Отключим все системные кнопки в заголовке окна
this.MinimizeBox = false;// Кнопка свертывания
this.MaximizeBox = false;// Кнопка развертывания
// Не отображать кнопку в панели задач
this.ShowInTaskbar = false; // Нет внизу экрана
// Появление формы при каждом запуске
// определяется нами свойством Location
this.StartPosition = FormStartPosition.Manual;
// Размер клиентской области окна
this.ClientSize = new Size(144, 56);
// Позиция диалогового окна
this.Location =
ActiveForm.Location // Координаты вызывающего окна
// Добавляем сдвиг вправо-вниз относительно вызывающего окна
+ SystemInformation.CaptionButtonSize // Размер надписи кнопки
+ SystemInformation.FrameBorderSize; // Толщина рамки
// Создаем и настраиваем флажок
// Создали объект
cbGrayShades = new CheckBox();
// Назначили родителя
cbGrayShades.Parent = this;
// Смысловая надпись для включенного состояния
cbGrayShades.Text = "Только случайный серый";
// Привязка левого-верхнего угла относительно родителя
int left, top; // Вспомогательные переменные
left = 16; top = 8;
cbGrayShades.Location = new Point(left, top);
// Ширина - высота в пикселах
cbGrayShades.Size = new Size(this.ClientSize.Width - left - 4, 12);
// Создаем и настраиваем две кнопки
Button btn = new Button();
btn.Parent = this;
btn.Text = "OK";
btn.Size = new Size(48, 14);// Размеры кнопки
left = (this.ClientSize.Width - btn.Width) / 2;
btn.Location = new Point(left, 32);// Позиционирование в окне
btn.Click += ButtonOnClick; // Изменить цвет
this.AcceptButton = btn; // Закрепили за клавишей Enter, когда в фокусе
// Автоматическое масштабирование под опорный шрифт
this.AutoScaleDimensions = new Size(4, 8);
this.AutoScaleMode = AutoScaleMode.Font;
}
void ButtonOnClick(object sender, EventArgs e)
{
// Послать сообщение об изменении цвета
if (this.Change != null)
Change(this, new EventArgs());
//Change(this, e);
}
// Для тренировки обертываем открытое свойство
// состояния флажка своим открытым свойством
public bool GrayShades
{
get { return cbGrayShades.Checked; }
set { cbGrayShades.Checked = value; }
}
// Послать сообщение главной форме при закрытии
protected override void OnClosed(EventArgs e)
{
// Аргументы нам не нужны, создаем однотипные затычки
this.DialogClosed(new Object(), new EventArgs());
// Пусть закроется как надо!
base.OnClosed(e);
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
class GeneralForm : Form // Наследуем библиотечному классу
{
ModelessDialogBox dlg = null;// Храним ссылку на окно
Random rnd = new Random();// Поле
// Конструктор с настройками
public GeneralForm()
{
// Начальная настройка окна
this.Text = "С немодальным окном";
this.StartPosition = FormStartPosition.CenterScreen;
// Создание и настройка объекта кнопки
Button btn = new Button(); // Кнопка вызова диалогового окна
btn.Parent = this; // Назначаем родителя
btn.Text = "Изменить цвет";// Надпись на кнопке
btn.AutoSize = true;// Подгонять размер под надпись
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = this.ClientSize.Height - 2 * btn.Height;
btn.Location = new Point(left, top); // Координаты привязки
// Подписываемся на событие
btn.Click += ButtonOnClick;
// Автоматическое масштабирование под конкретное разрешение
this.AutoScaleDimensions = new Size(96, 96);
this.AutoScaleMode = AutoScaleMode.Dpi;
}
// Обработчик создает диалоговое окно
void ButtonOnClick(object sender, EventArgs e)
{
// Создать диалоговое окно
if (dlg == null)
dlg = new ModelessDialogBox();
// Назначить владельца и расположить поверх окна приложения
dlg.Owner = this;
// Слушать события диалогового окна
dlg.Change += new EventHandler(DialogOnChange);
dlg.DialogClosed += DialogOnClosed;
// Отобразить диалоговое окно
dlg.Show();
}
void DialogOnChange(object sender, EventArgs e)
{
// Приводим тип переданной ссылки,
// адресующей объект диалогового окна
dlg = (ModelessDialogBox)sender;
// Изменяем цвет главной формы
if (dlg.GrayShades)
{
int iShade = rnd.Next(255);
this.BackColor = Color.FromArgb( // Только случайный серый
iShade,
iShade,
iShade);
}
else
{
this.BackColor = Color.FromArgb( // Случайный цветной
rnd.Next(255),
rnd.Next(255),
rnd.Next(255));
}
}
void DialogOnClosed(object sender, EventArgs e)
{
dlg = null;
}
}
}
using System.Windows.Forms;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new GeneralForm());
}
}
}
Динамически подключаемые библиотеки ( DLL - Dynamic Link Library ) являются наилучшим способом упаковки отлаженного и откомпилированного кода, который может вызываться приложением по мере необходимости. Сейчас в Visual Studio 2005 это сделать очень просто, стоит лишь установить командой Project/Properties/Application опцию Output type=Class Lybrary в свойствах проекта.
В прошлом упражнении мы создали немодальное диалоговое окно с именем класса ModelessDialogBox, еще раньше был создан код модального диалогового окна ModalDialogBox. Оба окна применялись для изменения цвета главной формы приложения. Попробуем упаковать эти классы в библиотечную динамическую сборку MyLibrary в пространстве имен DialogBox. Попутно рассмотрим работу оболочки, когда решение содержит несколько проектов.
С помощью команды меню File/New/Project вызовите окно мастера создания новых проектов и заполните его так
Мастер создаст необходимые вспомогательные файлы решения и проекта и разместит их в цепочке каталогов

В панели Solution Explorer вызовите контекстное меню для узла проекта MyLibrary и выберите опцию Properties
Настройте вкладку Application как показано на снимке
Теперь при добавлении файлов в проект оболочка по умолчанию будет упаковывать код в пространство имен DialogBox и при компиляции создавать сборку DLL с именем MyLibrary.
Командой Project/Add Reference вызовите диалоговое окно оболочки и добавьте к проекту ссылки на библиотечные сборки System.dll, System.Windows.Forms.dll и System.Drawing.dll
Добавьте к проекту два пустых файла с любым именем командой Proiect/Add New Item
Скопируйте в них коды классов модального и немодального диалоговых окон, разработанные нами ранее, измените Объявление классов общедоступными требуется для того, чтобы они были видимы коду, расположенному в других сборках. Собственно, для библиотечных классов, которые предполагается использовать из внешних сборок, это требование характерно всегда.
using System;
using System.Windows.Forms;
using System.Drawing;
namespace DialogBox
{
public class ModalDialogBox : Form
{
// Локальное поле ссылки на флажок
CheckBox cbGrayShades;
// Конструктор
public ModalDialogBox()
{
// Настройка диалогового окна
// Заголовок назначения окна
this.Text = "Цвет формы";
// Неизменяемый размер
this.FormBorderStyle = FormBorderStyle.FixedDialog;
// Отключим все системные кнопки в заголовке окна
this.ControlBox = false; // Значок слева и кнопка закрытия справа
this.MinimizeBox = false;// Кнопка свертывания
this.MaximizeBox = false;// Кнопка развертывания
// Не отображать кнопку в панели задач
this.ShowInTaskbar = false; // Нет внизу экрана
// Появление формы при каждом запуске
// определяется нами свойством Location
this.StartPosition = FormStartPosition.Manual;
// Размер клиентской области окна
this.ClientSize = new Size(144, 56);
// Позиция диалогового окна
this.Location =
ActiveForm.Location // Координаты вызывающего окна
// Добавляем сдвиг вправо-вниз относительно вызывающего окна
+ SystemInformation.CaptionButtonSize // Размер надписи кнопки
+ SystemInformation.FrameBorderSize; // Толщина рамки
// Создаем и настраиваем флажок
// Создали объект
cbGrayShades = new CheckBox();
// Назначили родителя
cbGrayShades.Parent = this;
// Смысловая надпись для включенного состояния
cbGrayShades.Text = "Только случайный серый";
// Привязка левого-верхнего угла относительно родителя
int left, top; // Вспомогательные переменные
left = 16; top = 8;
cbGrayShades.Location = new Point(left, top);
// Ширина - высота в пикселах
cbGrayShades.Size = new Size(this.ClientSize.Width - left - 4, 12);
// Создаем и настраиваем две кнопки
Button btn = new Button();
btn.Parent = this;
btn.Text = "OK";
btn.Location = new Point(16, 32);// Копируем значение структуры
btn.Size = new Size(48, 14);// Копируем значение структуры
btn.DialogResult = DialogResult.OK;// Пометили признаком "ПРИНЯТЬ"
this.AcceptButton = btn; // Закрепили за клавишей Enter, когда в фокусе
btn = new Button(); // Используем ту же ссылочную переменную
btn.Parent = this;
btn.Text = "Cancel";
btn.Location = new Point(80, 32);// Правее первой кнопки
btn.Size = new Size(48, 14);// Тот же размер
btn.DialogResult = DialogResult.Cancel;// Пометили признаком "ОТМЕНИТЬ"
this.CancelButton = btn; // Закрепили за клавишей Esc
// Автоматическое масштабирование под опорный шрифт
this.AutoScaleDimensions = new Size(4, 8);
this.AutoScaleMode = AutoScaleMode.Font;
}
// Для тренировки обертываем открытое свойство
// состояния флажка своим открытым свойством
public bool GrayShades
{
get { return cbGrayShades.Checked; }
set { cbGrayShades.Checked = value; }
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
namespace DialogBox
{
public class ModelessDialogBox : Form
{
// Локальное поле ссылки на флажок
CheckBox cbGrayShades;
// Объявляем события для главной формы
public event EventHandler Change;
public event EventHandler DialogClosed;
// Конструктор
public ModelessDialogBox()
{
// Настройка диалогового окна
// Заголовок назначения окна
this.Text = "Цвет формы";
// Неизменяемый размер
this.FormBorderStyle = FormBorderStyle.FixedDialog;
// Отключим все системные кнопки в заголовке окна
this.MinimizeBox = false;// Кнопка свертывания
this.MaximizeBox = false;// Кнопка развертывания
// Не отображать кнопку в панели задач
this.ShowInTaskbar = false; // Нет внизу экрана
// Появление формы при каждом запуске
// определяется нами свойством Location
this.StartPosition = FormStartPosition.Manual;
// Размер клиентской области окна
this.ClientSize = new Size(144, 56);
// Позиция диалогового окна
this.Location =
ActiveForm.Location // Координаты вызывающего окна
// Добавляем сдвиг вправо-вниз относительно вызывающего окна
+ SystemInformation.CaptionButtonSize // Размер надписи кнопки
+ SystemInformation.FrameBorderSize; // Толщина рамки
// Создаем и настраиваем флажок
// Создали объект
cbGrayShades = new CheckBox();
// Назначили родителя
cbGrayShades.Parent = this;
// Смысловая надпись для включенного состояния
cbGrayShades.Text = "Только случайный серый";
// Привязка левого-верхнего угла относительно родителя
int left, top; // Вспомогательные переменные
left = 16; top = 8;
cbGrayShades.Location = new Point(left, top);
// Ширина - высота в пикселах
cbGrayShades.Size = new Size(this.ClientSize.Width - left - 4, 12);
// Создаем и настраиваем две кнопки
Button btn = new Button();
btn.Parent = this;
btn.Text = "OK";
btn.Size = new Size(48, 14);// Размеры кнопки
left = (this.ClientSize.Width - btn.Width) / 2;
btn.Location = new Point(left, 32);// Позиционирование в окне
btn.Click += ButtonOnClick; // Изменить цвет
this.AcceptButton = btn; // Закрепили за клавишей Enter, когда в фокусе
// Автоматическое масштабирование под опорный шрифт
this.AutoScaleDimensions = new Size(4, 8);
this.AutoScaleMode = AutoScaleMode.Font;
}
void ButtonOnClick(object sender, EventArgs e)
{
// Послать сообщение об изменении цвета
if (this.Change != null)
Change(this, new EventArgs());
//Change(this, e);
}
// Для тренировки обертываем открытое свойство
// состояния флажка своим открытым свойством
public bool GrayShades
{
get { return cbGrayShades.Checked; }
set { cbGrayShades.Checked = value; }
}
// Послать сообщение главной форме при закрытии
protected override void OnClosed(EventArgs e)
{
// Аргументы нам не нужны, создаем однотипные затычки
this.DialogClosed(new Object(), new EventArgs());
// Пусть закроется как надо!
base.OnClosed(e);
}
}
}
Установите опцию Solution Configurations в значение Release (окончательный вариант) и выполните команду Build/Build MyLibrary 
В результате выполненных действий мы получили полноценную библиотечную сборку с именем MyLibrary.dll, которая находится в каталоге MyLibrary/bin/Release текущего проекта и хранит в себе два класса. Теперь нужно проверить ее работоспособность. Это делается также просто, как и с классами стандартной библиотеки .NET Framework.
Один из возможных вариантов - это удалить наши файлы с исходным кодом классов. Это можно сделать в панели Solution Explorer двумя способами:
В первом случае файлы будут исключены из проекта но останутся существовать физически, во втором - будут уничтожены.
Но мы сделаем по другому: добавим в решение еще пару проектов для тестирования модального и немодального окон соответственно.
Выполните команду меню File/Add/New Project и добавьте к решению два новых пустых проекта с именем TestModal и TestModeless
После этого

Выделите в панели Solution Explorer последовательно каждый новый проект и через контекстное меню командой Add/New Item добавьте по два новых пустых файла с именами GeneralForm.cs и Program.cs
После этого

Заполните файлы приведенным ниже кодом, который мы ранее уже применяли для тестирования модального и немодального окна, при этом добавьте директиву using на пространство имен нашей библиотечной сборкиusing System;
using System.Windows.Forms;
using System.Drawing;
using DialogBox;
namespace MyApp
{
class GeneralForm : Form // Наследуем библиотечному классу
{
// Конструктор с настройками
public GeneralForm()
{
// Начальная настройка окна
this.Text = "С модальным окном";
this.StartPosition = FormStartPosition.CenterScreen;
// Создание и настройка объекта кнопки
Button btn = new Button(); // Кнопка вызова диалогового окна
btn.Parent = this; // Назначаем родителя
btn.Text = "Изменить цвет";// Надпись на кнопке
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = this.ClientSize.Height - 2 * btn.Height;
btn.Location = new Point(left, top); // Координаты привязки
btn.AutoSize = true;// Подгонять размер под надпись
// Подписываемся на событие
btn.Click += ButtonOnClick;
}
// Обработчик создает диалоговое окно
void ButtonOnClick(object sender, EventArgs e)
{
// Создаем объект модального окна,
// локальный внутри обработчика
// Он будет заново создаваться при каждом вызове
ModalDialogBox dlg = new ModalDialogBox();
if (dlg.ShowDialog() == DialogResult.OK)// Надо изменить цвет
{
Random rnd = new Random();
int iShade = rnd.Next(255);
// Проверяем выбор пользователя по состоянию флажка
// Форма еще существует, хоть и скрыта после завершения
if (dlg.GrayShades)
{
this.BackColor = Color.FromArgb( // Только случайный серый
iShade,
iShade,
iShade);
}
else
{
this.BackColor = Color.FromArgb( // Случайный цветной
iShade,// Уже сгенерировано, экономим силы
rnd.Next(255),
rnd.Next(255));
}
}
}
}
}
using System.Windows.Forms;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new GeneralForm());
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing;
using DialogBox;
namespace MyApp
{
class GeneralForm : Form // Наследуем библиотечному классу
{
ModelessDialogBox dlg = null;// Храним ссылку на окно
Random rnd = new Random();// Поле
// Конструктор с настройками
public GeneralForm()
{
// Начальная настройка окна
this.Text = "С немодальным окном";
this.StartPosition = FormStartPosition.CenterScreen;
// Создание и настройка объекта кнопки
Button btn = new Button(); // Кнопка вызова диалогового окна
btn.Parent = this; // Назначаем родителя
btn.Text = "Изменить цвет";// Надпись на кнопке
btn.AutoSize = true;// Подгонять размер под надпись
int left = (this.ClientSize.Width - btn.Width) / 2;
int top = this.ClientSize.Height - 2 * btn.Height;
btn.Location = new Point(left, top); // Координаты привязки
// Подписываемся на событие
btn.Click += ButtonOnClick;
// Автоматическое масштабирование под конкретное разрешение
this.AutoScaleDimensions = new Size(96, 96);
this.AutoScaleMode = AutoScaleMode.Dpi;
}
// Обработчик создает диалоговое окно
void ButtonOnClick(object sender, EventArgs e)
{
// Создать диалоговое окно
if (dlg == null)
dlg = new ModelessDialogBox();
// Назначить владельца и расположить поверх окна приложения
dlg.Owner = this;
// Слушать события диалогового окна
dlg.Change += new EventHandler(DialogOnChange);
dlg.DialogClosed += DialogOnClosed;
// Отобразить диалоговое окно
dlg.Show();
}
void DialogOnChange(object sender, EventArgs e)
{
// Приводим тип переданной ссылки,
// адресующей объект диалогового окна
dlg = (ModelessDialogBox)sender;
// Изменяем цвет главной формы
if (dlg.GrayShades)
{
int iShade = rnd.Next(255);
this.BackColor = Color.FromArgb( // Только случайный серый
iShade,
iShade,
iShade);
}
else
{
this.BackColor = Color.FromArgb( // Случайный цветной
rnd.Next(255),
rnd.Next(255),
rnd.Next(255));
}
}
void DialogOnClosed(object sender, EventArgs e)
{
dlg = null;
}
}
}
using System.Windows.Forms;
namespace MyApp
{
class EntryPoint
{
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new GeneralForm());
}
}
}
Выделите по очереди каждый тестовый проект в панели Solution Explorer и к каждому из них командой Project/Add Reference присоедините ссылки на библиотечный сборки
Подобным же образом, но через вкладку Browse, присоедините к тестовым проектам ссылку на нашу библиотеку
После этого

В раскрывающемся списке Solution Configurations
выполните опцию Configuration Manager

и настройте диалоговое окно Configuration Manager так

В окне Configuration Manager мы установили, в каком режиме будут создаваться сборки проектов, входящих в наше решение.
При одновременной отладке библиотечной сборки и тестовых проектов необходимо, чтобы используемая в тестах сборка скомпилировалась раньше самих тестов. Этот порядок можно установить в окне зависимостей проектов Project Dependencies.
В панели Solution Explorer вызовите контекстное меню для узла решения и выполните опцию Project Dependencies (зависимости проекта). В появившемся диалоговом окне выберите вкладку Dependencies и установив поочередно в раскрывающемся списке Projects тестовые проекты отметьте для них проект с библиотекой MyLibrary, от которого эти тестовые проекты зависят
Переключитесь на вкладку Build Order, чтобы проверить порядок построения проектов оболочкой
Теперь библиотека будет создаваться раньше, чем использоваться.
Если решение состоит из нескольких проектов, оболочка Visual Studio 2005 будет компилировать их все, но запустит на выполнение только один проект. Этот стартовый проект выделяется в панели Solution Explorer жирным заголовком. Любой проект решения можно сделать стартовым, если его выделить и выполнить команду Project/Set as StartUp Project (или через контекстное меню узла проекта). То же самое можно сделать с помощью диалогового окна, вызвав его одноименной командой через контекстное меню узла решения.

Учитывая, что библиотечную сборку MyLibrary мы уже скомпилировали ранее, давайте вообще исключим ее из решения, сделав недоступной для оболочки.
В панели Solution Explorer для узла проекта MyLibrary выполните команду контекстного меню Unload ProjectПри добавлении к решению пустого проекта оболочка автоматически присваивает ему тип консольного приложения, т.е. при каждом запуске проекта на выполнение автоматически создается консольное окно. Мы будем тестировать библиотеку в оконном режиме Windows, поэтому не нуждаемся еще и в консольном окне. Отключим его для каждого тестового проекта.
В панели Solution Explorer последовательно для проектов TestModal и TestModeless выполните опцию Properties контекстное меню и во вкладке Application установите раскрывающийся список Output type в значение Windows Application
В панели Solution Explorer выделите узел проекта TestModal и, выполнив команду Project/Set as StartUp Project (или через контекстное меню узла проекта), назначьте его стартовым. Запустите решение и получите результат
В панели Solution Explorer выделите узел проекта TestModeless и, выполнив команду Project/Set as StartUp Project (или через контекстное меню узла проекта), назначьте его стартовым. Запустите решение и получите результат
Обратите внимание, что при компиляции тестовых проектов, использующих DLL -библиотеку MyLibrary, оболочка автоматически скопировала файл с MyLibrary.dll в один каталог с исполнимой сборкой соответствующего теста. Необходимые для работы исполнимого файла DLL -библиотеки должны находиться с ним в одном каталоге или в системном каталоге, где их и будет искать операционная система, если не найдет в текущем.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.