Events

 
Chapter 4 - Advanced C# Topics
bySimon Robinsonet al.
Wrox Press 2002
  

As we have indicated, events are the usual means by which an application running on the Windows platform can receive notifications when something interesting happens. Whenever Windows is running, you can be pretty sure a lot of events are firing. For example, when you click the mouse button, the application in whose window you clicked will almost certainly have been notified of this event. The same thing happens when you do almost anything with the mouse or keyboard. Similarly, when windows are minimized, restored, or maximized, the corresponding applications will be notified in case they wish to take any action in response.

Actually, we should point out that the above paragraph isn't completely accurate. Strictly, events aren't a part of the Windows operating system at all. Windows uses things called Windows messages in order to notify applications of interesting things that have happened . However, Windows messages are low-level C structures that are quite hard to work with, so high-level languages such as VB have traditionally wrapped up the messages in a high-level framework in which events are the main objects, to make things easier for the programmer. C# and the .NET runtime do this as well, which means we can work entirely in terms of events, even though it is the Windows messages that are actually doing the work behind the scenes.

Important 

In C# terms, events are actually a special form of delegates.

Believe it or not, in the preceding section on Delegates , we learned just about everything we needed 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, we are going to start off with a short discussion of events from the point of view of the client software. We will focus 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 we can show how easy handling events really is. After we have done that, we will write a sample that generates events, and as we do so, we will see how the underlying delegates framework is working with us.

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

In this context, we are using the term 'event' in two different senses. Firstly as something interesting that happens, and secondly 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 Consumer's View of Events

The consumer here is any application that wants to be notified when something happens. For the sake of argument, we will assume that the thing that happens is the mouse being clicked, but the event can be anything you like. There will also be some other software (often the Windows operating system or the .NET Framework itself) that represents whatever it is that normally becomes aware that an event has occurred. This software is responsible for notifying our application (for things like mouse and keyboard events, this will be Windows itself). We will refer to this other software as the event generator .

The pattern looks like this:

click to expand

Now, somewhere inside the consumer, there will be a method, which is the one the consumer wants called when a MouseClick happens. This method is known as the event handler for the event, and a good name for it in this case is OnClick (this name isn't entirely random; in Chapter 19, we will see that the relevant Windows Forms base classes really do have a handler called OnClick for this purpose). At some point in the past, most likely when the consumer application started up, it will have informed the event generator of its interest in mouse clicks - and of the fact that it wants OnClick() to be called whenever a mouse click happens. You can see the delegates coming in here. In C# terms, in order to give the event generator this information, the consumer is going to have to somehow place a reference to OnClick() inside a delegate. Once the event generator has this information, whenever it detects that a mouse click happens, it can use this delegate to call the consumer's OnClick() method.

Let's look at the coding details now. The .NET Framework requires a very precise signature for any event handler. OnClick() and all other event handlers must look like this:

   void OnClick(object sender, EventArgs e)   // e can also be derived     // from EventArgs     {     // code to handle event     }   

Event handlers can't return anything other than void . There's no point in them returning any value - all the event generator wants to do is call the method. It doesn't usually want to know anything about what the consumer does in response - that's the consumer's business (There are occasions when an event handler will wish to cancel an event, but this is handled by the handler modifying a property of the EventArgs parameter, not through any return value). Handlers must also take two parameters. The first parameter is a reference to the object that generated the event. In other words, the event generator passes in a reference to itself. The second parameter must be a reference to either a .NET base class, System.EventArgs , or to a class derived from that. You can think of EventArgs as the generic base class for any notifications that events have occurred. In some cases, the event generator will document that it will actually send in a reference to a derived class in response to certain specific events. A derived class might contain extra information pertaining to that event, such as the location of the mouse, or which key on the keyboard was pressed.

That's the event handler sorted out. All we need to do now is notify the event generator that we are interested in the event in the first place. For this to happen, we need the event generator to make some item available that is able to receive requests for notifications. This item will be a public member of the .NET class that represents the event generator, and is a member of type event . This member is the C# event itself, and is a specialized form of multicast delegate. Let's suppose here that the consumer has a reference to the event generator, via a reference variable called Generator , and that the member that represents the event is called MouseClick . In the code for the consumer class, we would presumably see something like this:

   EventGenerator Generator = GetAReferenceToTheEventGeneratorSomehow();   

where we assume that EventGenerator represents the event generator class.

The consumer can notify the event generator that it wants to receive notifications of mouse clicks with this line of code:

   // Generator is a reference to the event generator     Generator.MouseClick += OnClick();   

That is literally all the consumer needs to do.

Since we have already given away the fact that the event, MouseClick , is a kind of multicast delegate, you can probably guess what is really going on here. The event will contain references to all the event handlers that various consumers have registered to receive notifications, and this line of code simply adds our own event handler to the list. Another point of this is if the consumer subsequently decides it is not interested in this particular event any more, it can inform the event generator like this:

   Generator.MouseClick -= OnClick();   

We might have had to learn a lot of concepts to get this far, but the amount of coding you need to do in the consumer is fairly trivial. Also bear in mind that you will find yourself writing event consumers a lot more often than you write event generators. At least in the field of the Windows user interface, Microsoft has already written all the event generators you are likely to need (these are in the .NET base classes, in the Windows.Forms namespace, and we'll look at them in Chapter 7).

There is just one point you might notice in the above discussion. We were a bit vague about just how the consumer obtains a reference to the events generator in the first place. That is not part of the general events framework. You should find that the documentation for the particular event generator will tell you how to get this reference. In the case of Windows Forms, you can often just derive your consumer from a generator class. Very often, you will simply instantiate the event generator as one of the .NET base classes.

This architecture is actually extremely flexible. It means that your consumer can request notifications of as many events as it pleases. It can even request notifications from different sources. This is the reason for the first parameter passed to the event handler, the sender reference. Since the consumer has a reference to the object that generated the event, this means that if there is more than one possible source for the event, the event handler can easily work out which source is responsible. An example of this is that your application might be a Windows form that has several buttons on it. Any one of these buttons might have notified you that it has been clicked, and by examining the sender reference you can figure out which button is the correct one. Not only that, but also, many different consumers can request to be notified of the same events. Each one simply adds its event handler to the events, and because of the way that multicast delegates work, when the event fires, all the event handlers will be triggered in turn .

Events Example: Console Notifications

We are now going to write an example that generates events. The example is called UserInputNotify . For this example, we are going back to the Mortimer Phones mobile phone company. The Mortimer Phones company is a fictional company that we have used for our examples to illustrate the principles of inheritance in Chapter 3.

We are going to write a little console application for Mortimer Phones staff members , which displays a message to the user. The user gets a choice between seeing a personal message from Mortimer (the company president), or a general advertisement. The program keeps asking the user what message they want to see until the user hits X followed by Enter to exit the program. However, we are going to structure the program so that it uses events. It will follow a classic event-notification architecture that has a general applicability and is also very similar to the architecture that Windows Forms uses.

There are two objects of interest in our code:

  • The UserInputMonitor - This is the object that deals with user input. It is responsible for asking users which messages they want to see.

  • The MessageDisplayer - This is the object that is responsible for displaying the appropriate message.

Since the message displayer is not concerned with user input, it has no direct means of knowing when to display a message or which message to display - it has to rely on the UserInputMonitor to tell it. The UserInputMonitor will do so by raising events. The MessageDisplayer will notify the UserInputMonitor that it wishes to be told whenever the user has asked to display a message. After that, whenever the user makes such a request, the UserInputMonitor will fire an appropriate event, resulting in the event handler in the MessageDisplayer getting called.

At this point, we have just designed a program that illustrates events nicely , but which doesn't actually do anything that we couldn't have done with a simple loop inside one method in one single class, with the knowledge of C# that we picked up in Chapter 2! We are also now going to add something else that will demonstrate how flexible the events architecture is. You see, Mortimer quite likes to know when members of his staff have asked to see his personal message. It makes him feel popular and gives him a nice warm feeling inside. So he would like to be notified too whenever users request to see his personal messages.

To this end, we will code up another class, the ManagersStaffMonitor , which tells Mortimer when someone has asked to see his personal message. For the purposes of our sample, the ManagersStaffMonitor will simply display a dialog box that says Kaark! (Mortimer likes saying Kaark! when he's happy!). Doing this, we can see the beauty of the events architecture, because all we have to do to let Mortimer know about the events is plug the ManagersStaffMonitor 's own event handler into the event as well. Then, when a customer asks to see a message, the resulting event will call both event handlers in succession. If some other software wanted to be notified of this event, it would do the same thing: write an event handler and add it to the event in the UserInputMonitor . In this way, the event handlers can get effectively chained together.

The structure of our application looks like this:

click to expand

In this diagram, we have marked in the event handlers for our two consumer classes, the MessageDisplayer and the ManagersStaffMonitor , as well as the event in our event generator class, the UserInputMonitor . In order to pass details of the event, we are going to use a new class, UserRequestEventArgs , which we will derive from System.EventArgs , and which will implement one property, Request . This indicates which of the two possible requests the user has made.

To start off with, we need to write the UserRequestEventArgs class that will convey details of an event. For this, we will use an enumeration to list the possible events:

   enum RequestType {AdRequest, PersonalMessageRequest};     class UserRequestEventArgs : EventArgs     {     private RequestType request;     public UserRequestEventArgs(RequestType request)     :   base()     {     this.request = request;     }     public RequestType Request     {     get     {     return request;     }     }     }   

This code should be fairly self-explanatory. We simply add a field and corresponding property that specifies which request the user has made. Our constructor takes a parameter that specifies this value, and then calls the base class constructor.

Now for the event generator, the UserInputMonitor :

   class UserInputMonitor     {     public delegate void UserRequest(object sender, UserRequestEventArgs e);     public event UserRequest OnUserRequest;     public void Run()     {     bool finished = false;     do     {     Console.WriteLine("Select preferred option:");     Console.WriteLine("  Request advertisement - hit A then Return");     Console.WriteLine("  Request personal message from Mortimer " +     "- hit P then Return");     Console.WriteLine("  Exit - hit X then Return");     string response = Console.ReadLine();     char responseChar = (response == "") ? ' ' : char.ToUpper(response[0]);     switch(responseChar)     {     case 'X':     finished = true;     break;     case 'A':     OnUserRequest(this, new     UserRequestEventArgs(RequestType.AdRequest));     break;     case 'P':     OnUserRequest(this, new     UserRequestEventArgs(RequestType.PersonalMessageRequest));     break;     }     }     while (!finished);     }     }   

This class contains the only line of code in which we actually use the event keyword to declare an event. The two lines of code of interest are these:

 public delegate void UserRequest(object sender, UserRequestEventArgs e);    public event UserRequest OnUserRequest; 

The pattern is much the same as when we were declaring delegates in the previous section of this chapter. We first define a delegate, and then declare an instance of one. Note that the definition of the delegate signature matches the signature that .NET requires all event handlers to have: it returns a void , and takes an object and something derived from EventArgs as parameters.

The second line is more interesting - here we tell the compiler that this class contains a member event, of a type given by the delegate. You can think of this line as meaning something like:

 // not actual code - just presented for comparison    public UserRequest OnUserRequest = new UserRequest(// method); 

except that the event syntax allows the compiler to make sure the delegate has the correct signature. It also implies we are instantiating the object, but not yet supplying any methods for it to refer to, something that we could not do with the alternative delegate syntax.

The rest of the UserInputMonitor class is given over to a method, Run() , which repeatedly loops , asking the user for input. Notice how the event actually gets fired :

 switch(responseChar) {    case 'X':       finished = true;       break;    case 'A':       OnUserRequest(this, new UserRequestEventArgs(RequestType.AdRequest));       break;    case 'P':       OnUserRequest(this, new          UserRequestEventArgs(RequestType.PersonalMessageRequest));       break; } 

We simply call the event, which automatically invokes any methods that it currently references (these will be the event handlers that the consumers have notified it about). In each case, we instantiate a new EventArgs -derived class to pass to the event handler.

Now we can look at the consumer classes. First, the MessageDisplayer :

   class MessageDisplayer     {     public MessageDisplayer(UserInputMonitor monitor)     {     monitor.OnUserRequest +=     new UserInputMonitor.UserRequest(UserRequestHandler);     }     protected void UserRequestHandler(object sender, UserRequestEventArgs e)     {     switch (e.Request)     {     case RequestType.AdRequest:     Console.WriteLine("Mortimer Phones is better than anyone " +     "else because all our software is written in C#!\n");     break;     case RequestType.PersonalMessageRequest:     Console.WriteLine("Today Mortimer issued the following " +     "statement:\n  Nevermore!\n");     break;     }     }     }   

The constructor to the MessageDisplayer is where we actually tell the UserInputMonitor class that we want to receive event notifications. We do this by adding a reference to our event handler to the UserRequest event. The event handler is our UserRequestHandler() method, and it simply checks the EventArgs parameter to see what type of message the user has requested , and displays the appropriate event. Note that, in line with common practice, we make the event handler protected . Generally, event handlers are intended to be called through events, in other words, through delegates. There's usually no reason for them to be called directly, so there's no reason for outside classes to be able to see them. (Notice that the fact a method is invisible to outside classes does not prevent those classes from calling it through a delegate if that delegate has been supplied with a reference to that method.)

Mortimer's personal ManagersStaffMonitor class does basically the same thing, except it displays a dialog box instead of writing to the console:

   class ManagersStaffMonitor     {     public ManagersStaffMonitor(UserInputMonitor monitor)     {     monitor.OnUserRequest +=     new UserInputMonitor.UserRequest(UserRequestHandler);     }     protected void UserRequestHandler(object sender, UserRequestEventArgs e)     {     if (e.Request == RequestType.PersonalMessageRequest)     {     MessageBox.Show("Kaark!", "Mortimer says ...");     }     }     }   

Finally, we can see how the whole set of classes fit together. This is the code for the Main routine:

   using System;     using System.Windows.Forms;     namespace Wrox.ProCSharp.AdvancedCSharp     {     class MainEntryPoint     {     static void Main()     {     UserInputMonitor inputMonitor = new UserInputMonitor();     MessageDisplayer inputProcessor =     new MessageDisplayer(inputMonitor);     ManagersStaffMonitor mortimer =     new ManagersStaffMonitor(inputMonitor);     inputMonitor.Run();     }     }   

Quite apart from the event architecture, this is actually a classic example of object oriented programming. The main entry point does almost nothing itself. It simply instantiates the various objects that make up the application, and sets them off interacting with each other. In this sample, we create an instance of each of the three main classes, and initialize them appropriately. We then call the inputMonitor 's Run() method, which enters the main program loop, asking the user for input. When we cover Windows Forms in Chapter 7, we will find that a Windows Forms application has a very similar structure to this. For this type of application, we instantiate our objects, then call a static method, Run() on a .NET base class, Windows.Forms.Application , which sets the whole events process going.

If we run our sample, we get this result:

  UserInputNotify  Select preferred option:   Request advertisement - hit A then Return   Request personal message from Mortimer - hit P then Return   Exit - hit X then Return A Mortimer Phones is better than anyone else because all our software is written in C#! Select preferred option:   Request advertisement - hit A then Return   Request personal message from Mortimer - hit P then Return   Exit - hit X then Return P Today Mortimer issued the following statement:   Nevermore! 

with the Windows Form when the user hits P looking like this:

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

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