Binding actions to events
Handler objects may be bound to Tcl commands or 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, etc.
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 of deciding 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.
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.
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 upon
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.
\slide{class-event}{The event class}{
interface event { \fbox{event}
int type(); \c{ X event type }
char* name(); \c{type as string}
int x();
int y();
...
void* rawevent(); \c{delivers} raw X event
};
}{
interface event { \fbox{event}
int type(); \c{// X event type }
char* name(); \c{// type as string}
int x();
int y();
int button(int i = 0); \c{// ButtonPress}
int buttonup(int i = 0); \c{// ButtonRelease}
int motion(); \c{// MotionNotify}
int keyevent(); \c{// KeyPress or KeyRelease}
int buttonevent(int i = 0); \c{// ButtonPress or Release}
int keycode();
void trace(); \c{// prints event information}
void* rawevent(); // \c{delivers} raw X event
};
}
}
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 which
returns an integer value or 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, it is checked 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 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.
\c{
Handlers
Handler objects provide a type secure
way in which to deal with local data and global
resources needed when responding to an event.
A handler object may store the information
it needs in instance variables,
when its constructor is called.
See slide [class-handler].
}
.so sli-handler
\c{
Activating a handler object in response
to an event or a Tcl command occurs
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.
}
.so sli-dispatch
\c{
The dispatch function, in its turn,
calls the function,
after storing the kit, argc
and argv arguments in the corresponding
instance variables.
See slide [dispatching].
The function
fetches the latest event from the kit
object and selects one of the predefined
member functions (press, motion, release, etc.)
according to the type of the event.
The original handler class knows only
virtual functions.
Each of these function, including
the dispatch and
function, may be redefined.
The two-step indirection, via the
dispatch and functions,
is introduced to facilitate
derivation by inheritance,
directly from the handler
or from classes that are themselves
derived from the handler class,
such as the widget classes.
}
Actions
Handler objects will be activated only
when a binding has been defined,
by using
or implicitly by employing
or .
Such bindings may also be defined
by or and
.
Implicit binding results in the creation of
an anonymous action.
Actions, as characterized by the class action,
provide the actual means with which to bind C++
code to Tcl commands.
Actions may be defined either trivially
by a Tcl command,
by a C/C++ command function
or by handler objects.
In effect, defining an action by means
of a handler object amounts, implicitly,
to defining a command function with a
privileged client for which, by convention,
the dispatch function is invoked.
}
\slide{class-client}{The client class}{
Client
client
class client { }; empty class
Command
command
typedef command(client*, kit*, int argc, char* argv[]);
}
Data passed to a command function must be of the type
client, which is defined by an empty
class introduced only to please the compiler.
In slide [class-client] the type definition of command is given.
Apart from the parameter, a command
function must also declare a parameter
and an argc and argv parameter, similar to that for main.
The data of a command (and similarly for
the clientdata parameter of a tclcommand)
can be any kind of class.
However, it is to be preferred that such classes
are made a subclass of client.
The client data pointer is declared when creating
an action and passed to the command function when the actual call
is made.
The other parameters (argc and argv) depend
on the actual call.
The use of argc and argv comes from the original
C interface of Tcl.
It proves to be a very flexible way of
communicating data, especially in string-oriented
applications.
interface action { \fbox{action}
action(char* name);
action(char* name, handler* h);
action(char* name, command f, client* data = 0);
action(char* name, tcl_command f, ClientData data = 0);
action( handler* h); \ifsli{}{//} anonymous
action( command f, client* data = 0);
action( tcl_command f, ClientData data = 0);
char* name(); \c{// returns the name of an action}
};
slide: The action class
The class action offers no less than seven
constructors.
The first constructor, which takes a ()
string as a parameter, is merely
for convenience.
It may be used to convert the name of a Tcl script command
into an action.
The following three constructors differ from the last
three constructors only by their first string parameter,
which serves to define the name under which the
action will be known by the Tcl script interpreter.
The last three constructors, in contrast,
create anonymous actions, of which the user, however,
can ask the name by invoking the function name.
The preferred form of creating an action is by giving it
(apart from a name) a handler as a parameter.
As already noted,
handler objects offer a type-secure way of dealing with client
information.
In contrast, the second constructor of this group,
which takes a command function and possibly a pointer
to client data as parameters, may make (type-insecure) conversions
of the client data necessary.
The constructor taking a {\em tcl_command} and ClientData as parameters
is incorporated for compatibility reasons only.
When using any of the last six constructors,
as a side-effect an association is created between
the name of the action and a Tcl command.
If such a Tcl command already exists,
the previous association will be overwritten.
This is also the case if it has been defined
as a Tcl script command.