The Open Class Library uses events and event handlers to encapsulate and hide the message architectures of Windows, OS/2 Presentation Manager (PM) and Motif in an object-oriented way. The Open Class Library reserves message IDs beginning at 0xFF00. If you use the Open Class Library, define user messages only in the range of WM_USER (0x1000) through 0xFEFF.
The Windows operating system defines messages above the WM_USER range. Refer to your Windows documentation to ensure that you are not conflicting with these messages. OS/2 developers should also verify their user messages if developing portable applications.
In general, writing an event handler can be divided into the following steps:
The Hello World application has several event handlers. The following example illustrates how to use the above steps to process user menu selections. The code shown is from Hello World version 3.
When you select a menu item, an ICommandEvent is generated. The handler class for this type of event is ICommandHandler.
The Hello World application creates a new class called ACommandHandler that is derived from the ICommandHandler class. The virtual function, ICommandHandler::command processes command events. The class ACommandHandler overrides this function to provide its own command event handling.
The following sample, taken from the ahellow3.hpp, file, shows the class declaration of ACommandHandler:
class ACommandHandler : public ICommandHandler { public: /*------------------------------ Constructor -----------------------------| | Construct the object with: | | 1) A pointer to the main frame window | -------------------------------------------------------------------------*/ ACommandHandler(AHelloWindow *helloFrame); /*------------------------------ Destructor ------------------------------| | Destruct the object with: | | 1) No parameters | -------------------------------------------------------------------------*/ virtual ~ACommandHandler() { }; protected: /*------------------------ Override Command Function ---------------------| | The command function is called to handle application command events. | -------------------------------------------------------------------------*/ virtual bool command(ICommandEvent& cmdEvent); private: AHelloWindow *frame; };
The public constructor and private data member frame save a pointer to the frame window for which commands will be processed.
The ACommandHandler command function provides command processing for AHelloWindow class objects. The definition of the command function is taken from ahellow3.cpp. The ID of the menu item is extracted from the command event object using the commandId member function, as follows:
bool ACommandHandler :: command(ICommandEvent & cmdEvent) { bool eventProcessed(true); //Assume event will be processed switch (cmdEvent.commandId()) { case MI_CENTER: frame->setAlignment(AHelloWindow::center); break; case MI_LEFT: frame->setAlignment(AHelloWindow::left); break; case MI_RIGHT: frame->setAlignment(AHelloWindow::right); break; default: // Otherwise, eventProcessed=false; // the event wasn't processed } /* end switch */ return(eventProcessed);
Define a data member from your new handler class in your application window. The following code comes from the ahellow3.hpp file:
ACommandHandler commandHandler;
You should also add an initializer to the constructor for the application window. This is shown in the ahellow3.cpp file:
AHelloWindow :: AHelloWindow(unsigned long windowId) : IFrameWindow(IFrameWindow::defaultStyle() | IFrameWindow::minimizedIcon, windowId) ,menuBar(WND_MAIN, this) ,statusLine(WND_STATUS, this, this) ,hello(WND_HELLO, this, this) ,infoArea(this) ,commandHandler(this)
The base class IHandler provides a member function handleEventsFor to attach a handler to a window. In the Hello World application, ahellow3.cpp, the ACommandHandler begins processing command events for the AHelloWindow in its constructor with the following statement:
commandHandler.handleEventsFor(this);
The base class
IHandler provides a member function stopHandlingEventsFor
to stop event processing for the window. In the Hello
World application,
ahellow3.cpp, the ACommandHandler stops processing
command events for the AHelloWindow in its destructor
with the following statement:
commandHandler.stopHandlingEventsFor(this);
The Open Class Library provides handlers for common operating system messages. However, you may find it necessary to process messages for which there are no predefined handler classes. The Open Class Library makes it easy to add new event and handler classes.
The IHandler class is designed to act as a base class for handlers. All event handlers are derived from this class.
The following statements from the ownhdr.cpp file of the Create Your Own Handler sample show a way to provide a new handler class derived from IHandler. This sample uses timer functions to implement a timer event handler.
Note: | The ATimeHandler class demonstrates IHandler derivation; the timer functions might not handle all cases and might not work in a multithreaded environment. |
The steps for creating an IHandler class, ATimeHandler, follow.
Note that if functions on the AIX and OS/2 operating systems are different, you can use. ifdef compiler directives to determine which functions to call. This allows the application to be portable.
This is just an example of creating handlers. Open Class Library contains the ITimer class you can use to set time intervals.
class ATimeHandler : public IHandler { typedef IHandler Inherited; public: ATimeHandler() : timerId(0) { } //Initialize timerId data member virtual ~ATimeHandler() { } virtual ATimeHandler &handleEventsFor(IWindow *window), &stopHandlingEventsFor(IWindow *window ); protected: bool dispatchHandlerEvent(IEvent& event); virtual bool tick(IEvent& event); private: unsigned long timerId; };
This function starts the handler. In the timehdr.cpp file, the first timer starts, using a constant time interval of 1 second, as follows:
ATimeHandler &ATimeHandler :: handleEventsFor( IWindow *window ) { #ifdef IC_MOTIF timerId = XtAppAddTimeOut ( XtWidgetToApplicationContext ((Widget)window->handle()), TIME_INTERVAL, (XtTimerCallbackProc) postATimeHandlerEvent, window); #endif #ifdef IC_PM timerId = TIMER_ID; WinStartTimer( IThread::current().anchorBlock(), window->handle(), timerId, TIME_INTERVAL); #endif #ifdef IC_WIN timerId = TIMER_ID; SetTimer( window->handle(), timerId, TIME_INTERVAL, NULL ); #endif Inherited::handleEventsFor(window); return (*this); } /* end ATimeHandler :: handleEventsFor(...) */
The "typedef IHandler Inherited" statement in the constructor lets you generically call inherited functions that you have overridden. In this sample, the handleEventsFor function from IHandler is called to complete the starting of the handler.
The PM timer function
automatically posts a WM_TIMER event to the window
specified as the second argument of the timer call. In
this case, you do not have to provide any additional
processing to post the event.
The Windows start timer routine is called by specifying the handle to the window processing messages. Since this application uses only a single timer, the timer ID is specified as a constant. When the timer expires, Windows posts a WM_TIMER event to the window specified in parameter 1 of the SetTimer call.
In contrast, the AIX timer function uses a callback method for notifying the application when the timer has expired, but it does not post an event. Therefore, the callback routine must do the posting. The function to call back is specified as the third argument in the add timer call. This function must be declared as an extern void _System. The function posts the timer event to the window specified in the last argument of the add time-out call. The postTimeHandlerEvent function, from the timehdr.cpp file follows:
#ifdef IC_MOTIF extern void _System //Forward declare for post function postATimeHandlerEvent (IWindow *, XtIntervalId *timerUp); { if (window->isValid()) { IEventParameter2 newTimer = XtAppAddTimeOut ( XtWidgetToApplicationContext((Widget)window->handle()), TIME_INTERVAL, (XtTimerCallbackProc)postATimeHandlerEvent, window); window->postEvent (WM_TIMER, IEventParameter1(*timerUp), newTimer); } } /* end extern void _System postATimeHandlerEvent(...) */ #endif
This function determines the relevance of the message. If the message is not relevant, the function returns false and passes the message to other handlers attached to the window. If this event is relevant, then the handler's function for processing the event should be called. In the timehdr.cpp file, the tick function is called, as follows:
bool ATimeHandler :: dispatchHandlerEvent(IEvent& event) { bool eventProcessed(false); //Assume event will not be proccessed if ((event.eventId() == WM_TIMER) && (event.parameter1() == timerId)) { #ifdef IC_MOTIF timerId = event.parameter2(); #endif eventProcessed = tick(event); } return (eventProcessed); } /* end ATimeHandler :: dispatchHandlerEvent(...) */
Because the Windows and OS/2 timer is continuous, the timer ID can be a constant number. However, for AIX, a new timer is created every second. Therefore, when the expired timer ID is relevant, for example, dispatched, the new timer ID replaces the old one.
Normally, the event processing function of a general handler class does nothing but return false. It is the specific handler class, MyTimeHandler, in the ownhdr.cpp file that overrides the event processing function and returns true.
bool MyTimeHandler :: tick(IEvent& event) { return (false); //The timer event is not processed } /* end ATimeHandler :: tick(...) */
The MyTimeHandler::tick member function, in the ownhdr.cpp file, overrides the event handling, as follows:
bool MyTimeHandler::tick(IEvent& event) { /*----------------------------------------------------------------------------- | Set the static text to the current time | | Return false indicating we didn't handle the event | ----------------------------------------------------------------------------*/ pText->setText( ITime().asString() ); return false; }
The timer is removed or stopped, depending on the system, and the inherited stopHandlingEventsFor function completely stops the timer. The following code comes from the timehdr.cpp file:
ATimeHandler &ATimeHandler :: stopHandlingEventsFor( IWindow *window ) { #ifdef IC_MOTIF XtRemoveTimeOut (timerId); timerId = 0; #endif #ifdef IC_PM if ( window->isValid() ) WinStopTimer( IThread::current().anchorBlock(), window->handle(), timerId); #endif #ifdef IC_WIN if ( window->isValid() ) KillTimer( window->handle(), timerId ); #endif Inherited::stopHandlingEventsFor(window); return (*this);
Refer to the Create Your Own Handler sample applicaton (timehdr.cpp, timehdr.hpp, ownhdr.cpp, and ownhdr.hpp files) to see how to derive from ATimeHandler to provide a ticking clock.
To prevent ATimeHandler users from having to understand how information is encoded in the two message parameters inside the event, derive an event class from IEvent to encapsulate this information. The following statements show an example of how to do this:
class ATimerEvent : public IEvent { public: ATimerEvent( IEvent &evt ) : IEvent( evt ) {;} // Define functions inline unsigned long timerNumber() const { return parameter1().number1(); } };.
You can only construct objects of this class from an instance of IEvent. Because of the small amount of code required, the example defines the code inline.
To use the new class, change the dispatchHandlerEvent member function to create an instance of ATimerEvent. Also, change the ATimeHandler::tick member function to accept an ATimerEvent object as a parameter, as shown in the timehdr.cpp file:
bool ATimeHandler :: dispatchHandlerEvent(IEvent& event) { bool eventProcessed(false); // Assume event will not be processed if ((event.eventId() == WM_TIMER) && (event.parameter1() == timerId)) { #ifdef IC_MOTIF timerId = event.parameter2(); #endif eventProcessed = tick(event); } return (eventProcessed); } bool ATimeHandler :: tick(IEvent& event) { return (false); //The timer event is not processed }
The two classes now completely encapsulate timer messages. Users of the classes do not need to know which messages are generated or how the information is encoded in the message parameters.
You can restrict the window classes to which a handler can be attached. The following steps show you how to restrict the attachment of the ATimeHandler class to the ITextControl class and its derived classes.
class ATimeHandler : public IHandler { public: /* use default constructor */ bool dispatchHandlerEvent( IEvent& evt ); virtual ATimeHandler &handleEventsFor ( ITextControl* textWindow ), &stopHandlingEventsFor ( ITextControl* textWindow ); protected: virtual bool tick( ATimerEvent& evt ); private: //Make these functions private virtual ATimeHandler // so they cannot be called &handleEventsFor ( IWindow* window ), &stopHandlingEventsFor ( IWindow* window ); };
ATimeHandler &ATimeHandler::handleEventsFor( ITextControl* textWindow ) { return (handleEventsFor(window)); }
ATimeHandler &ATimeHandler::stopHandlingEventsFor( ITextControl* textWindow ) { return (stopHandlingEventsFor(window)); }
Creating a Frame Window
Handling Mouse Events
ICommandEvent
ICommandHandler
IEvent
IEventData
IHandler
ITimer