Event là một thứ mà hầu như bạn phải sử dụng tới trong bất kì ứng dụng nào sử dụng giao diện đồ họa người dùng (GUI). Các control của .Net cung cấp đầy đủ những event cần thiết để bạn có thể sử dụng một cách dễ dàng. Tuy nhiên có những trường hợp bạn cần tạo thêm event cho một lớp nào đó (chẳng hạn event để thông báo khi một collection bị thay đổi nội dung, truyền nội dung giữa hai Form,…). Những thắc mắc hay vấn đề liên quan đến event, bạn có thể sẽ tìm thấy trong bài viết này.
Giới thiệu delegate EventHandler
Event thực chất là một delegate, các event đều được tạo ra từ delegate EventHandler, gồm có hai tham số mà có lẽ bạn đã quen thuộc là hai đối tượng có kiểu object và EventArgs.
public delegate void EventHandler(
object sender,
EventArgs e
);
Object sender: đối tượng kích hoạt ra event
EventArgs e: các tham số liên quan đến event, tùy theo event mà đối tượng này sẽ có kiểu khác nhau (chẳng hạn như PaintEventArgs, CancelEventArgs,…). Tất nhiên các kiểu này đều được thừa kế từ lớp EventArgs.
Đăng kí event
Có vài cú pháp để bạn đăng kí một event, ta có danh sách ví dụ sau (cho sự kiện Click của Button). Bạn có thể cần tìm hiểu về delegate và lamdba expression nếu muốn hiểu rõ hơn các cách viết này:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
btn.Click+= new EventHandler(btn_Click); btn.Click+= btn_Click; btn.Click+= delegate { // do stuff }; btn.Click+= delegate (object sender,EventArgs e) { // do stuff }; button1.Click+=(sender,e)=> { // do stuff }; |
Tạo một event đơn giản từ EventHandler
Việc tạo một event rất đơn giản bằng cách khai báo một đối tượng có kiểu EventHandler kèm theo từ khóa event. Khi cần kích hoạt, bạn sẽ gọi đến event này và truyền cho nó các tham số cần thiết mà EventHandler yêu cầu (ở đây là object và EventArgs).
Ví dụ tôi tạo event cho lớp MyCircle để thông báo sự kiện thay đổi kích thước:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class MyCircle { public event EventHandler SizeChanged; private Size _size; public Size Size { get { return _size; } set { _size=value; if(SizeChanged!=null) { SizeChanged(this, EventArgs.Empty); } } } public MyCircle() { _size=new Size(10,10); } } |
Như bạn thấy trong lớp MyCircle trên, tôi gọi SizeChanged(this,EventArgs.Empty) để kích hoạt event này nếu như nó đã được đăng kí (SizeChanged!=null) thông qua toán tử +=. Tham số đầu tiên là this để chỉ ra rằng chính đối tượng này kích hoạt event và tham số thứ hai là Empty bởi vì ta không cung cấp thông tin thêm nào cho event này.
Một vấn đề nữa bạn cần quan tâm là cần đặt đoạn mã kích hoạt event đúng vị trí cần thiết. Như ví dụ trên tôi chỉ muốn event được kích hoạt khi property Size bị thay đổi. Bên trong lớp MyCircle, nếu bạn chỉ thay đổi giá trị của biến _size thì event này sẽ không bị kích hoạt.
Tạo một Custom Event
Bây giờ tôi muốn sự kiện SizeChanged cung cấp thêm thông tin về kích thước mới của đối tượng để có thể lấy sử dụng mà không cần phải truy xuất đến property Size của MyCircle. Như vậy bạn hạn chế được việc phải ép kiểu đối tượng sender thành kiểu MyCircle trong một số trường hợp.
Ta cần tạo thêm một kiểu SizeEventArgs thừa kế từ EventArgs có thể lưu trữ dữ liệu Size này (bạn nên giữ tiếp vị ngữ là EventArgs).
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class SizeEventArgs: EventArgs { public Size NewSize { get; set; } public SizeEventArgs(Size newSize) { NewSize=newSize; } } |
Khi sử dụng ta phải ép kiểu để lấy được giá trị NewSize này. Ví dụ:
1 2 3 4 |
void Circle_SizeChanged(object sender, EventArgs e) { MessageBox.Show(((SizeEventArgs)e).NewSize.ToString()); } |
Để hạn chế cách sử dụng bất tiện trên, ta cần tạo thêm một kiểu EventHandler mới sử dụng tham số SizeEventArgs, thông thường các EventHandler trả về kiểu void và bạn nên đặt tiếp vị ngữ là EventHandler:
public delegate void CircleEventHandler(object sender,SizeEventArgs se);
Và ta khai báo lại event SizeChanged như sau:
public event CircleEventHandler SizeChanged;
Sử dụng delegate EventHandler<TEventArgs>
Không phải lúc nào bạn cũng muốn tạo thêm các delegate cho các Custom Event, sẽ tốt hơn nếu bạn sử dụng delegate EventHandler<TEventArgs> của .Net thay vì khai báo thêm một delegate mới. Như bạn thấy đây là một generic delegate nhận các kiểu EventArgs khác nhau.
Ta có thể viết lại ví dụ trên như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System; using System.Drawing; namespace EventExample { public class MyCircle { public event EventHandler<SizeEventArgs> SizeChanged; private Size _size; public Size Size { get { return _size; } set { _size=value; if(SizeChanged!=null) { SizeChanged(this,new SizeEventArgs(value)); } } } public MyCircle() { _size=new Size(10,10); } } public class SizeEventArgs: EventArgs { public Size NewSize { get; set; } public SizeEventArgs(Size newSize) { NewSize=newSize; } } } |
Đăng kí và sử dụng:
1 2 3 4 5 6 7 |
// … circle.SizeChanged+= new EventHandler<SizeEventArgs>(Circle_SizeChanged); // … void Circle_SizeChanged(object sender, SizeEventArgs e) { MessageBox.Show(e.NewSize.ToString()); } |
Event Accessor: add và remove
Thay vì cho phép truy xuất trực tiếp bằng cách đặt modifier của event là public/internal ta có thể sử dụng kĩ thuật encapsulation tương tự như property cho event. Như vậy bạn có thể quản lý được việc đăng kí và hủy đăng kí event một cách thủ công. Và khác với property là sử dụng hai accessor get/set, ta sẽ dùng đến cặp accessor là add và remove.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class MyCircle { private event EventHandler<SizeEventArgs> _SizeChanged; public event EventHandler<SizeEventArgs> SizeChange { add { _SizeChanged+=value; // do stuff } remove { _SizeChanged-=value; // do more stuff } } private Size _size; public Size Size { get { return _size; } set { _size=value; if(_SizeChanged!=null) { _SizeChanged(this,new SizeEventArgs(value)); } } } public MyCircle() { _size=new Size(10,10); } } |
Quản lý hiệu quả các event với EventHandlerList
Lớp System.ComponentModel.EventHandlerList là một danh sách giúp bạn quản lý các event, lớp System.Windows.Forms.Form cũng sử dụng một đối tượng này để quản lý với tên Events. Đối tượng này được sử dụng cùng với hai event accessor và event được lưu trữ cùng với một đối tượng đại diện cho “khóa” của chúng:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public class MyCircle { public EventHandlerList Events = new EventHandlerList(); public event EventHandler<SizeEventArgs> SizeChange { add { Events.AddHandler("KeyOne",value); } remove { Events.RemoveHandler("KeyOne",value); } } private Size _size; public Size Size { get { return _size; } set { _size=value; EventHandler<SizeEventArgs> handler=(EventHandler<SizeEventArgs>)Events["KeyOne"]; if(handler!=null) { handler(this,new SizeEventArgs(value)); } } } public MyCircle() { _size=new Size(10,10); } } |
Như bạn thấy việc tạo các field khai báo các event trở thành không cần thiết và ta có thể tiết kiệm được bộ nhớ với việc loại bỏ các field này. Tuy nhiên như MSDN đã lưu ý, vì lớp này sử dụng cách tìm kiếm tuyến tính (Linear search) nên hạn chế của phương pháp này sẽ là làm giảm tốc độ kích hoạt event của ứng dụng nếu như nó chứa quá nhiều phần tử.