Пользовательские события

Каждый программист при разработке программ использует события для отслеживания действий пользователя или изменения значений, набор текста в текстовом поле и т.д. Но как насчет того, чтобы создать свои события в собственноручно созданом контроле. В этой статье мы и рассмотрим создание событий для вашего контрола.

Событие – это сообщение другим объектам программы, что произошло какое-то действие. Действие может быть вызвано пользователем, например нажатие мыши, или же другими элеметами программы. Объект, который вызывает событие называется отправитель сообщения, а объект, который сообщение получает – получатель. Получатель сообщения имеет метод, который автоматически выполняется в ответ на событие.

Платформа .NET Framework поддерживает простое программирование событий. Это настолько просто, что очень часто программисты не знают закулисную работу событий. Всё что нужно знать: если вы хотите получить событие от контрола, вы должны подписать обработчик события:

eventSource.someEvent += new SomeEventHandler(someEventMethod);

К примеру, если вы хотите подписаться к нажатию на кнопку button1 и чтобы это событие обрабатывал метод button1_Click, необходимо написать следующее:

button1.Click += new eventHandler(button1_Click);

После этого вы должны определить метод-обработчик следующим образом:

private void button1_Click(object sender, EventArgs e)
{
   //код обработчика события
}

Метод не обязательно должен быть скрытым (private), но он должен принимать два аргумента:
объекты типа Object и EventArgs. Теперь давайте рассмотрим создание наших собственных событий.

Предположим мы хотим создать событие MyEvent для элемента MyControl унаследованого от UserControl:

1. Первое что необходимо сделать – определить открытый делегат, который будет принимать два параметра: sender типа object и e типа MyEventArgs:

public delegate void MyEventHandler(object sender, MyEventArgs e);
public partial class MyControl : UserControl
{
   ...
}

2. MyEventArgs – это объет содержащий данные, которые будут пересылаться от отправителя к получателю. MyEventArgs должен быть унаследован от System.EventArgs класса. Определим в нашем простанстве имен новый класс:

namespace MyControl
{
   public class MyEventArgs : EventArgs
   {
      ...
   }
}

3. Определим событие MyEvent в классе нашего контрола:

public partial class MyControl : UserControl
{
    //наше событие
    public event MyEventHandler MyEvent;

    public MyControl()
    {
        InitializeComponent();
    }
}

4. Теперь, в классе нашего контрола (MyControl) необходимо определить метод с именем которое соостоит из приставки On и названия нашего события. Поскольку наше событие мы назвали MyEvent, то метод будет называться OnMyEvent. Обратите внимание, что метод имеет один аргумент типа MyEventArgs. Внутри этого метода мы будем вызывать событие. В C# вызов события достигается путем вызова имени события:

protected virtual void OnMyEvent(MyEventArgs e)
{
   MyEvent(this, e);
}

5. Теперь, все что осталось сделать, это вызвать OnMyEvent из любого места. Откуда именно это сделать, зависит от того, что именно должно вызвать событие.

Теперь перейдем к практике. Мы создадим элемент управления, который будет похож на доску (10 х 10) в которой пользователь может вводить текст. Кроме того, существует курсор, который показывает текущую позицию редактирования. Пользователь может использовать комбинации клавиш, как например Ctrl+R, Ctrl+G, Ctrl+B чтобы изменять фоновый цвет элемента и Ctrl+Alt+R, Ctrl+Alt+G, Ctrl+Alt+B чтобы изменять цвет текста. Мы усовершенствуем наш элемент двумя событиями:

  • ClineChanged – событие будет возникать, когда будет изменяться вертикальная позиция курсора
  • ColumnChanged – при изменении гризонтальной позиции

После этого мы будем перехватывать эти события для динамического обновления метки, которая показывает текущее положение курсора. Дальше идет краткое описание нашего эелемента:

    • Элемент содержит двумерный массив, который в свою очередь содержит символы вводимые пользователем. В конструкторе класса все элементы массива устанавливаются в пробелы.
    • В методе OnPaint будут рисоваться символы в элементе строка за строкой, символ за символом.
    • Курсор рисуется в отдельном потоке caretThread. Данный поток запускается в конструкторе класса и завязан на метод ShowsCaret. Этот метод содержит замкнутый цыкл, который показывает и прячет курсор каждые 350 милисекунд
    • Поток caretThread завершается, когда удаляется элемент управления. Нами будет усовершенствован метод Dispose
    • KeyPressed и ProcessDialogKey

отслеживают нажатия клавиш

  • Определим всего два свойства CaretX и CaretY которые могут быть использованы для получения и изменения позиции курсора

Начнем с добавления событий нашему элементу управления: LineChanged и ColumnChanged. Определим два делегата для наших событий:

public delegate void LineEventHandler(object sender, LineEventArgs e);

public delegate void ColumnEventHandler(object sender, ColumnEventArgs e);

Определим и реализуем LineEventArgs и ColumnEventArgs:

public class LineEventArgs : EventArgs
{
    private int oldValue, newValue;
    public LineEventArgs(int oldValue, int newValue)
    {
        this.oldValue = oldValue;
        this.newValue = newValue;
    }

    public int NewLine
    {
        get { return newValue; }
    }
    public int OldLine
    {
        get { return oldValue; }
    }
}

public class ColumnEventArgs : EventArgs
{
    private int oldValue, newValue;
    public ColumnEventArgs(int oldValue, int newValue)
    {
        this.oldValue = oldValue;
        this.newValue = newValue;
    }

    public int NewColumn
    {
        get { return newValue; }
    }
    public int OldColumn
    {
        get { return oldValue; }
    }
}

У обеих классов конструкторы принимают два целочисленных значения: старое и новое. Оба класса имеют свойства доступные только для чтения. У класса LineEventArgs события OldLine и NewLine, а у класса ColumnEventArgsOldColumn и NewColumn.

Теперь определим наши события LineChanged и ColumnChanged:

public event LineEventHandler LineChanged;

public event ColumnEventHandler ColumnChanged;

Следующим шагом будет создание двух вирутальных методов с модификатором доступа protected: OnLineChanged и OnColumnChanged. В OnLineChanged мы вызываем событие LineChanged и в OnColumnChangedColumnChanged:

protected virtual void OnLineChanged(LineEventArgs e)
{
    if(LineChanged != null)
       LineChanged(this, e);
}

protected virtual void OnColumnChanged(ColumnEventArgs e)
{
    if(ColumnChanged != null)
       ColumnChanged(this, e);
}

Теперь неоходимо вызвать методы OnLineChanged и OnColumnChanged из класса нашего элемента управления. Событие LineChanged срабатывает, когда изменяется вертиальная координата курсора, т.е. при изменении значеня свойства CaretY. Похоже происходит и с событием ColumnChanged. Оно происходит при изменении начения свойства CaretX. Определим эти свойтсва:

private int caretX, caretY;

public int CaretX
{
    get { return caretX; }
    set
    {
        int oldValue = caretX;
        caretX = value;
        if (oldValue != caretX)
            OnColumnChanged(new ColumnEventArgs(oldValue, caretX));
    }
}

public int CaretY
{
    get { return caretY; }
    set
    {
        int oldValue = caretY;
        caretY = value;
        if (oldValue != caretY)
            OnLineChanged(new LineEventArgs(oldValue, caretY));
    }
}

С событиями разобрались теперь перейдем к внешнему виду нашего элемента управления.
Для начала определим поля:


//количество строк
int rowCount = 10;
//количество столбцов
int columnCount = 9;
//массив, для хранения символов
char[,] board;
//размеры символа
int charachterWidth = 10;
int charachterHeight = 14;
//расстояние между строками
int lineSpace;
//цвет текста
public Color foreColor = Color.Black;
Thread caretThread;
//видимость курсора
bool caretVisible = true;
//толщина пера
int penWidth = 2;
Pen pen;
bool keyStrokePressed;

В контрукторе класса заполним массив пробелами, подишемся на событие нажатия клавиш и создадим поток, который будет отображать курсор:

public MyControl()
{
    InitializeComponent();

    board = new char[columnCount, rowCount];
    //заполняем массив пробелами
    for (int i = 0; i < columnCount; i++)
    {
        for (int j = 0; j < rowCount; j++)
            board[i, j] = ' ';
    }

    pen = new Pen(foreColor, penWidth);
    this.BackColor = Color.White;
    //подписываемся на события нажатия клавиш
    this.KeyPress += new KeyPressEventHandler(KeyPressed);
    //поток, отображающий курсор
    caretThread = new Thread(new ThreadStart(ShowCaret));
    caretThread.Start();
}

Метод, который будет отображать и скрывать курсор:

void ShowCaret()
{
    try
    {
        while (true)
        {
            this.Invalidate(new Rectangle(CaretX * charachterWidth, CaretY * (charachterHeight + lineSpace),
                CaretX * charachterWidth + 2 * penWidth, (CaretY + 1) * (charachterHeight + lineSpace)));
            Thread.Sleep(350);
            caretVisible = !caretVisible;
        }
    }
    catch (Exception ex)
    {

    }
}

Переопределим метод OnPaint который будет отрисовывать наш элемент:

protected override void OnPaint(PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Font font = new Font("Verdana", charachterWidth);
    Brush brush = new SolidBrush(foreColor);

    //рисуем символы
    for (int i = 0; i < columnCount; i++)
        for (int j = 0; j < rowCount; j++)
        {
            g.DrawString(board[i, j].ToString(), font, brush,
                new Point(i * charachterWidth, j * (lineSpace + charachterHeight)));
        }

    //рисуем курсор
    if (caretVisible)
    {
        int x = CaretX * charachterWidth;
        int y = CaretY * (charachterHeight + lineSpace);
        g.DrawLine(pen, x, y, x, y + lineSpace + charachterHeight);
    }
}

Теперь займемся обработкой нажатия клавиш. Здесь будут использоваться два ообработчика события: ProcessDialogKey и KeyPressed:

void KeyPressed(object sender, KeyPressEventArgs e)
{
    if (!keyStrokePressed)
    {
        char c = e.KeyChar;
        int i = (int)c;
        if (i == 8)
        {
            if (CaretX == 0)
            {
                CaretX = columnCount - 1;
                if (CaretY == 0)
                    CaretY = rowCount - 1;
                else
                    CaretY--;
            }
            else
            {
                CaretX--;
            }
            board[CaretX, CaretY] = ' ';
        }
        else
        {
            board[CaretX, CaretY] = c;
            if (CaretX == columnCount - 1)
            {
                CaretX = 0;
                if (CaretY == rowCount - 1)
                    CaretY = 0;
                else
                    CaretY++;
            }
            else
                CaretX++;
        }
        this.Invalidate();
        this.Update();
    }
}

protected override bool ProcessDialogKey(Keys keyData)
{
    keyStrokePressed = true;
    switch (keyData)
    {
        case Keys.Down:
            if (CaretY == rowCount - 1)
                CaretY = 0;
            else
                CaretY++;
            break;
        case Keys.Up:
            if (CaretY == 0)
                CaretY = rowCount - 1;
            else
                CaretY--;
            break;
        case Keys.Left:
            if (CaretX == 0)
            {
                CaretX = columnCount - 1;
                if (CaretY == 0)
                    CaretY = rowCount - 1;
                else
                    CaretY--;
            }
            else
                CaretX--;
            break;
        case Keys.Right:
            if (CaretX == columnCount - 1)
            {
                CaretX = 0;
                if (CaretY == rowCount - 1)
                    CaretY = 0;
                else
                    CaretY++;
            }
            else
                CaretX++;
            break;
        case Keys.Control | Keys.R:
            this.BackColor = Color.Red;
            break;
        case Keys.Control | Keys.G:
            this.BackColor = Color.Green;
            break;
        case Keys.Control | Keys.B:
            this.BackColor = Color.Blue;
            break;
        case Keys.Control | Keys.Alt| Keys.R:
            foreColor = Color.Red;
            break;
        case Keys.Control | Keys.Alt | Keys.G:
            foreColor = Color.Green;
            break;
        case Keys.Control | Keys.Alt | Keys.B:
            foreColor = Color.Blue;
            break;
        case Keys.Escape:
            this.BackColor = Color.White;
            break;
        case Keys.Alt|Keys.F4:
            Application.Exit();
            break;
        default :
            if ((int)(Keys.Control & keyData) != 0)
            {
                //нажата клавиша Control, если есть
                //необходимость, выполняйте какие-то действия
                return true;
            }
            else if ((int)(Keys.Alt & keyData) != 0)
            {
                //нажата клавиша Alt, если есть
                //необходимость, выполняйте какие-то действия
                return true;
            }
            else
            {
                // даем возможность выполниться коду в KeyPressed
                keyStrokePressed = false;
            }
            break;
    }
    this.Invalidate();
    this.Update();
    return base.ProcessDialogKey(keyData);
}

Осталось изменить метод Dispose – удаление объекта. Для этого, открываем файл MyControl.Designer.cs и в нем изменяем метод Dispose

protected override void Dispose(bool disposing)
{
    if (disposing && (components != null))
    {
        components.Dispose();
    }
    if (disposing)
        caretThread.Abort();
    base.Dispose(disposing);
}

Теперь, после сборки проекта, мы можем использовать наш элемент управления. Для этого, создадим Windows Forms Application в нашем решении (рис. 1). Я назвал приложение MyControlTest:

Рис.1 Добавление проекта для теста

Для того, чтобы добавить наш элемент управления: Tools -> Choose Toolbbox Item. В появившемся окне на влкадке .NET Framework Components жмем Browse (Рис. 2).

Рис. 2 Добавить элемент управления

Далее выбираем наш контрол. В моем случае, он находится в папке с проектом MyControl в подпаке Debug (у Вас может находится в папке Release). После добавления на панели компонентов должен появиться наш элемент управления (Рис. 3) (о том, как изменить стандартную иконку на свою, читайте здесь).

Рис. 3 Пользовательский элемент управления

Добавляем его на форму и ползьуемся. Для наглядности, я добавил два лейбла, которые показывают текущее положение курсора (Рис. 4)

Рис. 4 Использование пользовательского элемента

[download id=”8″]
Переведено с сайта www.ondotnet.com

Leave a Reply

Your email address will not be published. Required fields are marked *

http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_bye.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_good.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_negative.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_scratch.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_wacko.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_yahoo.gif 
http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_cool.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_heart.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_rose.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_smile.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_whistle3.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_yes.gif 
http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_cry.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_mail.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_sad.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_unsure.gif  http://csharpcoding.org/wp-content/plugins/wp-monalisa/icons/wpml_wink.gif