When referring to events in documentation, use the 'an event was raised' terminology instead of 'an event was fired'.
public delegate void MouseEventHandler(object sender, MouseEventArgs e);
public delegate void MouseEventHandler(object sender, MouseEventArgs e);
The sender parameter represents the object that raised the event and calls the delegate. The sender parameter is always of type object, even if it is possible to employ a more specific type.
The state associated with the event is encapsulated in an instance of an event class named e. Use an appropriate and specific event class for its type.
public delegate void MouseEventHandler(object sender, MouseEvent e);
public class MouseEvent : EventArgs { }
public class MouseEventArgs : EventArgs { private int x; private int y; public MouseEventArgs(int x, int y) { this.x = x; this.y = y; } public int X { get { return x; } } public int Y { get { return y; } } }
Each event has a set of methods that are used to add and remove event handlers. These start with AddOn and RemoveOn followed by the name of the event. The parameter is called handler.
class Button : Control { MouseEventHandler onClickHandler; public void AddOnClick(MouseEventHandler handler) { onMouseEvent = (MouseEventHandler)Delegate.Combine(onMouseEvent, handler); } public void RemoveOnClick(MouseEventHandler handler) { onMouseEvent = (MouseEventHandler)Delegate.Remove(onMouseEvent, handler); } }
Classes with many events can store the handlers more efficiently than just declaring a field for each, since the developer will probably not handle all the events for a given class all at once. For example, the Component class implements a more space efficient way of storing handlers which is exposed via the protected (family) methods AddEventHandler and RemoveEventHandler:
public class Foo : Component { static final object EventClick = new object(); static final object EventMouseDown = new object(); static final object EventMouseUp = new object(); static final object EventMouseMove = new object(); public void AddOnClick(MouseEventHandler handler) { AddEventHandler(EventClick, handler); } public void RemoveOnClick(MouseEventHandler handler) { RemoveEventHandler(EventClick, handler); } }
For each event on a class, include a corresponding protected (family) virtual method that raises the event. This is not appropriate for sealed classes, because they cannot be subclassed. The purpose of the method is to provide a way for a subclass to handle the event using an override. This is more natural than using delegates in the case where the developer is sub classing. The name of the method is OnEventName where EventName is the name of the event being raised. For example:
public class Button { ButtonClickHandler onClickHandler; protected virtual void OnClick(ClickEvent e) { if (onClickHandler != null) onClickHandler(this, e); // call the delegate if non-null } }
The subclass can choose not to call the base during the processing of OnEventName. Be prepared by this by not including any processing in the OnEventName method that is required for the base class to work correctly.
Classes are ready for the handler of the event to do pretty much anything, and in all cases the object is left in a good state after the event has been raised. Consider using try/finally at the point where the event is raised. Since the developer can call back on the object to perform other actions, do not assume anything about the object state when control returns to the point at which the event was raised. For example:
public class Button { ButtonClickHandler onClickHandler; protected void DoClick() { PaintDown(); // paint button in depressed state try { OnClick(); // call event handler } finally { // window may be deleted in event handler if (windowHandle != null) PaintUp(); // paint button in normal state } PaintUp(); // paint button in normal state } protected virtual void OnClick(ClickEvent e) { if (onClickHandler != null) onClickHandler(this, e); } }
For example, the treeview control fires an CancelEvent when the user is about to edit a node label. The developer can use this event to prevent a node from being edited:
public class Form1 : Form { TreeView treeView1 = new TreeView(); void treeView1_BeforeLabelEdit(object source, NodeLabelEditEvent e) { e.cancel = true; } }
Note that in this case, no error is generated to the user. The label is just read only.
In cases where the developer would like to cancel the operation and return an exception, then CancelEvent is not appropriate. In these cases, the event does not decend from CancelEvent, and the developer just raises an exception inside of the event handler in order to cancel. For example, the user may want to write validation logic in a edit control:
public class Form1 extends Form { TreeView edit1 = new Edit(); void edit1_TextChanging(object source, Event e) { throw new RuntimeException("Bad edit"); }