Events


Windows-based applications are message-based. This means that the application is communicating with Windows and Windows is communicating with the application by using predefined messages. These messages are structures that contain various pieces of information that the application and Windows will use to determine what to do next. Prior to libraries such as MFC (Microsoft Foundation Classes) or to development environments such as Visual Basic, the developer would have to handle the message that Windows sends to the application. Visual Basic and now .NET wrap some of these incoming messages as something called events. If you need to react to a specific incoming message, you would handle the corresponding event. A common example of this is when the user clicks a button on a form. Windows is sending a WM_MOUSECLICK message to the button’s message handler (sometimes referred to as the Windows Procedure or WndProc). To the .NET developer this is exposed as the Click event of the button.

In developing object-based applications, another form of communication between objects is required. When something of interest happens in one of your objects, chances are that other objects will want to be informed. Again, events come to the rescue. Just as the .NET Framework wraps up Windows messages in events, you can also utilize events as the communications medium between your objects.

Delegates are used as the means of wiring up the event when the message is received by the application. Believe it or not, in the preceding section on delegates, you learned just about everything you need to know to understand how events work. However, one of the great things about how Microsoft has designed C# events is that you don’t actually need to understand anything about the underlying delegates in order to use them. So, this section starts off with a short discussion of events from the point of view of the client software. It focuses on what code you need to write in order to receive notifications of events, without worrying too much about what is happening behind the scenes - just so you can see how easy handling events really is. After that, you write an example that generates events, and as you do so, you should see how the relationship between events and delegates works.

The discussion in this section will be of most use to C++ developers because C++ does not have any concept similar to events. C# events, on the other hand, are quite similar in concept to Visual Basic events, although the syntax and the underlying implementation are different in C#.

Tip 

In this context, the term event is used in two different senses. First, as something interesting that happens, and second, as a precisely defined object in the C# language - the object that handles the notification process. When we mean the latter, we will usually refer to it either as a C# event or, when the meaning is obvious from the context, simply as an event.

The Receiver’s View of Events

The event receiver is any application, object, or component that wants to be notified when something happens. To go along with the receiver, there will of course be the event sender. The sender’s job will be to raise the event. The sender can be either another object or assembly in your application, or in the case of system events such as mouse clicks or keyboard entry, the sender will be the .NET runtime. It is important to note that the sender of the event will not have any knowledge of who or what the receiver is. This is what makes events so useful.

Now, somewhere inside the event receiver will be a method that is responsible for handling the event. This event handler will be executed each time the event that it is registered to is raised. This is where the delegate comes in. Because the sender has no idea who the receiver(s) will be, there cannot be any type of reference set between the two. So the delegate is used as the intermediary. The sender defines the delegate that will be used by the receiver. The receiver registers the event handler with the event. The process of hooking up the event handler is known as wiring up an event. A simple example of wiring up the Click event will help illustrate this process.

First, create a simple Windows Forms application. Drag over a button control from the toolbox and place it on the form. In the properties window rename the button to buttonOne. In the code editor, add the following line of code in the Form1 constructor:

 public Form1() {    InitializeComponent();    buttonOne.Click += new EventHandler(Button_Click); }

Now in Visual Studio you should have noticed that after you typed in the += operator all you had to do was press the Tab key a couple of times and the editor will do the rest of the work for you. In most cases this is fine. However, in this example the default handler name is not being used, so you should just enter the text yourself.

What is happening is that you are telling the runtime that when the Click event of buttonOne is raised, that Button_Click method should be executed. EventHandler is the delegate that the event uses to assign the handler (Button_Click) to the event (Click). Notice that you used the += operator to add this new method to the delegate list. This is just like the multicast example that you looked at earlier in this chapter. This means that you can add more than one handler for any event. Because this is a multicast delegate, all of the rules about adding multiple methods apply; however, there is no guarantee as to the order that the methods are called. Go ahead and drag another button onto the form and rename it to buttonTwo. Now connect the buttonTwo Click event to the same Button_Click method, as shown here:

  buttonOne.Click += new EventHandler(Button_Click); buttonTwo.Click += new EventHandler(Button_Click); 

With delegate inference you can also write the code as follows where the compiler generates the same code as in the previous version:

  buttonOne.Click += Button_Click; buttonTwo.Click += Button_Click; 

The EventHandler delegate is defined for you in the .NET Framework. It is in the System namespace, and all of the events that are defined in the .NET Framework use it. As discussed earlier, a delegate requires that all of the methods that are added to the delegate list must have the same signature. This obviously holds true for event delegates as well. Here is the Button_Click method defined:

  private void Button_Click(object sender, EventArgs e) { } 

A few things are important about this method. First, it always returns void. Event handlers cannot return a value. Next are the parameters. As long as you use the EventHandler delegate, your parameters will be object and EventArgs. The first parameter is the object that raised the event. In this example it is either buttonOne or buttonTwo, depending on which button is clicked. By sending a reference to the object that raised the event you can assign the same event handler to more than one object. For example, you can define one button click handler for several buttons and then determine which button was clicked by asking the sender parameter.

The second parameter, EventArgs, is an object that contains other potentially useful information about the event. This parameter could actually be any type as long as it is derived from EventArgs. The MouseDown event uses the MouseDownEventArgs. It contains properties for which button was used, the X and Y coordinates of the pointer, and other info related to the event. Notice the naming pattern of ending the type with EventArgs. Later in the chapter, you see how to create and use a custom EventArgs-based object.

The name of the method should also be mentioned. As a convention, event handlers follow a naming convention of object_event. object is the object that is raising the event, and event is the event being raised. There is a convention and for readability’s sake it should be followed.

The last thing to do in this example is to add some code to actually do something in the handler. Now remember that two buttons are using the same handler. So, first you have to determine which button raises the event, and then you can call the action that should be performed. In this example, you can just output some text to a label control on the form. Drag a label control from the toolbox onto the form and name it labelInfo. Then write the following code on the Button_Click method:

  if(((Button)sender).Name == "buttonOne")    labelInfo.Text = "Button One was pressed"; else    labelInfo.Text = "Button Two was pressed"; 

Notice that because the sender parameter is sent as object, you will have to cast it to whatever object is raising the event, in this case Button. In this example, you use the Name property to determine what button raised the event; however, you can also use another property. The Tag property is handy to use in this scenario, because it can contain anything that you want to place in it. To see how the multicast capability of the event delegate works, add another method to the Click event of buttonTwo. The constructor of the form should look something like this now:

  buttonOne.Click += new EventHandler(Button_Click); buttonTwo.Click += new EventHandler(Button_Click); buttonTwo.Click += new EventHandler(Button2_Click); 

If you let Visual Studio create the stub for you, you will have the following method at the end of the source file. However, you have to add the call to the MessageBox.Show() function.

  private void Button2_Click(object sender, EventArgs e) {   MessageBox.Show("This only happens in Button 2 click event"); } 

If you go back and make use of anonymous methods, the methods Button_Click and Button2_Click would not be needed. The code for the events would like this:

  buttonOne.Click += delegate {labelInfo.Text = "Button One was pressed";}; buttonTwo.Click += delegate {labelInfo.Text = "Button Two was pressed";}; buttonTwo.Click += delegate       {          MessageBox.Show("This only happens in Button 2 click event");       }; 

When you run this example, clicking buttonOne will change the text in the label. Clicking buttonTwo will not only change the text but also display the MessageBox. Again the important thing to remember is that there is no guarantee that the label text changes before the MessageBox appears, so be careful not to write dependent code in the handlers.

You might have had to learn a lot of concepts to get this far, but the amount of coding you need to do in the receiver is fairly trivial. Also bear in mind that you will find yourself writing event receivers a lot more often than you write event senders. At least in the field of the Windows user interface, Microsoft has already written all the event senders you are likely to need (these are in the .NET base classes, in the Windows.Forms namespace).

Generating Events

Receiving events and responding to them is only one side of the story. For events to be really useful, you need the ability to generate them and raise them in your code. The example in this section looks at creating, raising, receiving, and optionally canceling an event.

The example has a form raise an event that will be listened to by another class. When the event is raised, the receiving object will determine if the process should execute and then cancel the event if the process cannot continue. The goal in this case is to determine whether the number of seconds of the current time is greater than or less than 30. If the number of seconds is less than 30, a property is set with a string that represents the current time; if the number of seconds is greater than 30, the event is canceled and the time string is set to an empty string.

The form used to generate the event has a button and a label on it. In the example code to download the button is named buttonRaise and the label is labelInfo; however, you can use any name you want for your labels. After you have created the form and added the two controls, you will be able to create the event and the corresponding delegate. Add the following code in the class declaration section of the form class:

  public delegate void ActionEventHandler(object sender, ActionCancelEventArgs ev); public static event ActionEventHandler Action; 

So, what exactly is going on with these two lines of code? First, you are declaring a new delegate type of ActionEventHandler. The reason that you have to create a new one and not use one of the predefined delegates in the .NET Framework is that there will be a custom EventArgs class used. Remember the method signature must match the delegate. So, you now have a delegate to use; the next line actually defines the event. In this case the Action event is defined, and the syntax for defining the event requires that you specify the delegate that will be associated with the event. You can also use a delegate that is defined in the .NET Framework. Nearly 100 classes are derived from the EventArgs class, so you might find one that works for you. Again, because a custom EventArgs class is used in this example, a new delegate type has to be created that matches it.

The new EventArgs-based class, ActionCancelEventArgs, is actually derived from CancelEventArgs, which is derived from EventArgs. CancelEventArgs and adds the Cancel property. Cancel is a Boolean that informs the sender object that the receiver wants to cancel or stop the event processing. In the ActionCancelEventArgs class a Message property has been added. This is a string property that will contain textual information on the processing state of the event. Here is the code for the ActionCancelEventArgs class:

  public class ActionCancelEventArgs : System.ComponentModel.CancelEventArgs {    string message = String.Empty;    public ActionCancelEventArgs() : base()  {}    public ActionCancelEventArgs(bool cancel) : base(cancel)  {}    public ActionCancelEventArgs(bool cancel, string message) : base(cancel)    {       this.message = message;    }    public string Message    {       get {return message;}       set {message = value;}    } } 

You can see that all an EventArgs-based class does is carry information about an event to and from the sender and receiver. Most times the information used from the EventArgs class will be used by the receiver object in the event handler. However, sometimes the event handler can add information into the EventArgs class and it will be available to the sender. This is how the example uses the EventArgs class. Notice that a couple of constructors are available in the EventArgs class. This extra flexibility adds to the usability of the class by others.

At this point, an event has been declared, the delegate has been defined, and the EventArgs class has been created. The next thing that has to happen is that the event needs to be raised. The only thing that you really need to do is make a call to the event with the proper parameters as shown in this example:

  ActionCancelEventArgs e = new ActionCancelEventArgs(); Action(this, e); 

Simple enough. Create the new ActionCancelEventArgs class and pass it in as one of the parameters to the event. However, there is one small problem. What if the event hasn’t been used anywhere yet? What if an event handler has not yet been defined for the event? The Action event would actually be null. If you tried to raise the event, you would get a null reference exception. If you wanted to derive a new form class and use the form that has the Action event defined as the base, you would have to do something else whenever the Action event is raised. Currently, you would have to enable another event handler in the derived form in order to get access to it. To make this process a little easier and to catch the null reference error you have to create a method with the name OnEventName where EventName is the name of the event. The example has a method named OnAction(). Here is the complete code for the OnAction() method:

  protected void OnAction(object sender, ActionCancelEventArgs e) {    if (Action != null)   {       Action(sender, e);    } } 

Not much to it but it does accomplish what is needed. By making the method protected, only derived classes have access to it. You can also see that the event is tested against null before it is raised. If you were to derive a new class that contains this method and event, you would have to override the OnAction method and then you would be hooked into the event. To do this, you would have to call base.OnAction() in the override. Otherwise, the event would not be raised. This naming convention is used throughout the .NET Framework and is documented in the .NET SDK documentation.

Notice the two parameters that are passed into the OnAction method. They should look familiar to you because they are the same parameters that will need to be passed to the event. If the event needed to be raised from another object other than the one that the method is defined in, you would need to make the accessor internal or public and not protected. Sometimes it makes sense to have a class that consists of nothing but event declarations and that these events are called from other classes. You would still want to create the OnEventName methods. However, in that case they might be static methods.

So, now that the event has been raised, something needs to handle it. Create a new class in the project and call it BusEntity. Remember that the goal of this project is to check the seconds property of the current time, and if it is less than 30, set a string value to the time, and if it is greater than 30, set the string to :: and cancel the event. Here is the code:

  using System; using System.IO; using System.ComponentModel; namespace Wrox.ProCSharp.Delegates {    public class BusEntity    {       string time = String.Empty;       public BusEntity()       {          Form1.Action += new Form1.ActionEventHandler(Form1_Action);       }       private void Form1_Action(object sender, ActionCancelEventArgs e)       {          e.Cancel = !DoActions();          if(e.Cancel)             e.Message = "Wasn't the right time.";       }       private bool DoActions()       {          bool retVal = false;          DateTime tm = DateTime.Now;          if(tm.Second < 30)          {             time = "The time is " + DateTime.Now.ToLongTimeString();             retVal = true;          }          else             time = "";          return retVal;       }       public string TimeString       {          get {return time;}       }    } } 

In the constructor, the handler for the Form1.Action event is declared. Notice that the syntax is very similar to the Click event that you registered earlier. Because you used the same pattern for declaring the event, the usage syntax stays consistent as well. Something else worth mentioning at this point is how you were able to get a reference to the Action event without having a reference to Form1 in the BusEntity class. Remember that in the Form1 class the Action event is declared static. This isn’t a requirement, but it does make it easier to create the handler. You could have declared the event public, but then an instance of Form1 would need to be referenced.

When you coded the event in the constructor, you called the method that was added to the delegate list Form1_Action, in keeping with the naming standards. In the handler a decision on whether or not to cancel the event needs to be made. The DoActions method returns a Boolean value based on the time criteria described earlier. DoAction also sets the time string to the proper value.

Next, the DoActions return value is set to the ActionCancelEventArgs Cancel property. Remember that EventArg classes generally do not do anything other than carry values to and from the event senders and receivers. If the event is canceled (e.Cancel = true), the Message property is also set with a string value that describes why the event was canceled.

Now if you look at the code in the buttonRaise_Click event handler again you will be able to see how the Cancel property is used:

  private void buttonRaise_Click(object sender, EventArgs e) {    ActionCancelEventArgs cancelEvent = new ActionCancelEventArgs();    OnAction(this, cancelEvent);    if(cancelEvent.Cancel) brackets       labelInfo.Text = cancelEvent.Message;    else       labelInfo.Text = busEntity.TimeString; } 

Note that the ActionCancelEventArgs object is created. Next, the event Action is raised, passing in the newly created ActionCancelEventArgs object. When the OnAction method is called and the event is raised, the code in the Action event handler in the BusEntity object is executed. If there were other objects that had registered for the Action event, they too would execute. Something to keep in mind is that if there were other objects handling this event, they would all see the same ActionCancelEventArgs object. If you needed to keep up with which object canceled the event and whether more than one object canceled the event, you would need some type of list-based data structure in the ActionCancelEventArgs class.

After the handlers that have been registered with the event delegate have been executed, you can query the ActionCancelEventArgs object to see if it has been canceled. If it has been canceled, lblInfo will contain the Message property value. If the event has not been canceled, the lblInfo will show the current time.

This should give you a basic idea of how you can utilize events and the EventArgs-based object in the event to pass information around in your applications.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net