Пользовательские события
Каждый программист при разработке программ использует события для отслеживания действий пользователя или изменения значений, набор текста в текстовом поле и т.д. Но как насчет того, чтобы создать свои события в собственноручно созданом контроле. В этой статье мы и рассмотрим создание событий для вашего контрола.
Событие – это сообщение другим объектам программы, что произошло какое-то действие. Действие может быть вызвано пользователем, например нажатие мыши, или же другими элеметами программы. Объект, который вызывает событие называется отправитель сообщения, а объект, который сообщение получает – получатель. Получатель сообщения имеет метод, который автоматически выполняется в ответ на событие.
Платформа .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, а у класса ColumnEventArgs – OldColumn и NewColumn.
Теперь определим наши события LineChanged и ColumnChanged:
public event LineEventHandler LineChanged; public event ColumnEventHandler ColumnChanged;
Следующим шагом будет создание двух вирутальных методов с модификатором доступа protected: OnLineChanged и OnColumnChanged. В OnLineChanged мы вызываем событие LineChanged и в OnColumnChanged – ColumnChanged:
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:
Для того, чтобы добавить наш элемент управления: Tools -> Choose Toolbbox Item. В появившемся окне на влкадке .NET Framework Components жмем Browse (Рис. 2).
Далее выбираем наш контрол. В моем случае, он находится в папке с проектом MyControl в подпаке Debug (у Вас может находится в папке Release). После добавления на панели компонентов должен появиться наш элемент управления (Рис. 3) (о том, как изменить стандартную иконку на свою, читайте здесь).
Добавляем его на форму и ползьуемся. Для наглядности, я добавил два лейбла, которые показывают текущее положение курсора (Рис. 4)
UsersEvents.rar (78,8 KiB, 1 666 закачек)
Переведено с сайта www.ondotnet.com