Опубликован: 25.03.2010 | Уровень: для всех | Доступ: платный
Лекция 17:

Пользовательские элементы управления

Комбинирование готовых элементов управления

Всегда пользовательский элемент управления лучше создавать на основе уже существующих, преимущественно библиотечных, элементов управления, которые хорошо отлажены и работают привычным образом. В предыдущих примерах мы усовершенствовали библиотечный класс 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());
        }
    }
}
Листинг 17.13 . Пример разработки пользовательского элемента управления
Максим Филатов
Максим Филатов

Прошел курс. Получил код Dreamspark. Ввожу код на сайте, пишет:

Срок действия этого кода проверки уже истек. Проверьте, правильно ли введен код. У вас осталось две попытки. Вы также можете выбрать другой способ проверки или предоставить соответствующие документы, подтверждающие ваш академический статус.

 

Как активировать код?

Денис Пашков
Денис Пашков
Россия
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974