Binding actions to events


intro Tcl/Tk programs handler actions to events widgets graphics appendix
[-<--%--^-->-] In the example in section Example we have seen that handler objects may be bound to Tcl commands. Handler objects may also be bound to events. Events are generated by the (X) window environment in response to actions of the user. These actions include pressing a mouse button, releasing a mouse button, moving the mouse, etcetera. Instead of explicitly dealing with all incoming events, applications delegate control to the environment by associating a callback function with each event that is relevant for a particular widget. This mechanism frees the programmer from the responsibility to decide to which widget the event belongs and what action to take. Nevertheless, from the perspective of program design, the proper organization of the callback functions is not a trivial matter. Common practice is to write only a limited number of callback functions and perform explicit dispatching according to the type of event. An object oriented approach may be advantageous as a means to organize a collection of callback functions as member functions of a single class Eliens95. One way of doing this is to define an abstract event handler class which provides a virtual member function for each of the most commonly occurring events. In effect, such a handler class hides the dispatching according to the type of the event. A concrete handler class may then be defined simply by overriding the member functions corresponding to the events of interest. In the following, we will look at how we may define a simple drawing editor by declaring a handler defining the response to pressing, moving and releasing a mouse button. After that we will look more closely at the notion of events and the definition of handlers and actions.

slide: A simple drawing editor

A simple drawing editor

Before looking at the program, think of what you would like a drawing editor to offer you. And, if you have any experience in programming graphics applications, how would you approach the implementation of a drawing editor? A drawing editor is a typical example of an interactive program. As a first approximation, we will define a drawing editor that allows the user to paint a series of black dots by pressing and moving the mouse button, as pictured in figure Draw. The program realizing our first attempt is given below:
  
    #include "hush.h" 
  
    // The drawing_canvas responds to press, motion and release events
    
    class drawing_canvas : public canvas {  
    public:
      
      drawing_canvas( char* path ) : canvas(path) {  
              geometry(200,100);
              bind(this);                 // become sensitive
              dragging = 0;
      }
      
      void press( event& ) { dragging = 1; }
      
      void motion( event& e) { 
      	if (dragging) circle(e.x(),e.y(),1,"-fill black");
      }
      
      void release( event&  ) { dragging = 0; }
      
    protected:
      int dragging; 
    };
  
    // To initialize the application
    
    class application : public session { 
    public:
      application( int argc, char* argv[] )
  			       : session(argc,argv,"draw") {}
      void main() {
       
          canvas* c = new drawing_canvas(".draw");
      
          c->pack();
          tk->pack(".quit");
      }
    };
    
    int main (int  argc, char* argv[]) { 
        session* s = new application(argc,argv);
        return s->run();
    }
  

slide: Drawing canvas

Again, the program may be broken up in a number components. First we must include the hush.h header file. Next we define the class drawing_canvas. The class drawing_canvas inherits from the canvas widget class and consequently allows for drawing figures such as a circle. See section Canvas and the appendix for further details on the canvas class. Before looking at the constructor of the drawing_canvas, note that the member functions press, motion and release expect a reference to an event. These are precisely the member functions corresponding to the event types for which the canvas is sensitive. The meaning of these member functions becomes clear when looking at the role of the instance variable dragging. When dragging is non-zero and a motion event occurs, a black dot is painted on the canvas. Drawing starts when pressing a mouse button and ends when releasing the button. Turning back to the constructor, we see that it expects a path string, which is passed to the canvas ancestor class to create an actual canvas widget. Further, the body of the constructor sets the size of the widget to 200 by 100 and initializes the variable dragging to zero. Finally, the drawing_canvas widget is declared to be its own handler. The member function handler is defined by the class widget. Invoking the handler function results in making the widget sensitive to a number of predefined events.

Discussion

A note on terminology is in place here. The reader may be confused by the fact that handlers can be bound to Tcl actions as well as to events. The situation may become even more confusing when realizing that the widget class itself is a descendant of the handler class. Schematically, we have
   class widget : public handler {
   public:
     ...
     void bind(class handler* h) { ... }
     ...
   };
  

slide: A widget is a handler

The bind function declares a handler object to be responsible for dealing with the events that are of interest to the widget. In other words, a drawing_canvas fulfills the dual role of being a widget and its handler. This must, however, be explicitly indicated by the programmer, which explains the occurrence of the otherwise mysterious expression bind(this). The reason not to identify a widget with a handler is simply that some widgets may need separate handlers. Before studying the abstract handler class in more detail, we will briefly look at the definition of the event class.

Events

Events always belong to a particular widget. To which widget events are actually directed depends on whether the programmer has defined a binding for the event type. When such a binding exists for a widget and the (toolkit) environment decides that the event belongs to that widget, then the callback associated with that event is executed. Information concerning the event may be retrieved by asking the kit for the latest event.
>
    interface event  {  
      int type();                    // X event type
      char* name();                  // type as string
      
      int x();			
      int y();
      
      int button(int i = 0);          // ButtonPress
      int buttonup(int i = 0);        // ButtonRelease
      int motion();                   // MotionNotify
      
      int keyevent();                 // KeyPress or KeyRelease
      int buttonevent(int i = 0);     // ButtonPress or ButtonRelease
      
      int keycode();
      
      void trace();                   // prints event information
      
      void* rawevent();               // delivers raw X event
    };
  

slide: The event interface

Event objects represent the events generated by the X-window system. Each event has a type. The type of the event can be inspected with type() which returns an integer value or name() which returns a string representation of the type. For some of the common event types, such as ButtonPress, ButtonRelease, and MotionNotify, member functions are provided to facilitate testing. If an integer argument (1,2 or 3) is given to button, buttonup or buttonevent, the routines check whether the event has occurred for the corresponding button. The functions x and y deliver the widget coordinates of the event, if appropriate. Calling trace for the event results in printing the type and coordinate information for the event. When setting the kit::trace level to 2 this information is automatically printed. Programmers not satisfied with this interface can check the type and access the underlying XEvent at their own risk.

Handlers

Handler objects provide a type secure way to deal with local data and global resources needed when responding to an event. An actual handler class must be derived from the (abstract) handler class defined below:
>
    interface handler { 
  
      virtual event* dispatch(event* e);
      virtual int operator()();
  
      virtual void press( event& ) { }
      virtual void release( event& ) { }
      virtual void keypress( event& ) { }
      virtual void keyrelease( event& ) { }
      virtual void motion( event& ) { }
      virtual void enter( event& ) { }
      virtual void leave( event& ) { }
      virtual void other( event& ) { }
  
    protected:
      event* _event;
      kit* tk;
    };
  

slide: The handler interface

An instance of an actual handler class may store the information it needs in its instance variables when it is created. A handler object can be activated in response to an event or a Tcl command by calling the dispatch function of the handler. The system takes care of this, provided that the user has bound the handler object to a Tcl command or event. The definition of the dispatch function is given below:
    event* handler::dispatch( event* e ) {
    	_event = e; 
    	int res = this->operator()();
  	return (res != OK) ? _event : 0;
    }
    
    int handler::operator()() { 
  
    	event& e = * _event;                 
fetch event
if ( e.type() == ButtonPress ) press(e); else if ( e.type() == ButtonRelease ) release(e); else if ( e.type() == KeyPress ) keypress(e); else if ( e.type() == KeyRelease ) keyrelease(e); else if ( e.type() == MotionNotify ) motion(e); else if ( e.type() == EnterNotify ) enter(e); else if ( e.type() == LeaveNotify ) leave(e); else other(e); return OK; }

slide: Dispatching events

The dispatch function, in its turn, calls the operator() function, after storing the incoming event in the corresponding instance variable. The kit variable is initialized by the constructor of the handler. The handler::operator() function selects one of the predefined member functions (press, motion, release, etcetera) according to the type of the event. The original handler class knows only virtual functions. Each of these function, including the dispatch and operator() function may be redefined. The two-step indirection, via the dispatch and operator() function, is introduced to facilitate derivation by inheritance, directly from the handler or from classes that are itself derived from the handler class, such as the widget classes. Handler objects are activated only when a binding has been defined, by using kit::bind or implicitly by employing widget::bind. Such bindings may also be defined by kit::after or item::bind. Implicit binding results in the creation of an anonymous binding. Technically, the implementation of event handling by means of handler objects employs a callback function with the handler object as client data. The dispatch function of the client object is called when the callback function is invoked in response to an event or a Tcl function. Bindings encode the binding of C++ handlers to Tcl/Tk commands. They will not be further discussed here.
intro Tcl/Tk programs handler actions to events widgets graphics appendix