Китай |
Разработка комбинированного компонента
Тестирование скроллирующей кнопки ArrowButton
- Откройте файл Form2.cs в режиме View Designer
- Откомпилируйте проект пользовательской библиотеки UserControls, чтобы в панели Toolbox появился разработанный компонент ArrowButton
- Поместите на Form2 четыре компонента ArrowButton
- Переведите форму в режим View Code и настройте программно (в отличие от декларативного способа через панель Properties ) свойство ScrollButton компонентов на разные значения в конструкторе класса Form2
namespace MyCompany.StudName { public partial class Form2 : Form { public Form2() { InitializeComponent(); // Выполнение кода, сгенерированного // визуальным конструктором оболочки // в декларативном режиме проектирования // и расположенного в файле Form2.Designer.cs // Настройка ориентации стрелок на скроллирующих кнопках arrowButton1.ScrollButton = ScrollButton.Right; arrowButton2.ScrollButton = ScrollButton.Left; arrowButton3.ScrollButton = ScrollButton.Up; arrowButton4.ScrollButton = ScrollButton.Down; } } }Листинг 19.17. Настройка ориентации стрелок в конструкторе класса Form2
- Запустите приложение и посмотрите на результат, который будет таким
Упражнение 3. Комбинирование элементов в единый компонент NumericScan
Теперь мы готовы сконструировать аналог библиотечного компонента NumericUpDown - поле со списком, только с горизонтальным расположением скролирующих кнопок.
- В панели Solution Explorer вызовите контекстное меню для узла UserControls и добавьте к проекту командой Add/User Control форму для размещения нового комбинированного компонента с именем NumericScan. Отметьте для себя, что в качестве базового в заготовке класса наследуется UserControl
- Перенесите на форму NumericScan.cs в режиме View Designer из панели Toolbox два наших компонента ArrowButton и один библиотечный компонент TextBox. На данном этапе особо не заботьтесь о размещение экземпляров компонентов на форме. Мы это действие выполним позже программным способом. А пока расположите объекты компонента примерно так
Далее мы часть настроек выполним декларативно через панель Properties, а часть - программно в конструкторе компонента.
- Через панель Properties выполните декларативные настройки объектов в соответствии с приведенной таблицей свойств
- Через панель Properties в режиме Events зарегистрируйте обработчики для составляющих объектов в соответствии с таблицей
Все остальные настройки составляющих объектов и самого компонента выполним программно.
- Переведите редактирование файла NumericScan.cs в режим View Code и добавьте в конструктор компонента код вычисления размеров формы
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; namespace MyCompany.StudName { public partial class NumericScan : UserControl { public NumericScan() { InitializeComponent(); // Вычисляем и устанавливаем размеры компонента // Четыре высоты текущего шрифта this.Width = 4 * this.Font.Height; // Предпочтительная высота текстового поля + // высота горизонтальных линеек прокрутки this.Height = txtBox.PreferredHeight + SystemInformation.HorizontalScrollBarHeight; } // При завершении ввода клавишей Enter или потери фокуса private void TextBoxOnTextChanged(object sender, EventArgs e) { } // При нажатии любой клавиши, когда объект имеет фокус ввода private void TextBoxOnKeyDown(object sender, KeyEventArgs e) { } // Щелчки на скроллирующих кнопках private void ButtonOnClick(object sender, EventArgs e) { } } }Листинг 19.18. Вычисление и установка размеров компонента NumericScan
- Добавьте в класс NumericScan закрытые поля для сохранения настроек компонента, а также оберните их общедоступными свойствами
public partial class NumericScan : UserControl { // Объявили базовые закрытые поля для открытых свойств 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 = (mValue = value).ToString(); } } public decimal Increment { get { return mIncrement; } set { mIncrement = value; } } public decimal Minimum { get { return mMinimum; } set { // Контроль нижней границы диапазона if (Value < (mMinimum = value)) Value = mMinimum; } } public decimal Maximum { get { return mMaximum; } set { // Контроль верхней границы диапазона if (Value > (mMaximum = value)) Value = mMaximum; } } .......................................... }Листинг 19.19. Поля для хранения настроек компонента NumericScan и свойства доступа
Обратите внимание, что свойство Value в аксессоре set выполняет двойное присваивание: текстовому полю и полю класса.
- Начните с ввода ключевого слова override и переопределите в классе NumericScan унаследованную виртуальную функцию GetPreferredSize() вычисления начальных размеров компонента при создании его экземпляра
// Срабатывает автоматически и устанавливает начальные размеры // экземпляра компонента this.Width, this.Height при его // создании или помещении на форму в режиме проектирования public override Size GetPreferredSize(Size proposedSize) { return new Size(4 * this.Font.Height, // Ширина txtBox.PreferredHeight + SystemInformation.HorizontalScrollBarHeight); }Листинг 19.20. Вычисление начальных размеров компонента
- Начните с ввода ключевого слова override и переопределите в классе NumericScan унаследованную виртуальную функцию OnResize() вычисления локализации и размеров дочерних объектов компонента
// Срабатывает автоматически, позиционирует // дочерние объекты и устанавливает их размеры protected override void OnResize(EventArgs e) { base.OnResize(e); txtBox.Location = new Point(0, 0); // В левом верхнем углу txtBox.Size = new Size(this.Width, txtBox.PreferredHeight); // По всей ширине btnLeft.Location = new Point(0, txtBox.Height); // Позиция btnRight.Location = new Point(this.Width / 2, txtBox.Height); // Позиция btnLeft.Size = btnRight.Size = new Size(this.Width / 2, this.Height - txtBox.Height); // Одинаковый размер }Листинг 19.21. Размещение дочерних объектов в функции OnResize()
Поддержку интерфейсного вида компонента мы обеспечили. Теперь нужно согласовать логику работы кнопок и текстового поля. Начнем с обработки события щелчка на кнопках. Для этого мы предусмотрели общий обработчик ButtonOnClick(). В нем нужно распознать нажатую кнопку и изменить значение текстового поля, а также сохранить это значение во внутреннем поле mValue класса. Но прежде объявим событие, которое будет информировать клиента, который будет создавать экземпляр компонента, об изменении значения поля компонента, а также функцию, которая будет инициировать генерацию этого события.
- Объявите в классе NumericScan с помощью библиотечного делегата EventHandler событие с именем ValueChanged и определите функцию OnValueChanged() диспетчеризации этого события. Функцию диспетчеризации объявите виртуальной на случай дальнейшего наследования нашего компонента
// Объявили пользовательское событие public event EventHandler ValueChanged; // Ввели свою функцию диспетчеризации события ValueChanged // изменения величины прямым вводом в текстовое поле protected virtual void OnValueChanged(EventArgs args) { // Последовательно проверяем выход величины за левую и правую // границы, и если выходит за границу, то обрезаем по границе Value = Math.Min(mMaximum, mValue); Value = Math.Max(mMinimum, mValue); // Округляет вещественный денежный тип до заданного // количества значащих цифр после запятой Value = Decimal.Round(mValue, iDecimalPlaces); // Генерируем событие if (ValueChanged != null) ValueChanged(this, args); }Листинг 19.22. Добавление в класс NumericScan события и функции его диспетчеризации
- Задайте в теле обработчика ButtonOnClick() код, который будет контролировать изменения внутреннего и текстового полей компонента при щелчках на кнопках, а также извещать подписавшегося клиента об этих изменениях
// Щелчки на скроллирующих кнопках private void ButtonOnClick(object sender, EventArgs e) { // Повышаем полномочия ссылки на объект скролирующей кнопки ArrowButton btn = (ArrowButton)sender; // Создаем пробную переменную decimal tmpValue = mValue; // Идентифицируем кнопку и меняем значение поля if (btn == btnLeft) { if ((tmpValue -= mIncrement) < mMinimum) return; } else // Других кнопок нет { if ((tmpValue += mIncrement) > mMaximum) return; } // Обновляем внутреннее и текстовое поля this.Value = tmpValue; // Генерируем событие, извещающее подписавшегося // клиента о произошедшем изменении значения счетчика OnValueChanged(EventArgs.Empty); }Листинг 19.23. Обработчик щелчков на кнопках компонента
- Заполните обработчик TextBoxOnTextChanged() кодом, который будет обновлять внутреннее поле при его корректном изменении или восстанавливать из него корректное значение текстового поля при неправильном заполнении
// При завершении ввода клавишей Enter или потери фокуса private void TextBoxOnTextChanged(object sender, EventArgs e) { if (txtBox.Text.Length == 0) return; // При неудачной попытке преобразования восстанавливаем старое decimal tmpValue; if (!Decimal.TryParse(txtBox.Text, out tmpValue)) txtBox.Text = mValue.ToString(); else mValue = tmpValue; }Листинг 19.24. Обработчик для обновления внутреннего поля из текстового
Когда компонент теряет фокус, возбуждается событие Leave, которое инициируется методом диспетчеризации OnLeave(). В нашем случае тоже нужно обновить состояние компонента и известить подписавшегося клиента.
// При уходе с компонента protected override void OnLeave(EventArgs e) { base.OnLeave(e); // Возбуждаем событие и обновление состояния компонента OnValueChanged(EventArgs.Empty); }Листинг 19.25. Переопределение виртуального метода OnLeave() потери компонентом фокуса
- Заполните обработчик TextBoxOnKeyDown() кодом, который будет контролировать нажатие клавиши Enter в текстовом поле и обновлять состояние компонента
// При нажатии любой клавиши, когда объект имеет фокус ввода private void TextBoxOnKeyDown(object sender, KeyEventArgs e) { switch (e.KeyCode) { case Keys.Enter: OnValueChanged(EventArgs.Empty); break; } }Листинг 19.26. Обработчик нажатия клавиши Enter