Опубликован: 13.07.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 27:

Разработка комбинированного компонента

Тестирование скроллирующей кнопки 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 выполните декларативные настройки объектов в соответствии с приведенной таблицей свойств
Таблица свойств декларативной настройки объектов компонента NumericScan
Тип Свойство Значение Пояснения
TextBox Name txtBox Имя экземпляра компонента текстового поля
  TextAlign Right Расположение текста
ArrowButton Name btnLeft Имя объекта скроллирующей кнопки
  ScrollButton Left Ориентация стрелки влево
ArrowButton Name btnRight Имя объекта скроллирующей кнопки
  ScrollButton Right Ориентация стрелки вправо ( по умолчанию )
  • Через панель Properties в режиме Events зарегистрируйте обработчики для составляющих объектов в соответствии с таблицей
Таблица событий декларативной настройки объектов компонента NumericScan
Объект Событие Обработчик Пояснения
txtBox TextChanged TextBoxOnTextChanged Возбуждается при завершении ввода клавишей Enter или потери фокуса
  KeyDown TextBoxOnKeyDown Возбуждается при нажатии любой клавиши, когда объект имеет фокус ввода
btnLeft Click ButtonOnClick Возбуждается при шелчке на кнопке курсором мыши
btnRight Click ButtonOnClick События двух кнопок подписаны на один обработчик

Все остальные настройки составляющих объектов и самого компонента выполним программно.

  • Переведите редактирование файла 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(). В нашем случае тоже нужно обновить состояние компонента и известить подписавшегося клиента.

  • Переопределите в классе компонента виртуальный метод 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
  • В панели Solution Explorer выберите узел UserControls и командой Rebuild контекстного меню откомпилируйте готовый компонент
Иван Циферблат
Иван Циферблат
Россия, Таганрог, 36, 2000