Каждый программист при разработке программ использует события для отслеживания действий пользователя или изменения значений, набор текста в текстовом поле и т.д. Но как насчет того, чтобы создать свои события в собственноручно созданом контроле. В этой статье мы и рассмотрим создание событий для вашего контрола.
Событие – это сообщение другим объектам программы, что произошло какое-то действие. Действие может быть вызвано пользователем, например нажатие мыши, или же другими элеметами программы. Объект, который вызывает событие называется отправитель сообщения, а объект, который сообщение получает – получатель. Получатель сообщения имеет метод, который автоматически выполняется в ответ на событие.
Платформа .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)
[download id=”8″]
Переведено с сайта www.ondotnet.com



