Элементы управления являются посредниками между приложением и пользователем. Они составляют так называемый графический интерфейс пользователя (GUI - ). Их задача состоит в отображении информации для пользователя, обеспечении удобного ввода и генерации событий, отрабатывающих предпочтения пользователя.
Как правило, библиотечных элементов управления достаточно для решения большинства задач, но иногда программисту может потребоваться нестандартный элемент управления. В таком случае он может разработать свой элемент управления с нуля или расширить стандартный элемент. Такой элемент управления называют пользовательским.
Механизм наследования C# предоставляет широкие возможности модификации существующих элементов управления на уровне
Построим кнопку, при щелчке на которой компьютер выдаст звуковой сигнал. Для этого используем библиотечный элемент управления Button в качестве базового класса и расширим его для решения своей задачи.
Создайте новый пустой проект командой File/New/Project и заполните окно мастера так
В панели Solution Explorer выделите корневой узел проекта UserControls и через контекстное меню выполните команду Properties
Откройте вкладку Application свойств оболочки и настройте поля Default namespace и Output type как показано на рисунке
В панели Solution Explorer вызовите для корневого узла проекта UserControls контекстное меню и выполните команду Add/Component
Добавьте к проекту новый файл компонента с именем BeepButton.cs
В рабочей области конструктора форм щелкните на ссылке click here to switch to code view или исполните команду контекстного меню View Code, чтобы перейти в режим редактирования кода
В панели Solution Explorer для узла References, где уже должна находиться ссылка на библиотечную сборку System, выполните команду Add Reference
Удерживая клавишу Ctrl через окно Add Reference выделите и добавьте к проекту ссылки на библиотечные сборки System.Drawing и System.Windows.Forms
Убедитесь, что теперь узел References проекта стал выглядеть так
Перейдите в режим редактирования файла BeepButton.cs и откорректируйте его такusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Media; // Для звука
namespace MyCompany.UserControls
{
public partial class BeepButton : Button
{
public BeepButton()
{
InitializeComponent();
}
public BeepButton(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
Класс Button является наследником класса Component, поэтому мы спокойно применяем его в качестве базового, продолжая цепочку наследования.
Большинство библиотечных классов, в которых определены события, имеют соответствующие виртуальные методы диспетчеризации с приставкой On. Эти методы можно переопределять в классах-наследниках, дополняя нужным кодом. Воспользуемся этой возможностью в нашем классе расширения кнопки, упаковав переопределение метода в отдельную часть класса BeepButton.
Дополните конец файла BeepButton.cs следующим кодомnamespace MyCompany.UserControls
{
partial class BeepButton
{
// Переопределяем виртуальный метод диспетчеризации
// класса Control (задействуется наследуемый метод
// диспетчеризации класса кнопки)
protected override void OnClick(EventArgs e)
{
// Проиграть системный звук
SystemSounds.Exclamation.Play();// Сочный через внешние динамики
//SystemSounds.Beep.Play(); // Простой через внешние динамики
//System.Console.Beep(); // Простой через внутренний динамик
// Подняться до базового класса Control,
// чтобы сгенерировать его событие Click и
// запустить наш будущий обработчик ButtonOnClick
base.OnClick(e);
}
}
}
В
protected virtual void OnClick(EventArgs args)
{
//...
// Если хоть один обработчик зарегистрирован
if (Click != null)
Click(this, args);
//...
}
Испытание кнопки BeepButton мы проведем позднее, разработав для этого тестовую исполнимую Windows -сборку.
В данном разделе мы спроектируем собственное простое диалоговое окно сообщений с именем MyDialogBox, которое и будет вызываться из обработчика события Click нашей кнопки, если на него подпишется клиент компонента BeepButton.
Создание диалогового окна будет состоять из трех шагов:
В панели Solution Explorer выделите узел проекта UserControls и командой Add/Windows Form добавьте к компоненту файл формы с именем MyDialogForm.cs
Удалите из узла References лишние добавленные мастером ссылки на библиотечные сборки System.Data и System.Xml
Удалите соответствующие инструкции using подключения пространств имен в начале файла MyDialogForm.cs
Настройте форму MyDialogForm в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
StartPosition |
CenterScreen |
Открывать в центре экрана |
FormBorderStyle |
FixedDialog |
Запретить пользователю менять размер |
MaximizeBox |
false |
Отключить системную кнопку развертывания |
MinimizeBox |
false |
Отключить системную кнопку свертывания |
ShowInTaskbar |
false |
Запретить появление в панели задач |
|
true |
Подстраиваться под содержимое |
AutoSizeMode |
GrowAndShrink |
Подстраиваться под содержимое (расширяться и сжиматься) |
Text |
Пусто | Заголовок окна |
Из свитка Containers панели инструментов Toolbox поместите на форму компоновочную панель FlowLayoutPanel и настройте ее в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
Name |
flow |
Имя компоновочной панели |
|
true |
Подстраиваться под содержимое |
FlowDirection |
TopDown |
Размещать сверху вниз |
WrapContents |
false |
Не переносить дочерние элементы в следующий столбец |
Padding.All |
8 | |
Dock |
Fill |
Распахнуть на всю клиентскую область родителя (формы) |
Эта панель будет следить за размещением своих дочерних элементов в соответствии с настройками.
Поместите внутрь экземпляра компоновочной панели flow из свитка Common Controls текстовую метку Label и настройте ее свойства в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
Name |
lblMessage |
Имя текстовой метки |
|
true |
Подстраиваться под содержимое |
Anchor |
None |
Расположить посредине |
Margin.All |
8 | Отступ по наружному периметру текстовой метки |
Modifiers |
Public |
Публичный доступ для клиентов формы (для класса MyDialogBox ) |
Для того, чтобы объект lblMessage был доступен из метода класса MyDialogBox, при его создании мы сделали его общедоступным.
Поместите внутрь компоновочной панели flow из свитка Common Controls кнопку Button и настройте ее свойства в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
Name |
btnShowDialog |
Имя кнопки |
|
true |
Подстраиваться под содержимое |
Anchor |
None |
Расположить посредине |
Margin.All |
8 | Отступ по наружному периметру кнопки |
Text |
OK |
Надпись |
DialogResult |
OK |
Назначить статус "кнопки OK" в родительской форме |
Свойству дочерних кнопок формы DialogResult можно присвоить любое значение из перечисления System.Windows.Forms.DialogResult ( Abort, Cancel, Ignore, No, None, OK, Retry, Yes ). Оно и будет возвращено функцией ShowDialog() формы, если пользователь выберет именно эту кнопку.
Мы создали интерфейсный класс диалогового окна. Теперь нужно создать
В панели Solution Explorer выделите узел проекта UserControls и, выполнив команду меню Project/Add Class, добавьте к проекту новый класс с именем MyDialogBox
Заполните файл MyDialogBox.cs следующим кодомusing System;
using System.Windows.Forms;
namespace MyCompany.UserControls
{
// Наше диалоговое окно сообщения
public class MyDialogBox
{
public static DialogResult Show(string strMessage, string strCaption)
{
// Создаем диалоговое окно
MyDialogForm frm = new MyDialogForm();
// Заголовок окна
frm.Text = strCaption;
// Содержимое текстовой метки
frm.lblMessage.Text = strMessage;
// Отобразить диалоговое окно в модальном режиме,
// а после его закрытия вернуть выбор пользователя
// вызывающему коду
return frm.ShowDialog();// Выполняем диалоговое окно
// и возвращаем статус нажатой кнопки
}
}
}
Класс MyDialogBox нужно объявить с модификатором public, чтобы он был виден в клиентских сборках. Объявление метода Show() в классе MyDialogBox как статического позволяет его вызывать без создания экземпляра этого класса в клиенте. Через аргументы метода Show() можно задавать заголовок диалогового окна и содержимое текстовой метки lblMessage, как это принято в библиотечном классе System.Windows.Forms.MessageBox.
Теперь осталось собрать все вместе и испытать на тестовом приложении, но прежде нужно откомпилировать проект UserControls и убедиться, что нет синтаксических ошибок.
Выполните команду меню оболочки Build/Build UserControls, чтобы откомпилировать проект нашей библиотеки пользовательских элементов управления
Добавьте к решению MySolution новый проект с именем Test типа Windows Application. Для этого выполните команду меню оболочки File/Add/New Project и заполните окно мастера примерно так
В панели Solution Explorer выделите узел проекта Test и назначьте его стартовым командой Project/Set as StartUp Project меню оболочки или аналогичной командой контекстного меню
Выделите форму Form1 и настройте ее свойства как показано в таблице| Свойство | Значение | Пояснения |
|---|---|---|
Text |
Кнопка со звуком | Заголовок тестового окна |
StartPosition |
CenterScreen |
Открывать в центре экрана |
Поместите из панели Toolbox на верхнюю часть формы экземпляр textBox1 текстового поля TextBox
Поместите из панели Toolbox на середину формы разработанную нами кнопку BeepButton и настройте ее в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
|
true |
Подстраиваться под содержимое |
Text |
Звукнуть | Надпись |
Location |
100; 100 | Позиционировали |
Для выделенного экземпляра кнопки BeepButton переведите панель Properties оболочки в режим Events и в поле события Click задайте имя обработчика ButtonOnClick
Заполните обработчик кодом вызова нашего диалогового окна MyDialogBox такprivate void ButtonOnClick(object sender, EventArgs e)
{
string msg;
if (textBox1.Text.Trim().Length == 0)
msg = "Щелкнули на кнопке";
else
msg = textBox1.Text.Trim();
// Запускаем наше диалоговое окно статическим методом
MyDialogBox.Show(msg, this.Text);
}
Добавьте в начало файла Form1.cs строку кода подключения пространства имен нашей библиотеки элементовusing MyCompany.UserControls;
для того, чтобы иметь возможность написать сокращенное имя класса MyDialogBox
Выполните приложение и получите примерно такой результат
В производном от кнопки классе BeepButton мы только переопределили виртуальный метод OnClick(), в который встроили вызов звукового сигнала. В вызывающем коде тестового приложения, использующем экземпляр класса расширения кнопки, мы подписались на событие Click, чтобы щелчок на кнопке запускал самодельное диалоговое окно.
Но мы этот метод перехватили в нашем расширении, и если не предусмотреть передачу вызова base.OnClick(e) дальше по цепочке наследования в класс Control, то событие Click инициировано не будет и наш зарегистрированный обработчик ButtonOnClick() никогда не сработает. А значит и наше диалоговое окно вызываться не будет (попробуйте закоментировать вызов base.OnClick(e) в файле BeepButton.cs ).
Таким образом, в
Если в
Обратите внимание, что при формировании интерфейса диалогового окна мы не расставляли по форме кнопку и текстовую метку. Но за счет применения библиотечного компонента FlowLayoutPanel эти элементы на этапе выполнения позиционируются автоматически.
В этом упражнении мы построим новое расширение библиотечной кнопки с возможностью проигрывания мелодий. Наша кнопка при щелчке на ней будет воспроизводить не простой сигнал, как в предыдущем упражнении, а полноценный wav -файл. Усовершенствование библиотечной кнопки мы получим за счет добавления новых свойств, обеспечивающих кнопку музыкальностью.
Закройте все окна редактора оболочки, выполнив команду Window/Close All Documents, чтобы они не мешали при выполнении нового упражнения
В панели Solution Explorer к узлу UserControls добавьте новый компонент с именем SoundButton
Откорректируйте заготовку файла SoundButton.cs следующим образомusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Media; // Для проигрывателя wav-файлов SoundPlayer
// и системного звука SystemSounds
namespace MyCompany.UserControls
{
public partial class SoundButton : Button
{
public SoundButton()
{
InitializeComponent();
}
public SoundButton(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
Добавим в класс музыкальной кнопки два свойства WaveFile и WaveStream, через которые будем указывать в клиенте кнопки загружаемый файл для проигрывания.
Добавьте в конец класса SoundButton файла SoundButton.cs следующий код, определяющий свойства загрузки звука// Создаем объект-проигрыватель как
// поле класса для управления звуком
SoundPlayer soundPlayer = new SoundPlayer();
// Свойство подключения звука к проигрывателю из файла
public string WaveFile
{
get
{
// Что загружено для проигрывания
return soundPlayer.SoundLocation;
}
set
{
// Подключить файл с диска или URL
soundPlayer.SoundLocation = value;
}
}
// Свойство подключения звука к проигрывателю из потока
public System.IO.Stream WaveStream
{
get
{
// Вернуть ссылку на загруженный в поток файл
return soundPlayer.Stream;
}
set
{
// Подключить файл к потоку
soundPlayer.Stream = value;
// Загрузить асинхронно (в фоновом режиме)
soundPlayer.LoadAsync();
}
}
В классе расширения кнопки мы создали объект класса SoundPlayer как поле, чтобы он был виден в членах этого класса - в свойствах и
Как и в случае с кнопкой BeepButton,
Добавьте в конец класса SoundButton файла SoundButton.cs следующий код, переопределяющий виртуальный метод диспетчеризации OnClick() класса Control и запускающий проигрывание звука// Переопределяем виртуальный метод класса Control,
// чтобы начать проигрывание по щелчку на кнопке
protected override void OnClick(EventArgs e)
{
// Если имя экземпляра кнопки - "Web",
// то вместо адресации к удаленному звуку
// в Internet проигрываем стандартный звуковой
// файл локального компьютера, встроенный
// в класс SystemSounds библиотеки .NET Framework
if (this.Name.ToUpper() == "Web".ToUpper())
{
SystemSounds.Asterisk.Play();
return;
}
// Загрузить асинхронно - в фоновом режиме.
// Поток для кнопки Stream открывается
// один раз в конструкторе клиента
if (this.Name.ToUpper() != "Stream".ToUpper())
soundPlayer.LoadAsync();
// Если загрузка закончена, то начинаем проигрывание
if (soundPlayer.IsLoadCompleted)
soundPlayer.Play();
else
MessageBox.Show("Проигрыватель пока не готов!");
// Подняться до класса Control,
// чтобы сгенерировать событие Click.
// Но в этом упражнении не нужно,
// поскольку не планируем создавать обработчики
// (хотя можно и оставить незакомментированной)
// base.OnClick(e);
}
Перекомпилируйте библиотеку пользовательских элементов вызовом команды Все! Музыкальная кнопка готова. Теперь осталось упаковать ее экземпляры в блок, жестко подключающий проигрываемые файлы, а также поместить его в тестирующее приложение.
Оформим компоновочный блок кнопок в виде .
Выделите в панели Solution Explorer узел UserControls и добавьте к проекту новый файл с именем SoundButtonBox.cs командой контекстного меню Add/User Control
В режиме редактирования SoundButtonBox.cs[Design] перейдите в панель инструментов Toolbox и двойным щелчком на компоненте FlowLayoutPanel свитка Containers создайте экземпляр компоновочной панели
Настройте компоновочную панель согласно таблице свойств| Свойство | Значение | Пояснения |
|---|---|---|
Name |
flow |
Имя экземпляра компоновочной панели |
|
true |
Подстраиваться под содержимое |
FlowDirection |
TopDown |
Размещать сверху вниз |
WrapContents |
false |
Не переносить дочерние элементы в следующий столбец |
Dock |
Fill |
Распахнуть на всю клиентскую область родителя (формы) |
Location |
0; 0 | Позиционировать в клиентской области родителя |
В режиме редактирования SoundButtonBox.cs[Design] выделите экземпляр flow, перейдите в панель инструментов Toolbox и двойным щелчком на компоненте SoundButton добавьте в коллекцию объекта flow четыре экземпляра музыкальной кнопкиВ силу настроек компоновочной панели, которая является родителем кнопок, они расположатся в столбец друг за другом в порядке их создания.
В раскрывающемся списке верхней части панели Properties выберите элемент SoundButtonBox, после чего он будет выделен в визуальном редакторе оболочки
Настройте класс SoundButtonBox согласно таблице свойств| Свойство | Значение | Пояснения |
|---|---|---|
|
true |
Подстраиваться под содержимое |
AutoSizeMode |
GrowAndShrink |
Подстраиваться под содержимое (расширяться и сжиматься) |
После этих действий вид компонента SoundButtonBox в визуальном конструкторе должен стать таким

Далее мы должны настроить все четыре экземпляра нашей музыкальной кнопки-компонента. Некоторые свойства, которые не требуют предварительных действий для их вычисления, можно задать через панель свойств оболочки Properties (но можно задать и непосредственно в конструкторе класса). Но те свойства, которые нуждаются в некоторых вычислительных операциях, зададим в конструкторе класса SoundButtonBox.
Откройте на редактирование файл SoundButtonBox.cs в режиме [Design] и, выделяя последовательно экземпляры кнопки, настройте их свойства в соответствии с таблицами свойств, приведенными ниже| Свойство | Значение | Пояснения |
|---|---|---|
Name |
soundButton1 |
Имя экземпляра кнопки (сгенерировано автоматически оболочкой) |
|
true |
Подстраиваться под надпись |
Anchor |
None |
Выравнивать по центру родителя |
Text |
Файл tada. в системном каталоге |
Надпись на кнопке |
Margin.All |
8 | Наружный отступ по контуру кнопки |
WaveFile |
Пусто | Будет определен в коде конструктора класса для загрузки звука, расположенного в системном каталоге |
| Свойство | Значение | Пояснения |
|---|---|---|
Name |
soundButton2 |
Имя экземпляра кнопки (сгенерировано автоматически оболочкой) |
|
true |
Подстраиваться под надпись |
Anchor |
None |
Выравнивать по центру родителя |
Text |
Файл SOUND827. в каталоге исполняемой сборки |
Надпись на кнопке |
Margin.All |
8 | Наружный отступ по контуру кнопки |
WaveFile |
Пусто | Будет определен в коде конструктора класса для загрузки звука, расположенного в файле |
| Свойство | Значение | Пояснения |
|---|---|---|
Name |
Stream | Имя экземпляра кнопки. Помечаем, чтобы распознать в OnClick() и не открывать уже открытый в конструкторе поток |
|
true |
Подстраиваться под надпись |
Anchor |
None |
Выравнивать по центру родителя |
Text |
Файл Trumpet1. в ресурсе исполняемой сборки |
Надпись на кнопке |
Margin.All |
8 | Наружный отступ по контуру кнопки |
WaveStream |
Пусто | Будет определен в коде конструктора класса для загрузки звука, расположенного в |
| Свойство | Значение | Пояснения |
|---|---|---|
Name |
Web | Имя экземпляра кнопки. Помечаем, чтобы распознать в OnClick() и не обращаться в Web (считаем, что Internet пока что не подключен) |
|
true |
Подстраиваться под надпись |
Anchor |
None |
Выравнивать по центру родителя |
Text |
Файл azlinsnd., загружаемый из Web по URL (отключено) |
Надпись на кнопке |
Margin.All |
8 | Наружный отступ по контуру кнопки |
WaveFile |
http://www.oaklandzoo.org/atoz/azlinsnd.wav | Подключаем для загрузки из Web, но в OnClick() распознаем по имени нажатой кнопки и заменяем другим звуком, встроенным в класс SystemSounds библиотеки .NET Framework |
Интерфейсная часть блока кнопок после их настройки должна выглядеть так
Добавьте в конец конструктора класса SoundButtonBox одноименного файла C# код, чтобы класс стал такимusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
namespace MyCompany.UserControls
{
public partial class SoundButtonBox : UserControl
{
public SoundButtonBox()
{
InitializeComponent();
// Подключить первую кнопку к звуковому файлу
soundButton1.WaveFile = System.IO.Path.Combine( // Склеить два пути
Environment.GetEnvironmentVariable("windir"), // Каталог системы
@"Media\tada.wav");
// Подключить вторую кнопку к звуковому файлу
soundButton2.WaveFile = "SOUND827.WAV";
// Подключить третью кнопку к звуковому файлу из файла ресурсов сборки
Stream.WaveStream = this.GetType().Assembly.GetManifestResourceStream(
"MyCompany.UserControls.Trumpet1.wav");
}
}
}
Ниже приведен код, формируемый оболочкой при настройке компонента SoundButtonBox в визуальном режиме
namespace MyCompany.UserControls
{
partial class SoundButtonBox
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true
if managed resources should be disposed;
otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.flow = new System.Windows.Forms.FlowLayoutPanel();
this.soundButton1 = new MyCompany.UserControls.SoundButton
(this.components);
this.soundButton2 = new MyCompany.UserControls.SoundButton
(this.components);
this.Stream = new MyCompany.UserControls.SoundButton
(this.components);
this.Web = new MyCompany.UserControls.SoundButton
(this.components);
this.flow.SuspendLayout();
this.SuspendLayout();
//
// flow
//
this.flow.AutoSize = true;
this.flow.Controls.Add(this.soundButton1);
this.flow.Controls.Add(this.soundButton2);
this.flow.Controls.Add(this.Stream);
this.flow.Controls.Add(this.Web);
this.flow.Dock = System.Windows.Forms.DockStyle.Fill;
this.flow.FlowDirection =
System.Windows.Forms.FlowDirection.TopDown;
this.flow.Location = new System.Drawing.Point(0, 0);
this.flow.Name = "flow";
this.flow.Size = new System.Drawing.Size(440, 172);
this.flow.TabIndex = 0;
this.flow.WrapContents = false;
//
// soundButton1
//
this.soundButton1.Anchor = System.Windows.Forms.AnchorStyles.None;
this.soundButton1.AutoSize = true;
this.soundButton1.Location = new System.Drawing.Point(88, 8);
this.soundButton1.Margin = new System.Windows.Forms.Padding(8);
this.soundButton1.Name = "soundButton1";
this.soundButton1.Size = new System.Drawing.Size(264, 27);
this.soundButton1.TabIndex = 0;
this.soundButton1.Text = "Файл tada.wav в системном каталоге";
this.soundButton1.UseVisualStyleBackColor = true;
this.soundButton1.WaveFile = "";
this.soundButton1.WaveStream = null;
//
// soundButton2
//
this.soundButton2.Anchor = System.Windows.Forms.AnchorStyles.None;
this.soundButton2.AutoSize = true;
this.soundButton2.Location = new System.Drawing.Point(28, 51);
this.soundButton2.Margin = new System.Windows.Forms.Padding(8);
this.soundButton2.Name = "soundButton2";
this.soundButton2.Size = new System.Drawing.Size(384, 27);
this.soundButton2.TabIndex = 1;
this.soundButton2.Text = "Файл SOUND827.WAV
в каталоге исполняемой сборки";
this.soundButton2.UseVisualStyleBackColor = true;
this.soundButton2.WaveStream = null;
//
// Stream
//
this.Stream.Anchor = System.Windows.Forms.AnchorStyles.None;
this.Stream.AutoSize = true;
this.Stream.Location = new System.Drawing.Point(41, 94);
this.Stream.Margin = new System.Windows.Forms.Padding(8);
this.Stream.Name = "Stream";
this.Stream.Size = new System.Drawing.Size(358, 27);
this.Stream.TabIndex = 2;
this.Stream.Text = "Файл Trumpet1.wav
в ресурсе исполняемой сборки";
this.Stream.UseVisualStyleBackColor = true;
this.Stream.WaveFile = "";
this.Stream.WaveStream = null;
//
// Web
//
this.Web.Anchor = System.Windows.Forms.AnchorStyles.None;
this.Web.AutoSize = true;
this.Web.Location = new System.Drawing.Point(8, 137);
this.Web.Margin = new System.Windows.Forms.Padding(8);
this.Web.Name = "Web";
this.Web.Size = new System.Drawing.Size(424, 27);
this.Web.TabIndex = 3;
this.Web.Text = "Файл azlinsnd.wav,
загружаемый из Web по URL (отключено)";
this.Web.UseVisualStyleBackColor = true;
this.Web.WaveFile =
"http://www.oaklandzoo.org/atoz/azlinsnd.wav";
this.Web.WaveStream = null;
//
// SoundButtonBox
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSize = true;
this.AutoSizeMode =
System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.Controls.Add(this.flow);
this.Name = "SoundButtonBox";
this.Size = new System.Drawing.Size(440, 172);
this.flow.ResumeLayout(false);
this.flow.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.FlowLayoutPanel flow;
private SoundButton soundButton1;
private SoundButton soundButton2;
private SoundButton Stream;
private SoundButton Web;
}
}
Присоединим адресуемые звуки к проекту нашей библиотеки компонентов UserControls.
Первый звук: Первый звук tada.wav хранится в системном каталоге Windows и путь к нему мы формируем с использованием библиотечного класса Environment.
Второй звук: Второй звук SOUND827.WAV (или любой другой) код компонента ищет в каталоге размещения самой сборки. Для того, чтобы он там оказался, выполните следующее:
В панели Solution Explorer выделите узел проекта UserControls и командой оболочки Project/Add Existing Item загрузите нужный файл в каталог проекта. Файл будет скопирован в каталог размещения проекта (звук SOUND827.WAV находится в прилагаемом к данной работе каталоге Source )Выполненным действием мы скопировали файл звука пока-что в каталог размещения проекта. А он должен находиться в каталоге размещения самой сборки кода компонента. Нам нужно настроить оболочку для работы с этим файлом так, чтобы при компиляции кода компонента она автоматически копировала звук в каталог размещения сборки самого компонента.
В панели Solution Explorer вызовите контекстное меню для скопированного файла звука SOUND827.WAV и выполнить команду Properties. Установите опцию Copy to Output Directory в значение Copy if newer (копировать при обновлении файла звука) или Copy always (копировать всегда). Теперь оболочка сама при компиляции проекта разместит файл в одном со сборкой компонента каталогеТретий звук: Свойство WaveStream используется для проигрывания звука, присоединенного к исполняемому файлу как внедренный ресурс. Файл Trumpet1.wav внедряемого как ресурс звука нужно добавить к проекту, а затем настроить его свойства так, чтобы компилятор поместил его в сборку компонента.
В панели Solution Explorer выделите узел проекта UserControls и командой Add/Add Existing Item контекстного меню загрузите файл Trumpet1.wav в каталог проекта из прилагаемого к работе каталога Source
Через команду Properties контекстного меню, вызванного в панели Solution Explorer для файла Trumpet1.wav, установите в таблице свойств Properties поле Build Action в значение Embedded Resource (назначить внедренным ресурсом)Теперь звук Trumpet1.wav на этапе компиляции будет внедрен в исполнимую сборку и готовое приложение может далее выполняться отдельно от первоисточника звука. Так удобно поступать с файлами небольших размеров. Для загрузки звука из ресурса нужно указывать полный путь к ресурсу, включая пространство имен проекта.
Четвертый звук: Специальных подготовительных действий не требует, поскольку извлекается из библиотеки .NET Framework.
Откомпилируйте проект с компонентами, выполнив команду Rebuild контекстного меню узла UserControls в панели Solution Explorer
Запустите проводник Windows Explorer и убедитесь, что размер сборки UserControls существенно увеличился за счет внедренного звука Trumpet1.wav.
Добавьте к решению командой File/Add/New Project новый проект типа оконного приложения ( Windows Application ) с именем SoundButtonsTest и назначьте его стартовым командой Project/Set as StartUp Project меню оболочки (или аналогичной командой контекстного меню)
Поместите на форму из панели Toolbox экземпляр компонента SoundButtonBox и выровняйте его по размеру формы
Подключите динамики, запустите приложение и испытайте его работуОтметьте, что разные исполнимые сборки используют одну и ту же библиотеку компонентов UserControls.dll при условии, что эта библиотека находится в том же каталоге, что и исполнимая сборка. При добавлении к нашему решению MySolution, содержащему проект библиотеки компонентов UserControls, нового проекта SoundButtonsTest исполнимого приложения оболочка автоматически копирует эту библиотеку в каталог, где будет находиться исполнимая сборка. При любом изменении библиотеки компонентов при ее компиляции оболочка обновляет файл библиотеки во всех каталогах, где находятся файлы исполнимых сборок.
Вид стандартного элемента управления можно изменить, переопределив его виртуальные методы OnPaint() и OnResize(). Предпочтимые размеры элемента управления устанавливаются в переопределении наследуемого метода GetPreferredSize(). Для прорисовки элементов управления можно использовать класс System.Windows.Forms.ControlPaint. Системная палитра, перья и кисти находятся в трех классах пространства имен SystemDrawing:
Пространство имен System.Windows.Forms также содержит цвета в классах
В этом упражнении мы создадим кнопку непрямоугольной формы, которая будет отрисовываться по пользовательскому алгоритму. На этом примере будет показано, как создавать пользовательские элементы управления непрямоугольной формы.
В панели Solution Explorer выделите узел UserControls и добавьте к проекту библиотеки новый компонент командой Project/Add Component с именем CircleButton
В заголовке класса CircleButton файла CircleButton.cs замените базовый класс Component на Button
Добавьте в начало файла CircleButton.cs инструкции подключения пространств именusing System.Drawing; using System.Windows.Forms;
Добавьте в класс CircleButton using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
namespace MyCompany.UserControls
{
public partial class CircleButton : Button
{
public CircleButton()
{
InitializeComponent();
Init();
}
public CircleButton(IContainer container)
{
container.Add(this);
InitializeComponent();
Init();
}
// Начальные настройки для выполнения в конструкторе
private void Init()
{
// Извещаем операционную систему, что будем сами
// заботиться об отрисовке компонента
this.SetStyle(ControlStyles.UserPaint, true);
// Игнорировать системное сообщение WM_ERASEBKGND
// очистки окна, чтобы уменьшить нежелательное мерцание
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
// Наружный отступ по контуру кнопки
this.Margin = new Padding(this.Font.Height);
// Подстраиваться под размер надписи
this.AutoSize = true;
}
}
}
Установка флага ControlStyles.UserPaint извещает операционную систему, что отрисовка окна компонента будет выполняться в переопределении его метода OnPaint().
Каждый раз, когда изображение кнопки на экране требует перерисовки, операционная система вызывает метод OnPaint(). Перед его выполнением необходима информация о размерах перерисуемой области. Такую информацию возвращает автоматически вызываемый метод GetPreferredSize(). Переопределим его в соответствии с нашими требованиями.
Добавьте в класс CircleButton код // Вычисляем размер описывающего квадрата кнопки
// в зависимости от размера надписи на ней
public override Size GetPreferredSize(Size proposedSize)
{
// Получаем ссылку на контекст графического устройства
Graphics gr = CreateGraphics();
// Измеряем размер описывающего текст прямоугольника
SizeF box = gr.MeasureString(this.Text, this.Font);
// Вычисляем диагональ текстового блока надписи
int diagonal = (int)Math.Sqrt(Math.Pow(box.Width, 2)
+ Math.Pow(box.Height, 2));
// Возвращаем объект с размерами описывающего квадрата кнопки
return new Size(diagonal, diagonal);
}
Метод GetPreferredSize() вызывается диспетчером размещения для определения предпочтительных размеров компонента, когда значение его свойства AutoSize=true (мы его установили в методе Init() ).
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace Test
{
// Класс круглой кнопки
class CircleButton : Button
{
public CircleButton()
{
// Сами отрисуем вместо операционной системы
this.SetStyle(ControlStyles.UserPaint, true);
// Отключить от системы автоматическую отрисовку
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
// Наружный отступ по контуру кнопки
this.Margin = new Padding(this.Font.Height);
}
// Возвращается предпочтимый размер кнопки в зависимости от текста
public override Size GetPreferredSize(Size proposedSize)
{
// Создаем временный контекст устройства
Graphics gr = CreateGraphics();
// Вычисляем размер текстового блока надписи
SizeF sizeF = gr.MeasureString(this.Text, this.Font);
// Освобождаем контекст
gr.Dispose();
// Теорема Пифагора
int radius = (int)Math.Sqrt(
Math.Pow(sizeF.Width / 2, 2)
+ Math.Pow(sizeF.Height / 2, 2));
// Возвращаем диаметр
return new Size(2 * radius, 2 * radius);
}
// Вычисление контура кнопки при изменении ее размеров
protected override void OnResize(EventArgs args)
{
// Рисуем кнопку круглой, используя класс Region
GraphicsPath path = new GraphicsPath();// Объект для региона
path.AddEllipse(this.ClientRectangle);// Преобразовать в элипс
this.Region = new Region(path);
// Вызываем предка для инициации OnPaint()
base.OnResize(args);
}
// Отрисовка круглой кнопки
protected override void OnPaint(PaintEventArgs args)
{
Graphics gr = args.Graphics;// Адресовались к контексту
// Качество рисования - сглаживать контуры
gr.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = this.ClientRectangle;
// Флаг попадания курсора внутрь кнопки при нажатии
bool bPressed = this.Capture
((Control.MouseButtons MouseButtons.Left) != 0)
this.ClientRectangle.
Contains(this.PointToClient(Control.MousePosition));
// Отрисовка внутренней части кнопки
// При нажатии она становится темнее
GraphicsPath path = new GraphicsPath();
path.AddEllipse(rect);
PathGradientBrush gradient = new PathGradientBrush(path);
int k = bPressed ? 2 : 1;// Для нажатой увеличим вес градиента
gradient.CenterPoint = new PointF(
k * (rect.Left + rect.Right) / 3,
k * (rect.Top + rect.Bottom) / 3);
gradient.CenterColor = bPressed ? Color.Blue : Color.White;
gradient.SurroundColors = new Color[] { Color.SkyBlue };
gr.FillRectangle(gradient, rect);
// Отрисовка контура кнопки, утолщенного для активных кнопок
Brush brush = new LinearGradientBrush(
rect,
Color.FromArgb(0, 0, 255),
Color.FromArgb(0, 0, 128),
LinearGradientMode.ForwardDiagonal);
Pen pen = new Pen(
brush,
(this.IsDefault ? 6 : 2) * gr.DpiX / 72);
gr.DrawEllipse(pen, rect);
// Отображение надписи в центре прямоугольника кнопки
// Для неактивной кнопки цвет надписи должен быть бледно-серым
StringFormat strFormat = new StringFormat();
// Определение точки привязки в центре текстового блока
strFormat.Alignment =
strFormat.LineAlignment =
StringAlignment.Center;
brush = this.Enabled ? SystemBrushes.WindowText // Для активной
: SystemBrushes.GrayText;// Для неактивной
gr.DrawString(this.Text, this.Font, brush, rect, strFormat);
// Отображение пунктира вокруг текста, если кнопка имеет фокус
if (this.Focused)
{
SizeF sizeF = gr.MeasureString(
this.Text,
this.Font,
PointF.Empty,
StringFormat.GenericTypographic);
pen = new Pen(this.ForeColor);// Текущий цвет
pen.DashStyle = DashStyle.Dash;// Пунктир
gr.DrawRectangle(
pen,
rect.Left + rect.Width / 2 - sizeF.Width / 2,
rect.Top + rect.Height / 2 - sizeF.Height / 2,
sizeF.Width,
sizeF.Height);
}
if (this.Paint != null)// Если есть хоть одна подписка
this.Paint(this, args);// Инициируем событие
}
new public event PaintEventHandler Paint;// Объявляем новое событие
}
// Форма приложения
class MyClass : Form
{
public MyClass()
{
this.Text = "Круглые кнопки";
this.Font = new Font("Times New Roman", 18);
this.MaximizeBox = false;
// Подстраиваться под кнопки
this.AutoSize = true;
this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// Панель для размещения элементов интерфейса в столбец
FlowLayoutPanel flow = new FlowLayoutPanel();
flow.Parent = this;
flow.AutoSize = true;
flow.FlowDirection = FlowDirection.TopDown;// В столбец
// Встроенная компоновочная панель для верха
FlowLayoutPanel flowTop = new FlowLayoutPanel();
flowTop.Parent = flow;
flowTop.FlowDirection = FlowDirection.LeftToRight;// По умолчанию
flowTop.AutoSize = true;
// Выравнивание в столбец по центру родительской панели
flowTop.Anchor = AnchorStyles.None;
// Помещаем в верхнюю компоновочную панель метку
Label lbl = new Label();
lbl.Parent = flowTop;
lbl.AutoSize = true;
lbl.Text = "Введите текст: ";
lbl.Anchor = AnchorStyles.None;// Выравнивание по центру строки
// Помещаем в верхнюю компоновочную панель поле ввода
TextBox txtBox = new TextBox();
txtBox.Parent = flowTop;
txtBox.Width = 8 * this.Font.Height;// Длина поля ввода
txtBox.TabStop = false;// Отключить клавишу Tab
// Встроенная компоновочная панель для низа
FlowLayoutPanel flowBottom = new FlowLayoutPanel();
flowBottom.Parent = flow;
flowBottom.FlowDirection =
FlowDirection.LeftToRight;// По умолчанию
flowBottom.AutoSize = true;
// Выравнивание в столбец по центру родительской панели
flowBottom.Anchor = AnchorStyles.None;
// Помещаем в нижнюю компоновочную панель кнопки
CircleButton btn = new CircleButton();
btn.Parent = flowBottom;
btn.AutoSize = true;// Подстраиваемая
btn.Text = "Кнопка 1";
btn.Anchor = AnchorStyles.None;// Выравнивать по горизонтали
btn.Click += btn_Click;
btn = new CircleButton();
btn.Parent = flowBottom;
btn.AutoSize = true;// Подстраиваемая
btn.Text = "Кнопка 2";
btn.Anchor = AnchorStyles.None;// Выравнивать по горизонтали
btn.Click += btn_Click;
btn = new CircleButton();
btn.Parent = flowBottom;
btn.AutoSize = true;// Подстраиваемая
btn.Text = "Кнопка 3";
btn.Anchor = AnchorStyles.None;// Выравнивать по горизонтали
btn.Click += btn_Click;
}
void btn_Click(object sender, EventArgs e)
{
//this.Close();// Завершить приложение
}
}
// Запуск
class Program
{
static void Main()
{
Application.EnableVisualStyles();
// Создали форму и запустили цикл сообщений Windows
Application.Run(new MyClass());
}
}
}
Результат выполнения будет таким

Поскольку в классе расширения кнопки мы переопределили виртуальный метод OnPaint() и не предусмотрели его вызов для базового класса, то событие Paint никогда инициировано не будет. Потому что оно вызывается в методе OnPaint() именно базового класса, который мы перехватили. Чтобы не потерять событие Paint, мы его заново объявили в классе расширения, а в
Всегда пользовательский элемент управления лучше создавать на основе уже существующих, преимущественно библиотечных, элементов управления, которые хорошо отлажены и работают привычным образом. В предыдущих примерах мы усовершенствовали библиотечный класс Button, приспосабливая его к нашим задачам путем расширения в другом классе.
Теперь мы разработаем свой элемент управления на основе нескольких библиотечных элементов. Применим прием многоэтапного последовательного
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Test
{
//******************************************************
// Кнопка, генерирующая множество событий Click
// При нажатии и удержании на ней курсора мыши
class ClickmaticButton : Button
{
// Определяем поля класса
// Создаем таймер
Timer timer = new Timer();
// Задержка срабатывания кнопки Button после нажатия мыши
int iDelay = 250 * (1 + SystemInformation.KeyboardDelay);
// Скорость для срабатывания таймера, генерирующего события повторных
// нажатий кнопки компонента при удержании нажатой левой кнопки мыши
int iSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
// Конструктор
public ClickmaticButton()
{
timer.Tick += TimerOnTick;// Подписались на тики
}
// Перехватываем нажатие левой кнопки мыши и запускаем таймер
// для генерации событий Click кнопки Button
protected override void OnMouseDown(MouseEventArgs args)
{
base.OnMouseDown(args);
// Если нажата левая кнопка мыши
if ((args.Button MouseButtons.Left) != 0)
{
timer.Interval = iDelay;// Задержка до первого тика
timer.Start();// Включили таймер
}
}
// Отработка тиков
void TimerOnTick(object sender, EventArgs e)
{
// Программно генерируем повторное
// нажатие Button при каждом тике
this.OnClick(EventArgs.Empty);
// Меняем скорость таймера
timer.Interval = iSpeed;
}
// Контролируем, находится ли нажатый курсор мыши
// над кнопкой и управляем приостановкой таймера
protected override void OnMouseMove(MouseEventArgs args)
{
base.OnMouseMove(args);
// Приостанавливаем таймер
timer.Enabled = this.Capture
this.ClientRectangle.Contains(args.Location);
}
// При отпускании кнопки таймер останавливаем
protected override void OnMouseUp(MouseEventArgs args)
{
base.OnMouseUp(args);
timer.Stop();
}
}
//******************************************************
// Рисование на генерирующей событие Click кнопке
// изображения стрелки
class ArrowButton : ClickmaticButton
{
// Поле для хранения ориентации кнопки
// По умолчанию острый угол треугольника направлен вправо
ScrollButton scrollButton = ScrollButton.Right;
// Конструктор
public ArrowButton()
{
// Задание стиля кнопки как не получающей фокус ввода
this.SetStyle(ControlStyles.Selectable, false);
}
// Свойство для поддержки переменной перечисления
public ScrollButton ScrollButton
{
get { return scrollButton; }
set
{
scrollButton = value;
this.Invalidate();// Перерисовать
}
}
// Переопределяем метод отрисовки кнопки
protected override void OnPaint(PaintEventArgs args)
{
Point point = PointToClient(Control.MousePosition);
bool mouseInButton = this.Capture
this.ClientRectangle.Contains(point);
ButtonState state = mouseInButton ?
ButtonState.Pushed : ButtonState.Normal;
state = !this.Enabled ?
ButtonState.Inactive : state;
Graphics gr = args.Graphics;
// Рисуем кнопку с треугольником внутри
ControlPaint.DrawScrollButton(
gr,
this.ClientRectangle,
scrollButton,
state);
}
protected override void OnMouseCaptureChanged(EventArgs args)
{
base.OnMouseCaptureChanged(args);
this.Invalidate();// Перерисовать
}
}
//******************************************************
// Самодельный элемент управления - поле с кнопками
public class NumericScan : UserControl
{
// Объявили событие
public event EventHandler ValueChanged;
// Объявили ссылки-поля для видимости объектов
TextBox txtBox;
ArrowButton btn1, btn2;
// Объявили базовые закрытые поля для открытых свойств
int iDecimalPlaces = 0;
decimal mValue = 0;
decimal mIncrement = 1;
decimal mMinimum = 0;
decimal mMaximum = 100;
// Доступные свойства-обертки полей
public int DecimalPlaces
{
get { return iDecimalPlaces; }
set { iDecimalPlaces = value; }
}
public decimal Value
{
get { return mValue; }
set { txtBox.Text = ValueToText(mValue = value); }
}
public decimal Increment
{
get { return mIncrement; }
set { mIncrement = value; }
}
public decimal Minimum
{
get { return mMinimum; }
set
{
// Заодно отслеживаем диапазон Value
if (Value < (mMinimum = value))
Value = mMinimum;
}
}
public decimal Maximum
{
get { return mMaximum; }
set
{
// Заодно отслеживаем диапазон Value
if (Value > (mMaximum = value))
Value = mMaximum;
}
}
// Конструктор
public NumericScan()
{
txtBox = new TextBox();
txtBox.Parent = this;
// Текст у правой границы поля
txtBox.TextAlign = HorizontalAlignment.Right;
txtBox.Text = ValueToText(mValue);// Инициализируем
txtBox.TextChanged += TextBoxOnTextChanged;
// При вводе Enter обновить Value элемента
txtBox.KeyDown += TextBoxOnKeyDown;
// Не принимать лишние символы
txtBox.KeyPress += new KeyPressEventHandler(txtBox_KeyPress);
btn1 = new ArrowButton();
btn1.Parent = this;
// Ориентация треугольничка влево
btn1.ScrollButton = ScrollButton.Left;
btn1.Click += ButtonOnClick;
btn2 = new ArrowButton();
btn2.Parent = this;
// Ориентация треугольничка вправо
btn2.ScrollButton = ScrollButton.Right;// По умолчанию
btn2.Click += ButtonOnClick;
}
string ValueToText(decimal mValue)
{
// Возвращаем с форматированием
return mValue.ToString("F" + iDecimalPlaces);
}
// Обработчик события изменения содержимого текстового поля
void TextBoxOnTextChanged(object sender, EventArgs args)
{
try
{
// Преобразуем текст в число
mValue = Decimal.Parse(txtBox.Text);
}
catch { }// На исключения не реагируем
}
// Обрабатывает каждую клавишу, но ждет клавишу Enter
void TextBoxOnKeyDown(object sender, KeyEventArgs args)
{
switch (args.KeyCode)
{
// При нажатии Enter генерируем наше событие ValueChanged
case Keys.Enter:
OnValueChanged(EventArgs.Empty);
break;
}
// Обнаруживаем цифру
isNumeric =
// Основная клавиатура
args.KeyCode >= Keys.D0 args.KeyCode <= Keys.D9
// Дополнительная клавиатура
|| args.KeyCode >= Keys.NumPad0
args.KeyCode <= Keys.NumPad9
// Клавиша BackSpace
|| args.KeyCode == Keys.Back;
}
// Отфильтровываем лишние символы из поля ввода
bool isNumeric;// Видимое поле
void txtBox_KeyPress(object sender, KeyPressEventArgs args)
{
if (isNumeric != true)// Пропускаем только цифры
args.Handled = true;// Не принимаем в TextBox
}
// Срабатывает автоматически и устанавливает
// размеры компонента this.Width, this.Height
public override Size GetPreferredSize(Size proposedSize)
{
const float weight = 1.2f;// Вес ширины
// Высота компонента
int height = txtBox.PreferredHeight +
SystemInformation.HorizontalScrollBarHeight;
// Устанавливаем размеры компонента
return new Size((int)(weight * height), height);
}
// Срабатывает автоматически, позиционирует
// кнопки и устанавливает их размеры
protected override void OnResize(EventArgs args)
{
base.OnResize(args);
txtBox.Height = txtBox.PreferredHeight;
txtBox.Width = this.Width;// Текстовое поле по ширине компонента
btn1.Location = new Point(0, txtBox.Height);// Слева снизу
btn2.Location = new Point
(this.Width / 2, txtBox.Height);// Середина низ
// Приравниваем значения полей структур
btn1.Size = btn2.Size = new Size(txtBox.Width / 2,
this.Height - txtBox.Height);
}
// Когда элемент управления уходит из фокуса ввода, надо
// проверить введенное и сгенерировать событие ValueChanged
protected override void OnLeave(EventArgs args)
{
base.OnLeave(args);
this.OnValueChanged(EventArgs.Empty);// Аргумент не используется
}
// Изменения величины пользователем по щелчкам мыши
void ButtonOnClick(object sender, EventArgs e)
{
// Если не было, то дать фокус тексовому полю
if (!txtBox.Focused)
txtBox.Focus();
// Повышаем полномочия ссылки
ArrowButton btn = sender as ArrowButton;
// Пробная величина для изменения значения на шаге
decimal mNewValue = Value;
// Выявляем кнопку и проверяем выход за границу
if (btn == btn1)
if ((mNewValue -= Increment) < Minimum)
return;
if (btn == btn2)
if ((mNewValue += Increment) > Maximum)
return;
// Принимаем изменение и вызываем обработчик,
// который сгенерирует событие ValueChanged
Value = mNewValue;
OnValueChanged(EventArgs.Empty);
}
// Ввели свою дополнительную функцию
// Изменение величины прямым вводом в текстовое поле
protected virtual void OnValueChanged(EventArgs args)
{
// Последовательно проверяем выход величины за левую и правую
// границы, и если выходит за границу, то обрезаем по границе
Value = Math.Min(Maximum, Value);
Value = Math.Max(Minimum, Value);
//Value = Decimal.Round(Value, DecimalPlaces);
// Генерируем событие
if (ValueChanged != null)
ValueChanged(this, args);
}
}
//******************************************************
// Тест для проверки нашего элемента управления
class MyClass : Form
{
// Внутренние поля для видимости в методах
Label lbl1, lbl2;// Заполняются обработчиком события ValueChanged
NumericScan numscan1, numscan2;// Ссылки на компоненты
public MyClass()
{
this.Text = "Тест пользовательского компонента";
// Размеры формы
this.Width = (int)(1.3f * this.Width);
this.Height = (int)(.8f * this.Height);
FlowLayoutPanel flow = new FlowLayoutPanel();
flow.Parent = this;// Привязали к форме
// Размещение по столбцам
flow.FlowDirection = FlowDirection.TopDown;
// Внутреннее обрамление в пикселах
flow.Padding = new Padding(20);
// Растянули по форме
flow.Dock = DockStyle.Fill;
numscan1 = new NumericScan();
numscan1.Parent = flow;
numscan1.AutoSize = true;
// Элементы между собой выравнивать по центру
numscan1.Anchor = AnchorStyles.None;
numscan1.ValueChanged += NumericScanOnValueChanged;
lbl1 = new Label();
lbl1.Parent = flow;
lbl1.AutoSize = true;
// Элементы между собой выравнивать по центру
lbl1.Anchor = AnchorStyles.None;
// Пустая текстовая метка для отступа элементов
(new Label()).Parent = flow;
numscan2 = new NumericScan();
numscan2.Parent = flow;
numscan2.AutoSize = true;
// Элементы между собой выравнивать по центру
numscan2.Anchor = AnchorStyles.None;
numscan2.ValueChanged += NumericScanOnValueChanged;
lbl2 = new Label();
lbl2.Parent = flow;
lbl2.AutoSize = true;
// Элементы между собой выравнивать по центру
lbl2.Anchor = AnchorStyles.None;
// Сами принудительно выполняем обработчик
// для начальной инициализации текстовых меток
NumericScanOnValueChanged(numscan1, EventArgs.Empty);
NumericScanOnValueChanged(numscan2, EventArgs.Empty);
}
void NumericScanOnValueChanged(object sender, EventArgs args)
{
NumericScan ob = sender as NumericScan;// Приводим ссылку
if (ob == numscan1)
lbl1.Text = "Первый: " + numscan1.Value;
else if (ob == numscan2)
lbl2.Text = "Второй: " + numscan2.Value;
}
}
//******************************************************
// Запуск
class Program
{
static void Main()
{
Application.EnableVisualStyles();
// Создали форму и запустили цикл сообщений Windows
Application.Run(new MyClass());
}
}
}
Элементы управления являются посредниками между приложением и пользователем. Они составляют так называемый графический интерфейс пользователя (GUI - ). Их задача состоит в отображении информации для пользователя, обеспечении удобного ввода и генерации событий, отрабатывающих предпочтения пользователя.
Как правило, библиотечных элементов управления достаточно для решения большинства задач, но иногда программисту может потребоваться нестандартный элемент управления. В таком случае он может разработать свой элемент управления с нуля или расширить стандартный элемент. Такой элемент управления называют пользовательским.
Механизм наследования C# предоставляет широкие возможности модификации существующих элементов управления на уровне
Построим кнопку, при щелчке на которой компьютер выдаст звуковой сигнал. Для этого используем библиотечный элемент управления Button в качестве базового класса и расширим его для решения своей задачи.
Создайте новый пустой проект командой File/New/Project и заполните окно мастера так
В панели Solution Explorer выделите корневой узел проекта UserControls и через контекстное меню выполните команду Properties
Откройте вкладку Application свойств оболочки и настройте поля Default namespace и Output type как показано на рисунке
В панели Solution Explorer вызовите для корневого узла проекта UserControls контекстное меню и выполните команду Add/Component
Добавьте к проекту новый файл компонента с именем BeepButton.cs
В рабочей области конструктора форм щелкните на ссылке click here to switch to code view или исполните команду контекстного меню View Code, чтобы перейти в режим редактирования кода
В панели Solution Explorer для узла References, где уже должна находиться ссылка на библиотечную сборку System, выполните команду Add Reference
Удерживая клавишу Ctrl через окно Add Reference выделите и добавьте к проекту ссылки на библиотечные сборки System.Drawing и System.Windows.Forms
Убедитесь, что теперь узел References проекта стал выглядеть так
Перейдите в режим редактирования файла BeepButton.cs и откорректируйте его такusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Media; // Для звука
namespace MyCompany.UserControls
{
public partial class BeepButton : Button
{
public BeepButton()
{
InitializeComponent();
}
public BeepButton(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
Класс Button является наследником класса Component, поэтому мы спокойно применяем его в качестве базового, продолжая цепочку наследования.

Большинство библиотечных классов, в которых определены события, имеют соответствующие виртуальные методы диспетчеризации с приставкой On. Эти методы можно переопределять в классах-наследниках, дополняя нужным кодом. Воспользуемся этой возможностью в нашем классе расширения кнопки, упаковав переопределение метода в отдельную часть класса BeepButton.
Дополните конец файла BeepButton.cs следующим кодомnamespace MyCompany.UserControls
{
partial class BeepButton
{
// Переопределяем виртуальный метод диспетчеризации
// класса Control (задействуется наследуемый метод
// диспетчеризации класса кнопки)
protected override void OnClick(EventArgs e)
{
// Проиграть системный звук
SystemSounds.Exclamation.Play();// Сочный через внешние динамики
//SystemSounds.Beep.Play(); // Простой через внешние динамики
//System.Console.Beep(); // Простой через внутренний динамик
// Подняться до базового класса Control,
// чтобы сгенерировать его событие Click и
// запустить наш будущий обработчик ButtonOnClick
base.OnClick(e);
}
}
}
В
protected virtual void OnClick(EventArgs args)
{
//...
// Если хоть один обработчик зарегистрирован
if (Click != null)
Click(this, args);
//...
}
Испытание кнопки BeepButton мы проведем позднее, разработав для этого тестовую исполнимую Windows -сборку.
В данном разделе мы спроектируем собственное простое диалоговое окно сообщений с именем MyDialogBox, которое и будет вызываться из обработчика события Click нашей кнопки, если на него подпишется клиент компонента BeepButton.
Создание диалогового окна будет состоять из трех шагов:
В панели Solution Explorer выделите узел проекта UserControls и командой Add/Windows Form добавьте к компоненту файл формы с именем MyDialogForm.cs
Удалите из узла References лишние добавленные мастером ссылки на библиотечные сборки System.Data и System.Xml
Удалите соответствующие инструкции using подключения пространств имен в начале файла MyDialogForm.cs
Настройте форму MyDialogForm в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
StartPosition |
CenterScreen |
Открывать в центре экрана |
FormBorderStyle |
FixedDialog |
Запретить пользователю менять размер |
MaximizeBox |
false |
Отключить системную кнопку развертывания |
MinimizeBox |
false |
Отключить системную кнопку свертывания |
ShowInTaskbar |
false |
Запретить появление в панели задач |
|
true |
Подстраиваться под содержимое |
AutoSizeMode |
GrowAndShrink |
Подстраиваться под содержимое (расширяться и сжиматься) |
Text |
Пусто | Заголовок окна |
Из свитка Containers панели инструментов Toolbox поместите на форму компоновочную панель FlowLayoutPanel и настройте ее в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
Name |
flow |
Имя компоновочной панели |
|
true |
Подстраиваться под содержимое |
FlowDirection |
TopDown |
Размещать сверху вниз |
WrapContents |
false |
Не переносить дочерние элементы в следующий столбец |
Padding.All |
8 | |
Dock |
Fill |
Распахнуть на всю клиентскую область родителя (формы) |
Эта панель будет следить за размещением своих дочерних элементов в соответствии с настройками.
Поместите внутрь экземпляра компоновочной панели flow из свитка Common Controls текстовую метку Label и настройте ее свойства в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
Name |
lblMessage |
Имя текстовой метки |
|
true |
Подстраиваться под содержимое |
Anchor |
None |
Расположить посредине |
Margin.All |
8 | Отступ по наружному периметру текстовой метки |
Modifiers |
Public |
Публичный доступ для клиентов формы (для класса MyDialogBox ) |
Для того, чтобы объект lblMessage был доступен из метода класса MyDialogBox, при его создании мы сделали его общедоступным.
Поместите внутрь компоновочной панели flow из свитка Common Controls кнопку Button и настройте ее свойства в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
Name |
btnShowDialog |
Имя кнопки |
|
true |
Подстраиваться под содержимое |
Anchor |
None |
Расположить посредине |
Margin.All |
8 | Отступ по наружному периметру кнопки |
Text |
OK |
Надпись |
DialogResult |
OK |
Назначить статус "кнопки OK" в родительской форме |
Свойству дочерних кнопок формы DialogResult можно присвоить любое значение из перечисления System.Windows.Forms.DialogResult ( Abort, Cancel, Ignore, No, None, OK, Retry, Yes ). Оно и будет возвращено функцией ShowDialog() формы, если пользователь выберет именно эту кнопку.
Мы создали интерфейсный класс диалогового окна. Теперь нужно создать
В панели Solution Explorer выделите узел проекта UserControls и, выполнив команду меню Project/Add Class, добавьте к проекту новый класс с именем MyDialogBox
Заполните файл MyDialogBox.cs следующим кодомusing System;
using System.Windows.Forms;
namespace MyCompany.UserControls
{
// Наше диалоговое окно сообщения
public class MyDialogBox
{
public static DialogResult Show(string strMessage, string strCaption)
{
// Создаем диалоговое окно
MyDialogForm frm = new MyDialogForm();
// Заголовок окна
frm.Text = strCaption;
// Содержимое текстовой метки
frm.lblMessage.Text = strMessage;
// Отобразить диалоговое окно в модальном режиме,
// а после его закрытия вернуть выбор пользователя
// вызывающему коду
return frm.ShowDialog();// Выполняем диалоговое окно
// и возвращаем статус нажатой кнопки
}
}
}
Класс MyDialogBox нужно объявить с модификатором public, чтобы он был виден в клиентских сборках. Объявление метода Show() в классе MyDialogBox как статического позволяет его вызывать без создания экземпляра этого класса в клиенте. Через аргументы метода Show() можно задавать заголовок диалогового окна и содержимое текстовой метки lblMessage, как это принято в библиотечном классе System.Windows.Forms.MessageBox.
Теперь осталось собрать все вместе и испытать на тестовом приложении, но прежде нужно откомпилировать проект UserControls и убедиться, что нет синтаксических ошибок.
Выполните команду меню оболочки Build/Build UserControls, чтобы откомпилировать проект нашей библиотеки пользовательских элементов управления
Добавьте к решению MySolution новый проект с именем Test типа Windows Application. Для этого выполните команду меню оболочки File/Add/New Project и заполните окно мастера примерно так
В панели Solution Explorer выделите узел проекта Test и назначьте его стартовым командой Project/Set as StartUp Project меню оболочки или аналогичной командой контекстного меню
Выделите форму Form1 и настройте ее свойства как показано в таблице| Свойство | Значение | Пояснения |
|---|---|---|
Text |
Кнопка со звуком | Заголовок тестового окна |
StartPosition |
CenterScreen |
Открывать в центре экрана |
Поместите из панели Toolbox на верхнюю часть формы экземпляр textBox1 текстового поля TextBox
Поместите из панели Toolbox на середину формы разработанную нами кнопку BeepButton и настройте ее в соответствии с таблицей| Свойство | Значение | Пояснения |
|---|---|---|
|
true |
Подстраиваться под содержимое |
Text |
Звукнуть | Надпись |
Location |
100; 100 | Позиционировали |
Для выделенного экземпляра кнопки BeepButton переведите панель Properties оболочки в режим Events и в поле события Click задайте имя обработчика ButtonOnClick
Заполните обработчик кодом вызова нашего диалогового окна MyDialogBox такprivate void ButtonOnClick(object sender, EventArgs e)
{
string msg;
if (textBox1.Text.Trim().Length == 0)
msg = "Щелкнули на кнопке";
else
msg = textBox1.Text.Trim();
// Запускаем наше диалоговое окно статическим методом
MyDialogBox.Show(msg, this.Text);
}
Добавьте в начало файла Form1.cs строку кода подключения пространства имен нашей библиотеки элементовusing MyCompany.UserControls;
для того, чтобы иметь возможность написать сокращенное имя класса MyDialogBox
Выполните приложение и получите примерно такой результат
В производном от кнопки классе BeepButton мы только переопределили виртуальный метод OnClick(), в который встроили вызов звукового сигнала. В вызывающем коде тестового приложения, использующем экземпляр класса расширения кнопки, мы подписались на событие Click, чтобы щелчок на кнопке запускал самодельное диалоговое окно.
Но мы этот метод перехватили в нашем расширении, и если не предусмотреть передачу вызова base.OnClick(e) дальше по цепочке наследования в класс Control, то событие Click инициировано не будет и наш зарегистрированный обработчик ButtonOnClick() никогда не сработает. А значит и наше диалоговое окно вызываться не будет (попробуйте закоментировать вызов base.OnClick(e) в файле BeepButton.cs ).
Таким образом, в
Если в
Обратите внимание, что при формировании интерфейса диалогового окна мы не расставляли по форме кнопку и текстовую метку. Но за счет применения библиотечного компонента FlowLayoutPanel эти элементы на этапе выполнения позиционируются автоматически.
В этом упражнении мы построим новое расширение библиотечной кнопки с возможностью проигрывания мелодий. Наша кнопка при щелчке на ней будет воспроизводить не простой сигнал, как в предыдущем упражнении, а полноценный wav -файл. Усовершенствование библиотечной кнопки мы получим за счет добавления новых свойств, обеспечивающих кнопку музыкальностью.
Закройте все окна редактора оболочки, выполнив команду Window/Close All Documents, чтобы они не мешали при выполнении нового упражнения
В панели Solution Explorer к узлу UserControls добавьте новый компонент с именем SoundButton
Откорректируйте заготовку файла SoundButton.cs следующим образомusing System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Media; // Для проигрывателя wav-файлов SoundPlayer
// и системного звука SystemSounds
namespace MyCompany.UserControls
{
public partial class SoundButton : Button
{
public SoundButton()
{
InitializeComponent();
}
public SoundButton(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
Добавим в класс музыкальной кнопки два свойства WaveFile и WaveStream, через которые будем указывать в клиенте кнопки загружаемый файл для проигрывания.
Добавьте в конец класса SoundButton файла SoundButton.cs следующий код, определяющий свойства загрузки звука// Создаем объект-проигрыватель как
// поле класса для управления звуком
SoundPlayer soundPlayer = new SoundPlayer();
// Свойство подключения звука к проигрывателю из файла
public string WaveFile
{
get
{
// Что загружено для проигрывания
return soundPlayer.SoundLocation;
}
set
{
// Подключить файл с диска или URL
soundPlayer.SoundLocation = value;
}
}
// Свойство подключения звука к проигрывателю из потока
public System.IO.Stream WaveStream
{
get
{
// Вернуть ссылку на загруженный в поток файл
return soundPlayer.Stream;
}
set
{
// Подключить файл к потоку
soundPlayer.Stream = value;
// Загрузить асинхронно (в фоновом режиме)
soundPlayer.LoadAsync();
}
}
В классе расширения кнопки мы создали объект класса SoundPlayer как поле, чтобы он был виден в членах этого класса - в свойствах и
Как и в случае с кнопкой BeepButton,
Добавьте в конец класса SoundButton файла SoundButton.cs следующий код, переопределяющий виртуальный метод диспетчеризации OnClick() класса Control и запускающий проигрывание звука// Переопределяем виртуальный метод класса Control,
// чтобы начать проигрывание по щелчку на кнопке
protected override void OnClick(EventArgs e)
{
// Если имя экземпляра кнопки - "Web",
// то вместо адресации к удаленному звуку
// в Internet проигрываем стандартный звуковой
// файл локального компьютера, встроенный
// в класс SystemSounds библиотеки .NET Framework
if (this.Name.ToUpper() == "Web".ToUpper())
{
SystemSounds.Asterisk.Play();
return;
}
// Загрузить асинхронно - в фоновом режиме.
// Поток для кнопки Stream открывается
// один раз в конструкторе клиента
if (this.Name.ToUpper() != "Stream".ToUpper())
soundPlayer.LoadAsync();
// Если загрузка закончена, то начинаем проигрывание
if (soundPlayer.IsLoadCompleted)
soundPlayer.Play();
else
MessageBox.Show("Проигрыватель пока не готов!");
// Подняться до класса Control,
// чтобы сгенерировать событие Click.
// Но в этом упражнении не нужно,
// поскольку не планируем создавать обработчики
// (хотя можно и оставить незакомментированной)
// base.OnClick(e);
}
Перекомпилируйте библиотеку пользовательских элементов вызовом команды Все! Музыкальная кнопка готова. Теперь осталось упаковать ее экземпляры в блок, жестко подключающий проигрываемые файлы, а также поместить его в тестирующее приложение.
Оформим компоновочный блок кнопок в виде .
Выделите в панели Solution Explorer узел UserControls и добавьте к проекту новый файл с именем SoundButtonBox.cs командой контекстного меню Add/User Control
В режиме редактирования SoundButtonBox.cs[Design] перейдите в панель инструментов Toolbox и двойным щелчком на компоненте FlowLayoutPanel свитка Containers создайте экземпляр компоновочной панели
Настройте компоновочную панель согласно таблице свойств| Свойство | Значение | Пояснения |
|---|---|---|
Name |
flow |
Имя экземпляра компоновочной панели |
|
true |
Подстраиваться под содержимое |
FlowDirection |
TopDown |
Размещать сверху вниз |
WrapContents |
false |
Не переносить дочерние элементы в следующий столбец |
Dock |
Fill |
Распахнуть на всю клиентскую область родителя (формы) |
Location |
0; 0 | Позиционировать в клиентской области родителя |
В режиме редактирования SoundButtonBox.cs[Design] выделите экземпляр flow, перейдите в панель инструментов Toolbox и двойным щелчком на компоненте SoundButton добавьте в коллекцию объекта flow четыре экземпляра музыкальной кнопкиВ силу настроек компоновочной панели, которая является родителем кнопок, они расположатся в столбец друг за другом в порядке их создания.
В раскрывающемся списке верхней части панели Properties выберите элемент SoundButtonBox, после чего он будет выделен в визуальном редакторе оболочки
Настройте класс SoundButtonBox согласно таблице свойств| Свойство | Значение | Пояснения |
|---|---|---|
|
true |
Подстраиваться под содержимое |
AutoSizeMode |
GrowAndShrink |
Подстраиваться под содержимое (расширяться и сжиматься) |
После этих действий вид компонента SoundButtonBox в визуальном конструкторе должен стать таким

Далее мы должны настроить все четыре экземпляра нашей музыкальной кнопки-компонента. Некоторые свойства, которые не требуют предварительных действий для их вычисления, можно задать через панель свойств оболочки Properties (но можно задать и непосредственно в конструкторе класса). Но те свойства, которые нуждаются в некоторых вычислительных операциях, зададим в конструкторе класса SoundButtonBox.
Откройте на редактирование файл SoundButtonBox.cs в режиме [Design] и, выделяя последовательно экземпляры кнопки, настройте их свойства в соответствии с таблицами свойств, приведенными ниже| Свойство | Значение | Пояснения |
|---|---|---|
Name |
soundButton1 |
Имя экземпляра кнопки (сгенерировано автоматически оболочкой) |
|
true |
Подстраиваться под надпись |
Anchor |
None |
Выравнивать по центру родителя |
Text |
Файл tada. в системном каталоге |
Надпись на кнопке |
Margin.All |
8 | Наружный отступ по контуру кнопки |
WaveFile |
Пусто | Будет определен в коде конструктора класса для загрузки звука, расположенного в системном каталоге |
| Свойство | Значение | Пояснения |
|---|---|---|
Name |
soundButton2 |
Имя экземпляра кнопки (сгенерировано автоматически оболочкой) |
|
true |
Подстраиваться под надпись |
Anchor |
None |
Выравнивать по центру родителя |
Text |
Файл SOUND827. в каталоге исполняемой сборки |
Надпись на кнопке |
Margin.All |
8 | Наружный отступ по контуру кнопки |
WaveFile |
Пусто | Будет определен в коде конструктора класса для загрузки звука, расположенного в файле |
| Свойство | Значение | Пояснения |
|---|---|---|
Name |
Stream | Имя экземпляра кнопки. Помечаем, чтобы распознать в OnClick() и не открывать уже открытый в конструкторе поток |
|
true |
Подстраиваться под надпись |
Anchor |
None |
Выравнивать по центру родителя |
Text |
Файл Trumpet1. в ресурсе исполняемой сборки |
Надпись на кнопке |
Margin.All |
8 | Наружный отступ по контуру кнопки |
WaveStream |
Пусто | Будет определен в коде конструктора класса для загрузки звука, расположенного в |
| Свойство | Значение | Пояснения |
|---|---|---|
Name |
Web | Имя экземпляра кнопки. Помечаем, чтобы распознать в OnClick() и не обращаться в Web (считаем, что Internet пока что не подключен) |
|
true |
Подстраиваться под надпись |
Anchor |
None |
Выравнивать по центру родителя |
Text |
Файл azlinsnd., загружаемый из Web по URL (отключено) |
Надпись на кнопке |
Margin.All |
8 | Наружный отступ по контуру кнопки |
WaveFile |
http://www.oaklandzoo.org/atoz/azlinsnd.wav | Подключаем для загрузки из Web, но в OnClick() распознаем по имени нажатой кнопки и заменяем другим звуком, встроенным в класс SystemSounds библиотеки .NET Framework |
Интерфейсная часть блока кнопок после их настройки должна выглядеть так

Добавьте в конец конструктора класса SoundButtonBox одноименного файла C# код, чтобы класс стал такимusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
namespace MyCompany.UserControls
{
public partial class SoundButtonBox : UserControl
{
public SoundButtonBox()
{
InitializeComponent();
// Подключить первую кнопку к звуковому файлу
soundButton1.WaveFile = System.IO.Path.Combine( // Склеить два пути
Environment.GetEnvironmentVariable("windir"), // Каталог системы
@"Media\tada.wav");
// Подключить вторую кнопку к звуковому файлу
soundButton2.WaveFile = "SOUND827.WAV";
// Подключить третью кнопку к звуковому файлу из файла ресурсов сборки
Stream.WaveStream = this.GetType().Assembly.GetManifestResourceStream(
"MyCompany.UserControls.Trumpet1.wav");
}
}
}
Ниже приведен код, формируемый оболочкой при настройке компонента SoundButtonBox в визуальном режиме
namespace MyCompany.UserControls
{
partial class SoundButtonBox
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true
if managed resources should be disposed;
otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.flow = new System.Windows.Forms.FlowLayoutPanel();
this.soundButton1 = new MyCompany.UserControls.SoundButton
(this.components);
this.soundButton2 = new MyCompany.UserControls.SoundButton
(this.components);
this.Stream = new MyCompany.UserControls.SoundButton
(this.components);
this.Web = new MyCompany.UserControls.SoundButton
(this.components);
this.flow.SuspendLayout();
this.SuspendLayout();
//
// flow
//
this.flow.AutoSize = true;
this.flow.Controls.Add(this.soundButton1);
this.flow.Controls.Add(this.soundButton2);
this.flow.Controls.Add(this.Stream);
this.flow.Controls.Add(this.Web);
this.flow.Dock = System.Windows.Forms.DockStyle.Fill;
this.flow.FlowDirection =
System.Windows.Forms.FlowDirection.TopDown;
this.flow.Location = new System.Drawing.Point(0, 0);
this.flow.Name = "flow";
this.flow.Size = new System.Drawing.Size(440, 172);
this.flow.TabIndex = 0;
this.flow.WrapContents = false;
//
// soundButton1
//
this.soundButton1.Anchor = System.Windows.Forms.AnchorStyles.None;
this.soundButton1.AutoSize = true;
this.soundButton1.Location = new System.Drawing.Point(88, 8);
this.soundButton1.Margin = new System.Windows.Forms.Padding(8);
this.soundButton1.Name = "soundButton1";
this.soundButton1.Size = new System.Drawing.Size(264, 27);
this.soundButton1.TabIndex = 0;
this.soundButton1.Text = "Файл tada.wav в системном каталоге";
this.soundButton1.UseVisualStyleBackColor = true;
this.soundButton1.WaveFile = "";
this.soundButton1.WaveStream = null;
//
// soundButton2
//
this.soundButton2.Anchor = System.Windows.Forms.AnchorStyles.None;
this.soundButton2.AutoSize = true;
this.soundButton2.Location = new System.Drawing.Point(28, 51);
this.soundButton2.Margin = new System.Windows.Forms.Padding(8);
this.soundButton2.Name = "soundButton2";
this.soundButton2.Size = new System.Drawing.Size(384, 27);
this.soundButton2.TabIndex = 1;
this.soundButton2.Text = "Файл SOUND827.WAV
в каталоге исполняемой сборки";
this.soundButton2.UseVisualStyleBackColor = true;
this.soundButton2.WaveStream = null;
//
// Stream
//
this.Stream.Anchor = System.Windows.Forms.AnchorStyles.None;
this.Stream.AutoSize = true;
this.Stream.Location = new System.Drawing.Point(41, 94);
this.Stream.Margin = new System.Windows.Forms.Padding(8);
this.Stream.Name = "Stream";
this.Stream.Size = new System.Drawing.Size(358, 27);
this.Stream.TabIndex = 2;
this.Stream.Text = "Файл Trumpet1.wav
в ресурсе исполняемой сборки";
this.Stream.UseVisualStyleBackColor = true;
this.Stream.WaveFile = "";
this.Stream.WaveStream = null;
//
// Web
//
this.Web.Anchor = System.Windows.Forms.AnchorStyles.None;
this.Web.AutoSize = true;
this.Web.Location = new System.Drawing.Point(8, 137);
this.Web.Margin = new System.Windows.Forms.Padding(8);
this.Web.Name = "Web";
this.Web.Size = new System.Drawing.Size(424, 27);
this.Web.TabIndex = 3;
this.Web.Text = "Файл azlinsnd.wav,
загружаемый из Web по URL (отключено)";
this.Web.UseVisualStyleBackColor = true;
this.Web.WaveFile =
"http://www.oaklandzoo.org/atoz/azlinsnd.wav";
this.Web.WaveStream = null;
//
// SoundButtonBox
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSize = true;
this.AutoSizeMode =
System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.Controls.Add(this.flow);
this.Name = "SoundButtonBox";
this.Size = new System.Drawing.Size(440, 172);
this.flow.ResumeLayout(false);
this.flow.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.FlowLayoutPanel flow;
private SoundButton soundButton1;
private SoundButton soundButton2;
private SoundButton Stream;
private SoundButton Web;
}
}
Присоединим адресуемые звуки к проекту нашей библиотеки компонентов UserControls.
Первый звук: Первый звук tada.wav хранится в системном каталоге Windows и путь к нему мы формируем с использованием библиотечного класса Environment.
Второй звук: Второй звук SOUND827.WAV (или любой другой) код компонента ищет в каталоге размещения самой сборки. Для того, чтобы он там оказался, выполните следующее:
В панели Solution Explorer выделите узел проекта UserControls и командой оболочки Project/Add Existing Item загрузите нужный файл в каталог проекта. Файл будет скопирован в каталог размещения проекта (звук SOUND827.WAV находится в прилагаемом к данной работе каталоге Source )Выполненным действием мы скопировали файл звука пока-что в каталог размещения проекта. А он должен находиться в каталоге размещения самой сборки кода компонента. Нам нужно настроить оболочку для работы с этим файлом так, чтобы при компиляции кода компонента она автоматически копировала звук в каталог размещения сборки самого компонента.
В панели Solution Explorer вызовите контекстное меню для скопированного файла звука SOUND827.WAV и выполнить команду Properties. Установите опцию Copy to Output Directory в значение Copy if newer (копировать при обновлении файла звука) или Copy always (копировать всегда). Теперь оболочка сама при компиляции проекта разместит файл в одном со сборкой компонента каталогеТретий звук: Свойство WaveStream используется для проигрывания звука, присоединенного к исполняемому файлу как внедренный ресурс. Файл Trumpet1.wav внедряемого как ресурс звука нужно добавить к проекту, а затем настроить его свойства так, чтобы компилятор поместил его в сборку компонента.
В панели Solution Explorer выделите узел проекта UserControls и командой Add/Add Existing Item контекстного меню загрузите файл Trumpet1.wav в каталог проекта из прилагаемого к работе каталога Source
Через команду Properties контекстного меню, вызванного в панели Solution Explorer для файла Trumpet1.wav, установите в таблице свойств Properties поле Build Action в значение Embedded Resource (назначить внедренным ресурсом)Теперь звук Trumpet1.wav на этапе компиляции будет внедрен в исполнимую сборку и готовое приложение может далее выполняться отдельно от первоисточника звука. Так удобно поступать с файлами небольших размеров. Для загрузки звука из ресурса нужно указывать полный путь к ресурсу, включая пространство имен проекта.
Четвертый звук: Специальных подготовительных действий не требует, поскольку извлекается из библиотеки .NET Framework.
Откомпилируйте проект с компонентами, выполнив команду Rebuild контекстного меню узла UserControls в панели Solution Explorer
Запустите проводник Windows Explorer и убедитесь, что размер сборки UserControls существенно увеличился за счет внедренного звука Trumpet1.wav.
Добавьте к решению командой File/Add/New Project новый проект типа оконного приложения ( Windows Application ) с именем SoundButtonsTest и назначьте его стартовым командой Project/Set as StartUp Project меню оболочки (или аналогичной командой контекстного меню)
Поместите на форму из панели Toolbox экземпляр компонента SoundButtonBox и выровняйте его по размеру формы
Подключите динамики, запустите приложение и испытайте его работуОтметьте, что разные исполнимые сборки используют одну и ту же библиотеку компонентов UserControls.dll при условии, что эта библиотека находится в том же каталоге, что и исполнимая сборка. При добавлении к нашему решению MySolution, содержащему проект библиотеки компонентов UserControls, нового проекта SoundButtonsTest исполнимого приложения оболочка автоматически копирует эту библиотеку в каталог, где будет находиться исполнимая сборка. При любом изменении библиотеки компонентов при ее компиляции оболочка обновляет файл библиотеки во всех каталогах, где находятся файлы исполнимых сборок.
Вид стандартного элемента управления можно изменить, переопределив его виртуальные методы OnPaint() и OnResize(). Предпочтимые размеры элемента управления устанавливаются в переопределении наследуемого метода GetPreferredSize(). Для прорисовки элементов управления можно использовать класс System.Windows.Forms.ControlPaint. Системная палитра, перья и кисти находятся в трех классах пространства имен SystemDrawing:
Пространство имен System.Windows.Forms также содержит цвета в классах
В этом упражнении мы создадим кнопку непрямоугольной формы, которая будет отрисовываться по пользовательскому алгоритму. На этом примере будет показано, как создавать пользовательские элементы управления непрямоугольной формы.
В панели Solution Explorer выделите узел UserControls и добавьте к проекту библиотеки новый компонент командой Project/Add Component с именем CircleButton
В заголовке класса CircleButton файла CircleButton.cs замените базовый класс Component на Button
Добавьте в начало файла CircleButton.cs инструкции подключения пространств именusing System.Drawing; using System.Windows.Forms;
Добавьте в класс CircleButton using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
namespace MyCompany.UserControls
{
public partial class CircleButton : Button
{
public CircleButton()
{
InitializeComponent();
Init();
}
public CircleButton(IContainer container)
{
container.Add(this);
InitializeComponent();
Init();
}
// Начальные настройки для выполнения в конструкторе
private void Init()
{
// Извещаем операционную систему, что будем сами
// заботиться об отрисовке компонента
this.SetStyle(ControlStyles.UserPaint, true);
// Игнорировать системное сообщение WM_ERASEBKGND
// очистки окна, чтобы уменьшить нежелательное мерцание
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
// Наружный отступ по контуру кнопки
this.Margin = new Padding(this.Font.Height);
// Подстраиваться под размер надписи
this.AutoSize = true;
}
}
}
Установка флага ControlStyles.UserPaint извещает операционную систему, что отрисовка окна компонента будет выполняться в переопределении его метода OnPaint().
Каждый раз, когда изображение кнопки на экране требует перерисовки, операционная система вызывает метод OnPaint(). Перед его выполнением необходима информация о размерах перерисуемой области. Такую информацию возвращает автоматически вызываемый метод GetPreferredSize(). Переопределим его в соответствии с нашими требованиями.
Добавьте в класс CircleButton код // Вычисляем размер описывающего квадрата кнопки
// в зависимости от размера надписи на ней
public override Size GetPreferredSize(Size proposedSize)
{
// Получаем ссылку на контекст графического устройства
Graphics gr = CreateGraphics();
// Измеряем размер описывающего текст прямоугольника
SizeF box = gr.MeasureString(this.Text, this.Font);
// Вычисляем диагональ текстового блока надписи
int diagonal = (int)Math.Sqrt(Math.Pow(box.Width, 2)
+ Math.Pow(box.Height, 2));
// Возвращаем объект с размерами описывающего квадрата кнопки
return new Size(diagonal, diagonal);
}
Метод GetPreferredSize() вызывается диспетчером размещения для определения предпочтительных размеров компонента, когда значение его свойства AutoSize=true (мы его установили в методе Init() ).
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace Test
{
// Класс круглой кнопки
class CircleButton : Button
{
public CircleButton()
{
// Сами отрисуем вместо операционной системы
this.SetStyle(ControlStyles.UserPaint, true);
// Отключить от системы автоматическую отрисовку
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
// Наружный отступ по контуру кнопки
this.Margin = new Padding(this.Font.Height);
}
// Возвращается предпочтимый размер кнопки в зависимости от текста
public override Size GetPreferredSize(Size proposedSize)
{
// Создаем временный контекст устройства
Graphics gr = CreateGraphics();
// Вычисляем размер текстового блока надписи
SizeF sizeF = gr.MeasureString(this.Text, this.Font);
// Освобождаем контекст
gr.Dispose();
// Теорема Пифагора
int radius = (int)Math.Sqrt(
Math.Pow(sizeF.Width / 2, 2)
+ Math.Pow(sizeF.Height / 2, 2));
// Возвращаем диаметр
return new Size(2 * radius, 2 * radius);
}
// Вычисление контура кнопки при изменении ее размеров
protected override void OnResize(EventArgs args)
{
// Рисуем кнопку круглой, используя класс Region
GraphicsPath path = new GraphicsPath();// Объект для региона
path.AddEllipse(this.ClientRectangle);// Преобразовать в элипс
this.Region = new Region(path);
// Вызываем предка для инициации OnPaint()
base.OnResize(args);
}
// Отрисовка круглой кнопки
protected override void OnPaint(PaintEventArgs args)
{
Graphics gr = args.Graphics;// Адресовались к контексту
// Качество рисования - сглаживать контуры
gr.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = this.ClientRectangle;
// Флаг попадания курсора внутрь кнопки при нажатии
bool bPressed = this.Capture
((Control.MouseButtons MouseButtons.Left) != 0)
this.ClientRectangle.
Contains(this.PointToClient(Control.MousePosition));
// Отрисовка внутренней части кнопки
// При нажатии она становится темнее
GraphicsPath path = new GraphicsPath();
path.AddEllipse(rect);
PathGradientBrush gradient = new PathGradientBrush(path);
int k = bPressed ? 2 : 1;// Для нажатой увеличим вес градиента
gradient.CenterPoint = new PointF(
k * (rect.Left + rect.Right) / 3,
k * (rect.Top + rect.Bottom) / 3);
gradient.CenterColor = bPressed ? Color.Blue : Color.White;
gradient.SurroundColors = new Color[] { Color.SkyBlue };
gr.FillRectangle(gradient, rect);
// Отрисовка контура кнопки, утолщенного для активных кнопок
Brush brush = new LinearGradientBrush(
rect,
Color.FromArgb(0, 0, 255),
Color.FromArgb(0, 0, 128),
LinearGradientMode.ForwardDiagonal);
Pen pen = new Pen(
brush,
(this.IsDefault ? 6 : 2) * gr.DpiX / 72);
gr.DrawEllipse(pen, rect);
// Отображение надписи в центре прямоугольника кнопки
// Для неактивной кнопки цвет надписи должен быть бледно-серым
StringFormat strFormat = new StringFormat();
// Определение точки привязки в центре текстового блока
strFormat.Alignment =
strFormat.LineAlignment =
StringAlignment.Center;
brush = this.Enabled ? SystemBrushes.WindowText // Для активной
: SystemBrushes.GrayText;// Для неактивной
gr.DrawString(this.Text, this.Font, brush, rect, strFormat);
// Отображение пунктира вокруг текста, если кнопка имеет фокус
if (this.Focused)
{
SizeF sizeF = gr.MeasureString(
this.Text,
this.Font,
PointF.Empty,
StringFormat.GenericTypographic);
pen = new Pen(this.ForeColor);// Текущий цвет
pen.DashStyle = DashStyle.Dash;// Пунктир
gr.DrawRectangle(
pen,
rect.Left + rect.Width / 2 - sizeF.Width / 2,
rect.Top + rect.Height / 2 - sizeF.Height / 2,
sizeF.Width,
sizeF.Height);
}
if (this.Paint != null)// Если есть хоть одна подписка
this.Paint(this, args);// Инициируем событие
}
new public event PaintEventHandler Paint;// Объявляем новое событие
}
// Форма приложения
class MyClass : Form
{
public MyClass()
{
this.Text = "Круглые кнопки";
this.Font = new Font("Times New Roman", 18);
this.MaximizeBox = false;
// Подстраиваться под кнопки
this.AutoSize = true;
this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// Панель для размещения элементов интерфейса в столбец
FlowLayoutPanel flow = new FlowLayoutPanel();
flow.Parent = this;
flow.AutoSize = true;
flow.FlowDirection = FlowDirection.TopDown;// В столбец
// Встроенная компоновочная панель для верха
FlowLayoutPanel flowTop = new FlowLayoutPanel();
flowTop.Parent = flow;
flowTop.FlowDirection = FlowDirection.LeftToRight;// По умолчанию
flowTop.AutoSize = true;
// Выравнивание в столбец по центру родительской панели
flowTop.Anchor = AnchorStyles.None;
// Помещаем в верхнюю компоновочную панель метку
Label lbl = new Label();
lbl.Parent = flowTop;
lbl.AutoSize = true;
lbl.Text = "Введите текст: ";
lbl.Anchor = AnchorStyles.None;// Выравнивание по центру строки
// Помещаем в верхнюю компоновочную панель поле ввода
TextBox txtBox = new TextBox();
txtBox.Parent = flowTop;
txtBox.Width = 8 * this.Font.Height;// Длина поля ввода
txtBox.TabStop = false;// Отключить клавишу Tab
// Встроенная компоновочная панель для низа
FlowLayoutPanel flowBottom = new FlowLayoutPanel();
flowBottom.Parent = flow;
flowBottom.FlowDirection =
FlowDirection.LeftToRight;// По умолчанию
flowBottom.AutoSize = true;
// Выравнивание в столбец по центру родительской панели
flowBottom.Anchor = AnchorStyles.None;
// Помещаем в нижнюю компоновочную панель кнопки
CircleButton btn = new CircleButton();
btn.Parent = flowBottom;
btn.AutoSize = true;// Подстраиваемая
btn.Text = "Кнопка 1";
btn.Anchor = AnchorStyles.None;// Выравнивать по горизонтали
btn.Click += btn_Click;
btn = new CircleButton();
btn.Parent = flowBottom;
btn.AutoSize = true;// Подстраиваемая
btn.Text = "Кнопка 2";
btn.Anchor = AnchorStyles.None;// Выравнивать по горизонтали
btn.Click += btn_Click;
btn = new CircleButton();
btn.Parent = flowBottom;
btn.AutoSize = true;// Подстраиваемая
btn.Text = "Кнопка 3";
btn.Anchor = AnchorStyles.None;// Выравнивать по горизонтали
btn.Click += btn_Click;
}
void btn_Click(object sender, EventArgs e)
{
//this.Close();// Завершить приложение
}
}
// Запуск
class Program
{
static void Main()
{
Application.EnableVisualStyles();
// Создали форму и запустили цикл сообщений Windows
Application.Run(new MyClass());
}
}
}
Результат выполнения будет таким

Поскольку в классе расширения кнопки мы переопределили виртуальный метод OnPaint() и не предусмотрели его вызов для базового класса, то событие Paint никогда инициировано не будет. Потому что оно вызывается в методе OnPaint() именно базового класса, который мы перехватили. Чтобы не потерять событие Paint, мы его заново объявили в классе расширения, а в
Всегда пользовательский элемент управления лучше создавать на основе уже существующих, преимущественно библиотечных, элементов управления, которые хорошо отлажены и работают привычным образом. В предыдущих примерах мы усовершенствовали библиотечный класс Button, приспосабливая его к нашим задачам путем расширения в другом классе.
Теперь мы разработаем свой элемент управления на основе нескольких библиотечных элементов. Применим прием многоэтапного последовательного
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Test
{
//******************************************************
// Кнопка, генерирующая множество событий Click
// При нажатии и удержании на ней курсора мыши
class ClickmaticButton : Button
{
// Определяем поля класса
// Создаем таймер
Timer timer = new Timer();
// Задержка срабатывания кнопки Button после нажатия мыши
int iDelay = 250 * (1 + SystemInformation.KeyboardDelay);
// Скорость для срабатывания таймера, генерирующего события повторных
// нажатий кнопки компонента при удержании нажатой левой кнопки мыши
int iSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
// Конструктор
public ClickmaticButton()
{
timer.Tick += TimerOnTick;// Подписались на тики
}
// Перехватываем нажатие левой кнопки мыши и запускаем таймер
// для генерации событий Click кнопки Button
protected override void OnMouseDown(MouseEventArgs args)
{
base.OnMouseDown(args);
// Если нажата левая кнопка мыши
if ((args.Button MouseButtons.Left) != 0)
{
timer.Interval = iDelay;// Задержка до первого тика
timer.Start();// Включили таймер
}
}
// Отработка тиков
void TimerOnTick(object sender, EventArgs e)
{
// Программно генерируем повторное
// нажатие Button при каждом тике
this.OnClick(EventArgs.Empty);
// Меняем скорость таймера
timer.Interval = iSpeed;
}
// Контролируем, находится ли нажатый курсор мыши
// над кнопкой и управляем приостановкой таймера
protected override void OnMouseMove(MouseEventArgs args)
{
base.OnMouseMove(args);
// Приостанавливаем таймер
timer.Enabled = this.Capture
this.ClientRectangle.Contains(args.Location);
}
// При отпускании кнопки таймер останавливаем
protected override void OnMouseUp(MouseEventArgs args)
{
base.OnMouseUp(args);
timer.Stop();
}
}
//******************************************************
// Рисование на генерирующей событие Click кнопке
// изображения стрелки
class ArrowButton : ClickmaticButton
{
// Поле для хранения ориентации кнопки
// По умолчанию острый угол треугольника направлен вправо
ScrollButton scrollButton = ScrollButton.Right;
// Конструктор
public ArrowButton()
{
// Задание стиля кнопки как не получающей фокус ввода
this.SetStyle(ControlStyles.Selectable, false);
}
// Свойство для поддержки переменной перечисления
public ScrollButton ScrollButton
{
get { return scrollButton; }
set
{
scrollButton = value;
this.Invalidate();// Перерисовать
}
}
// Переопределяем метод отрисовки кнопки
protected override void OnPaint(PaintEventArgs args)
{
Point point = PointToClient(Control.MousePosition);
bool mouseInButton = this.Capture
this.ClientRectangle.Contains(point);
ButtonState state = mouseInButton ?
ButtonState.Pushed : ButtonState.Normal;
state = !this.Enabled ?
ButtonState.Inactive : state;
Graphics gr = args.Graphics;
// Рисуем кнопку с треугольником внутри
ControlPaint.DrawScrollButton(
gr,
this.ClientRectangle,
scrollButton,
state);
}
protected override void OnMouseCaptureChanged(EventArgs args)
{
base.OnMouseCaptureChanged(args);
this.Invalidate();// Перерисовать
}
}
//******************************************************
// Самодельный элемент управления - поле с кнопками
public class NumericScan : UserControl
{
// Объявили событие
public event EventHandler ValueChanged;
// Объявили ссылки-поля для видимости объектов
TextBox txtBox;
ArrowButton btn1, btn2;
// Объявили базовые закрытые поля для открытых свойств
int iDecimalPlaces = 0;
decimal mValue = 0;
decimal mIncrement = 1;
decimal mMinimum = 0;
decimal mMaximum = 100;
// Доступные свойства-обертки полей
public int DecimalPlaces
{
get { return iDecimalPlaces; }
set { iDecimalPlaces = value; }
}
public decimal Value
{
get { return mValue; }
set { txtBox.Text = ValueToText(mValue = value); }
}
public decimal Increment
{
get { return mIncrement; }
set { mIncrement = value; }
}
public decimal Minimum
{
get { return mMinimum; }
set
{
// Заодно отслеживаем диапазон Value
if (Value < (mMinimum = value))
Value = mMinimum;
}
}
public decimal Maximum
{
get { return mMaximum; }
set
{
// Заодно отслеживаем диапазон Value
if (Value > (mMaximum = value))
Value = mMaximum;
}
}
// Конструктор
public NumericScan()
{
txtBox = new TextBox();
txtBox.Parent = this;
// Текст у правой границы поля
txtBox.TextAlign = HorizontalAlignment.Right;
txtBox.Text = ValueToText(mValue);// Инициализируем
txtBox.TextChanged += TextBoxOnTextChanged;
// При вводе Enter обновить Value элемента
txtBox.KeyDown += TextBoxOnKeyDown;
// Не принимать лишние символы
txtBox.KeyPress += new KeyPressEventHandler(txtBox_KeyPress);
btn1 = new ArrowButton();
btn1.Parent = this;
// Ориентация треугольничка влево
btn1.ScrollButton = ScrollButton.Left;
btn1.Click += ButtonOnClick;
btn2 = new ArrowButton();
btn2.Parent = this;
// Ориентация треугольничка вправо
btn2.ScrollButton = ScrollButton.Right;// По умолчанию
btn2.Click += ButtonOnClick;
}
string ValueToText(decimal mValue)
{
// Возвращаем с форматированием
return mValue.ToString("F" + iDecimalPlaces);
}
// Обработчик события изменения содержимого текстового поля
void TextBoxOnTextChanged(object sender, EventArgs args)
{
try
{
// Преобразуем текст в число
mValue = Decimal.Parse(txtBox.Text);
}
catch { }// На исключения не реагируем
}
// Обрабатывает каждую клавишу, но ждет клавишу Enter
void TextBoxOnKeyDown(object sender, KeyEventArgs args)
{
switch (args.KeyCode)
{
// При нажатии Enter генерируем наше событие ValueChanged
case Keys.Enter:
OnValueChanged(EventArgs.Empty);
break;
}
// Обнаруживаем цифру
isNumeric =
// Основная клавиатура
args.KeyCode >= Keys.D0 args.KeyCode <= Keys.D9
// Дополнительная клавиатура
|| args.KeyCode >= Keys.NumPad0
args.KeyCode <= Keys.NumPad9
// Клавиша BackSpace
|| args.KeyCode == Keys.Back;
}
// Отфильтровываем лишние символы из поля ввода
bool isNumeric;// Видимое поле
void txtBox_KeyPress(object sender, KeyPressEventArgs args)
{
if (isNumeric != true)// Пропускаем только цифры
args.Handled = true;// Не принимаем в TextBox
}
// Срабатывает автоматически и устанавливает
// размеры компонента this.Width, this.Height
public override Size GetPreferredSize(Size proposedSize)
{
const float weight = 1.2f;// Вес ширины
// Высота компонента
int height = txtBox.PreferredHeight +
SystemInformation.HorizontalScrollBarHeight;
// Устанавливаем размеры компонента
return new Size((int)(weight * height), height);
}
// Срабатывает автоматически, позиционирует
// кнопки и устанавливает их размеры
protected override void OnResize(EventArgs args)
{
base.OnResize(args);
txtBox.Height = txtBox.PreferredHeight;
txtBox.Width = this.Width;// Текстовое поле по ширине компонента
btn1.Location = new Point(0, txtBox.Height);// Слева снизу
btn2.Location = new Point
(this.Width / 2, txtBox.Height);// Середина низ
// Приравниваем значения полей структур
btn1.Size = btn2.Size = new Size(txtBox.Width / 2,
this.Height - txtBox.Height);
}
// Когда элемент управления уходит из фокуса ввода, надо
// проверить введенное и сгенерировать событие ValueChanged
protected override void OnLeave(EventArgs args)
{
base.OnLeave(args);
this.OnValueChanged(EventArgs.Empty);// Аргумент не используется
}
// Изменения величины пользователем по щелчкам мыши
void ButtonOnClick(object sender, EventArgs e)
{
// Если не было, то дать фокус тексовому полю
if (!txtBox.Focused)
txtBox.Focus();
// Повышаем полномочия ссылки
ArrowButton btn = sender as ArrowButton;
// Пробная величина для изменения значения на шаге
decimal mNewValue = Value;
// Выявляем кнопку и проверяем выход за границу
if (btn == btn1)
if ((mNewValue -= Increment) < Minimum)
return;
if (btn == btn2)
if ((mNewValue += Increment) > Maximum)
return;
// Принимаем изменение и вызываем обработчик,
// который сгенерирует событие ValueChanged
Value = mNewValue;
OnValueChanged(EventArgs.Empty);
}
// Ввели свою дополнительную функцию
// Изменение величины прямым вводом в текстовое поле
protected virtual void OnValueChanged(EventArgs args)
{
// Последовательно проверяем выход величины за левую и правую
// границы, и если выходит за границу, то обрезаем по границе
Value = Math.Min(Maximum, Value);
Value = Math.Max(Minimum, Value);
//Value = Decimal.Round(Value, DecimalPlaces);
// Генерируем событие
if (ValueChanged != null)
ValueChanged(this, args);
}
}
//******************************************************
// Тест для проверки нашего элемента управления
class MyClass : Form
{
// Внутренние поля для видимости в методах
Label lbl1, lbl2;// Заполняются обработчиком события ValueChanged
NumericScan numscan1, numscan2;// Ссылки на компоненты
public MyClass()
{
this.Text = "Тест пользовательского компонента";
// Размеры формы
this.Width = (int)(1.3f * this.Width);
this.Height = (int)(.8f * this.Height);
FlowLayoutPanel flow = new FlowLayoutPanel();
flow.Parent = this;// Привязали к форме
// Размещение по столбцам
flow.FlowDirection = FlowDirection.TopDown;
// Внутреннее обрамление в пикселах
flow.Padding = new Padding(20);
// Растянули по форме
flow.Dock = DockStyle.Fill;
numscan1 = new NumericScan();
numscan1.Parent = flow;
numscan1.AutoSize = true;
// Элементы между собой выравнивать по центру
numscan1.Anchor = AnchorStyles.None;
numscan1.ValueChanged += NumericScanOnValueChanged;
lbl1 = new Label();
lbl1.Parent = flow;
lbl1.AutoSize = true;
// Элементы между собой выравнивать по центру
lbl1.Anchor = AnchorStyles.None;
// Пустая текстовая метка для отступа элементов
(new Label()).Parent = flow;
numscan2 = new NumericScan();
numscan2.Parent = flow;
numscan2.AutoSize = true;
// Элементы между собой выравнивать по центру
numscan2.Anchor = AnchorStyles.None;
numscan2.ValueChanged += NumericScanOnValueChanged;
lbl2 = new Label();
lbl2.Parent = flow;
lbl2.AutoSize = true;
// Элементы между собой выравнивать по центру
lbl2.Anchor = AnchorStyles.None;
// Сами принудительно выполняем обработчик
// для начальной инициализации текстовых меток
NumericScanOnValueChanged(numscan1, EventArgs.Empty);
NumericScanOnValueChanged(numscan2, EventArgs.Empty);
}
void NumericScanOnValueChanged(object sender, EventArgs args)
{
NumericScan ob = sender as NumericScan;// Приводим ссылку
if (ob == numscan1)
lbl1.Text = "Первый: " + numscan1.Value;
else if (ob == numscan2)
lbl2.Text = "Второй: " + numscan2.Value;
}
}
//******************************************************
// Запуск
class Program
{
static void Main()
{
Application.EnableVisualStyles();
// Создали форму и запустили цикл сообщений Windows
Application.Run(new MyClass());
}
}
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.