The Event Hierarchy

Now consider the interfaces to the various event classes. As mentioned earlier, having Event as a base class for all types of events allows any of them to be passed to the single handleEvent function in the interface to Interactor. However, an Interactor needs to know specifically which type of event it's received, since it has different responses to keyboard events than to mouse events.

For this reason, the Event class defines a virtual getType function, which returns a constant indicating the type of the event. The interfaces of Event and its derived classes look like this:

class Event

{

public:

virtual EventType getType() = 0;

};

class KbdEvent : public Event

{

public:

EventType getType() { return KBD_EVENT; }

unsigned int val()

private:

char ascii;

char scancode;

};

class MouseEvent : public Event

{

public:

EventType getType() { return MOUSE_EVENT; }

Point getPosition()

int getButton()

private:

Point pos;

int buttons;

};

Since there is no type associated with generic Event objects, the getType function is declared as pure virtual. Each derived class overrides getType to return a unique constant.

This design is analogous to the use of a union in C:

// Analogous situation in C

struct Event

{

EventType type;

union

{

struct KbdEvent keyAction;

struct MouseEvent mouseAction;

};

};

The generic Event interface corresponds to a structure containing a union, and the KbdEvent and MouseEvent classes correspond to possible contents of the union. The getType function acts like a discriminator field in the structure, indicating the current type of the contents of the union.

This design can be extended to include a third type of event, ScrollEvent, which was mentioned in the previous section. This type of event contains the direction and amount of scrolling that is requested. However, this isn't enough information to perform scrolling. If a text window contains two scroll bars and receives a ScrollEvent, it doesn't know whether to scroll the text vertically or horizontally unless it knows which scroll bar sent the message. Therefore, a ScrollEvent also contains a pointer to the ScrollBar that created it. Its interface is as follows:

class ScrollEvent : public Event

{

public:

EventType getType() { return SCROLL_EVENT; }

int getDirection();

int getDistance();

ScrollBar *getSource();

private:

int direction;

int distance;

ScrollBar *source;

};

Notice that you can interpret a ScrollEvent to have a meaning other than “scroll text.” You can use scroll bars to adjust the volume of a beeper, change the shading of a colored panel, or perform other similar actions.

A window's handleEvent function must use a switch statement to determine the type of event received. For example:

void Win::handleEvent( Event &action )

{

switch( action.getType() )

{

case KBD_EVENT:

KbdEvent &keyAction = (KbdEvent &)action; // Cast

// Respond to keyboard event

break;

case MOUSE_EVENT:

MouseEvent &mouseAction = (MouseEvent &)action; // Cast

// Respond to mouse event

break;

//...

};

}

Once the type of the event is found, the handleEvent function converts the base class pointer to a derived class pointer (this is not generally a safe conversion in C++, but it works properly in this case because the return value of getType guarantees the type of the object). This allows handleEvent to access the information specific to that type of event and respond with the appropriate action.

To add a new type of event, you must derive a new class from Event. If you want an existing window class to respond to the new type of event, you have to change that class's handleEvent function. You can extend the switch statement by adding a case clause for the new event type.