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.
You can either post or send a message. When a message is sent (sendEvent), the sender is blocked until the receiver processes the message and returns. If the sender and receiver are on the same thread, a direct function call is made to the receiver. If the sender and receiver are on different threads, the message is placed on a private area of the receiver's message queue. In either case, the sender is blocked until the receiver processes the message and returns.
When a message is posted, the message is placed on the receiver's message queue. The poster's thread then continues without waiting for a reply.
A thread switch occurs on each sent message. Continuously posting messages to another thread is less expensive than sending them.
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.
Note:
The notification and observer mechanism is not an extension of the event and handler mechanism. They are designed for two different purposes. IBM Open Class uses handlers and event objects to provide a platform independent abstraction of the underlying operating system UI events and event processing.
On OS/2 the underlying mechanism is subclassing of window procedures and processing of control window messages. But the same handler and event classes are used to support MOTIF widgets on AIX and controls on Windows.
A handler allows you to watch for certain events, perform some work in response to the event before passing it on, or even prevent it from being passed on to other handlers (including the control's window procedure). Notifications and observers are not related to the underlying operating system events and event processing. They are related to the interfaces of the IBM Open Class classes.
Multiple observers can say they are interested in knowing when certain aspects of an IBM Open Class object change or some event occurs. This is different than events and handlers in the sense that as an observer, you are notified when the aspect you are observing changes or occurs, but so are all the other observers watching that object. As an observer, you cannot stop notification from proceeding.
Notifications are not implemented with WM_CONTROL or WM_USER messages. The IBM Class Library objects generate notifications themselves in their member functions (for example, this occurs at the C++ object level not the presentation system control level). Since IWindow derives from INotifier, all controls know how to notify observers from any member function that does something an observer might be interested in knowing.
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->setTextAlignment(AHelloWindow::center);
break;
case MI_LEFT:
frame->setTextAlignment(AHelloWindow::left);
break;
case MI_RIGHT:
frame->setTextAlignment(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 system functions vary by operating system. 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
/*--------------------------- Start a Timer ------------------------------|
| The X-Windows application add timeout routine is called by specifying, |
| the application context which is obtained using the window handle, |
| a time interval in milliseconds which is set to a constant 1000UL, |
| an external routine that is called when the timer is up, and |
| client_data which is a pointer to application specific data. |
| The timerId is a unique ID returned from the add timer routine that |
| will be used when processing the expired timer. |
| When the timer expires, the callback routine will be called with |
| two parameters, the client_data and the timer id. |
|------------------------------------------------------------------------*/
timerId = XtAppAddTimeOut (
XtWidgetToApplicationContext ((Widget)window->handle()),
TIME_INTERVAL,
(XtTimerCallbackProc) postATimeHandlerEvent,
window);
#endif
#ifdef IC_PM
/*--------------------------- Start a Timer ------------------------------|
| The Presentation Manager start timer routine is called by specifying |
| the anchor block handle for the current thread, |
| the handle of the window passed in, |
| a unique timer ID that is below the PM TID_USER_MAX constant, and |
| a time interval in milliseconds which is set to a constant 1000UL. |
| Since this application uses only a single timer, the timer ID is |
| specified as a constant. |
| When the timer expires, PM will post a WM_TIMER event to the window |
| specified in parameter 2 of the WinStartTimer call. IParameter1 |
| of the timer event will contain the timer ID. |
|------------------------------------------------------------------------*/
timerId = TIMER_ID;
WinStartTimer( IThread::current().anchorBlock(),
window->handle(), timerId, TIME_INTERVAL);
#endif
#ifdef IC_WIN
/*--------------------------- Start a Timer ------------------------------|
| 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. |
|------------------------------------------------------------------------*/
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);
{
/*-------------------------- Is Window Valid ? ---------------------------|
| This test prevents an exception in IWindow in the case where a timer |
| expires while the window is being closed. If the window is not valid,|
| do nothing. |
|------------------------------------------------------------------------*/
if (window->isValid())
{
/*--------------------------- Add Next Timer -----------------------------|
| When an X-Windows timer is up, it does not restart automatically. |
| Therefore, the callback routine starts another timer and registers |
| itself as the callback routine. The new timer ID is passed to the |
| time handler in the timer event as parameter 2. |
|------------------------------------------------------------------------*/
IEventParameter2 newTimer = XtAppAddTimeOut (
XtWidgetToApplicationContext((Widget)window->handle()),
TIME_INTERVAL,
(XtTimerCallbackProc)postATimeHandlerEvent,
window);
/*-------------------------- Post Timer Event ----------------------------|
| The timer event is created by posting an event to the window passed |
| in as client_data to the callback routine. The ID of the timer that |
| expired is passed to the time handler as IParameter1 of the event. |
|------------------------------------------------------------------------*/
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