Adding Events and Event Handlers

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.


Writing an Event Handler

In general, writing an event handler can be divided into the following steps:

  1. Determine which handler class processes the event.
  2. Create a handler derived class and override the event-handling functions.
  3. Create an instance of your derived class.
  4. Start processing events for the window.
  5. Stop processing events for the window.

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.

  1. Determine which handler class processes the event.

    When you select a menu item, an ICommandEvent is generated. The handler class for this type of event is ICommandHandler.

  2. Create a handler derived class and override the event-handling function.

    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);                                                  
    


  3. Create an instance of your derived class.

    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)                                                   
    
    
  4. Start processing events for the window.

    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);                                        
    
    
  5. Stop processing events for the window.

    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);                                  
    
    

Extending Event Handling

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.

  1. Create an IHandler derived class by creating a class declaration for ATimeHandler. The class is derived from IHandler and provides a virtual function tick to process the event. The following code comes from the timehdr.hpp file:
    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;                                                   
    };                                                                         
    
  2. Override the handleEventsFor member function.

    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.

  3. Optionally, post the event.

    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                                                                     
    
    
  4. Override the dispatchHandlerEvent member function.

    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.

  5. Create the member function for processing the event.

    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;
    }
    
  6. Override the stopHandlingEventsFor member function.

    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.

Writing Handlers

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.

  1. Write the class declaration following this example:
        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 );
            };
        
  2. Override the handleEventsFor member function to accept only ITextControl objects, as shown in the following example:
    ATimeHandler
      &ATimeHandler::handleEventsFor( ITextControl* textWindow )
    {
    
      return (handleEventsFor(window));
    }
    
  3. Override stopHandlingEventsFor member function to accept only ITextControl objects. For example:
    ATimeHandler
      &ATimeHandler::stopHandlingEventsFor( ITextControl* textWindow )
    {
    
      return (stopHandlingEventsFor(window));
    }
    



Events and Event Handlers


Creating a Frame Window
Handling Mouse Events


ICommandEvent
ICommandHandler
IEvent
IEventData
IHandler
ITimer