Documentation: Messages [Remove Frame]
|
Why a Target/Message System
|
There are many methods to connect Graphical User Interface elements to an application code; the most common methods being used today are callback functions. However, in C++, callback functions are not an obvious choice, as the technique does not easily allow a certain object to be specified.
Another method being used in C++ is the signal-slot technique. In its typical implementation, connector objects are created that connect a signal to a slot. However, in order to provide the necessary isolation between caller and callee, template instantiations are involved; this limits its use to compile-time connectivity.
The approach taken by FOX is a Target/Message System. Each Widget sends its message to a certain object called the target. As there may be multiple Widgets sending messages to one specific target, a message id is used to tell them apart.
Moreover, a single Widget may be able to send several kinds of messages; this problem
is solved by typing the messages by a message type.
Using the message type and message id, the source and type of a GUI event or
action can be uniquely identified.
Messages can be sent to any object that is derived (directly or indirectly)
from FXObject. Of course, all FOX Widgets are derived from FXObject, and so is the
FXApp application object.
Thus pretty much every object in FOX is able to receive messages.
An advantage of the fact that an explicit object is the target of a message (as opposed to lets say an implicit message routing scheme), is the fact that message id's don't have to be globally unique within an application; all that is required is that it is unique for a certain class and its base classes.
This is a particularly important consideration when one considers making
component oriented software, where components are perhaps written by
different people, or even different organizations.
With FOX, they do not have to coordinate message id's with each other in order
for components to interact properly.
Another important benefit of the target/message system is the fact that
the message a Widget sends, and the target to whom it sends it, may be
changed at run time.
This is an significant benefit for building programs such as GUI Builders
and other component oriented software.
Finally, since all FOX Widgets derive from FXObject, they are
capable of receiving messages, as well as sending
them.
This allows FOX Widgets to implement a number of typical commands
that are common in GUI systems; for example, consider the following code ragment:
new FXHorizontalFrame(main,LAYOUT_SIDE_TOP|LAYOUT_FILL_X); .... .... .... new FXMenuCommand(windowmenu,"&Toolbar",NULL,toolbar,FXWindow::ID_TOGGLESHOWN);
In the above example, the toolbar Widget is a direct target of the MenuCommand Widget. Each time the Toolbar command is invoked, it will toggle the toolbar Widget on or off. Moreover, when the GUI Update process takes place during idle time, the MenuCommand will also send an update message to the toolbar Widget; in response to this update, the toolbar examines its current state, and either checks or unchecks the MenuCommand by sending it back a ID_CHECK or ID_UNCHECK message.
Note that the toolbar can not assume that the sender of the update
message is a MenuCommand; but it does know its an FXObject!
So it needs to send a ID_CHECK (ID_UNCHECK) message to this object instead
of trying to call the check() or uncheck() member function of MenuCommand
directly.
The above code fragment shows the flexibility of the target/message
system, especially when combined with the GUI Update idle processing capability.
The mechanism is used extensively inside FOX itself as well.
Message Maps
|
The messages an object receives are mapped to a specific member function of the object by means of a message map. A message map is nothing but a static, compile-time defined table which associates one or more messages with a certain member function. Complicated Widgets may have several dozen messages that are being mapped this way. Message maps are un unfortunate necessity in C++ as the exact binding of a message to a member function is performed at run time; C++ does not natively support such dynamic binding very well.Fortunately, FOX makes it fairly easy to define those message maps by providing a number of macros to set them up. The following code fragment illustrates the process:
FXDEFMAP(FXGLViewer) FXGLViewerMap[]={ FXMAPFUNC(SEL_PAINT,0,FXGLViewer::onPaint), .... FXMAPFUNCS(SEL_UPDATE,MINKEY,MAXKEY,FXGLViewer::onUpdAll), }; FXIMPLEMENT(FXGLViewer,FXGLCanvas,FXGLViewerMap,ARRAYNUMBER(FXGLViewerMap))
The FXDEFMAP macro takes as the argument the name of the class. It is used to define the entries into the message map table. The FXMAPFUNC macro takes three arguments:- first, the type of the message, second, the id of the message, and last the member function to which this message is being mapped. A similar macro called FXMAPFUNCS is used to define a range of message id's instead of just one. You can use this macro to map a many messages to one and the same member function.
For example, in a calculator program you may have one button for '0', '1', and so on till '9'. Instead of defining ten very similar member functions, you can define just one of them. The member function can use the macro FXSELID(sel) to acquire the id of the message that called it, and FXSELTYPE(sel) to find the messsage type of the message.
The last macro FXIMPLEMENT has four arguments: the name of the class, the name of the immediate base class, a pointer to the message map, and the number of entries in the message map. If an object does not implement any message handlers, you may pass NULL and 0 for these last two arguments instead. The corresponding macro in the header file is called FXDECLARE.
Every FOX object should always use FXDECLARE in its header file or class declaration, and FXIMPLEMENT in its implementation file!
Besides FXMAPFUNC and FXMAPFUNCS, there are two (rarely used) macros that key on the message type only; FXMAPTYPE takes just two arguments, the message type and the member function, and FXMAPTYPES takes three, the first and last message id, and the member function. FXMAPTYPE and FXMAPTYPES will completely disregard the message id, and map any message of the appropriate type to the indicated member function.
All message id's should be in the range MINKEY to MAXKEY, and all message types in the range MINTYPE to MAXTYPE. In addition, the special message id of zero (0) is reserved for system-originated messages.
Messages are resolved to the message handler functions from the derived class upward to the base class. This allows developers to catch messages in their derived class, before it gets handled in the base class. Thus, you can easily redefine behavior of FOX built-in Widgets.
As the message association is performed at run time, it is common practice to place the most-often occurring messages first in the map; this way, the least amount of searching takes place to find them; thus, the SEL_PAINT message is often placed first.
Keeping Track of Message Numbering
|
FOX does not require that all message id's be globally unique.
However, it does require that they are unique for a specific target.
The messages understood by a target are the union of the messages understood
by the target's class, and all of its base classes.
An easy way to keep the numbering straight is to use enums.
FOX itself uses the technique illustrated below:
class FXWindow : public FXDrawable { ... public: enum { ID_SHOW=1, ID_HIDE, ... ID_LAST }; public: ... }; class MyWindow : public FXWindow { ... public: enum { ID_MYMESSAGE=FXWindow::ID_LAST, ID_MYOTHERMESSAGE, ... ID_LAST }; public: ... };
This way, the compiler will automatically arrange to make sure the numbering is correct. It is also easy to add more messages in before ID_LAST, a recompile will adjust the message id's automatically. Of course, you're welcome to use any other scheme if so desired; just make sure your messages do not clash with those of the base-classes of your object.
Message Targets should Outlive Message Sources
|
It is obvious that when a Widget sends a message to some object, the receiving object should of course still exist. A potential pitfall would rear its ugly head if this were not true. Fortunately, in most cases, Control widgets will send messages to their containing Dialog Box, or the Application Object, or other long-lived objects. In rare cases, you may want to make sure that as a Widget or Object is deleted, all references to it are cleaned up as well.FOX provides two member functions:
FXWindow::setTarget(FXObject* tgt)
and
FXWindow::setSelector(FXSelector sel)
that allow you to change the target, as well as the message that a Widget will send. Setting the target of a Widget to NULL will stop it from sending any future messages to anybody.
In order to catch the possibility that messages would be sent to an object that has been destructed, FOX will utterly thrash each object in the destructor. Thus, if such a bug exists in an application, it is likely to surface quickly, leading to more reliable programs.
Sending Your Own Messages
|
In many cases, you will want to send messages to Widgets yourself. For example, in an GUI update handler you may want to send a message to the sender of the update message:
.... FXMAPFUNC(SEL_COMMAND,FXWindow::ID_TOGGLESHOWN,FXWindow::onCmdToggleShown), // Command FXMAPFUNC(SEL_UPDATE,FXWindow::ID_TOGGLESHOWN,FXWindow::onUpdToggleShown), // Update .... // Hide or show window< long FXWindow::onCmdToggleShown(FXObject*,FXSelector,void*){ .... return 1; } // Update hide or show window long FXWindow::onUpdToggleShown(FXObject* sender,FXSelector,void*){ sender->handle(this,shown()?FXSEL(SEL_COMMAND,ID_CHECK) :FXSEL(SEL_COMMAND,ID_UNCHECK),NULL); return 1; }
What happens here? During GUI Updating, the Menu Command connected to
the Toolbar sends a SEL_UPDATE message [instead of the SEL_COMMAND
it sends when the command is invoked by the user].
The onUpdToggleShown function above determines whether
the Toolbar is currently shown, then sends a ID_CHECK or ID_UNCHECK back
to the sender.
Upon getting the ID_CHECK or ID_UNCHECK, a Menu Command object will
subsequently place or remove a little check mark in front of its label.
If the sender of the SEL_UPDATE message were some other Widget, e.g.
a Check Button, it would still work properly, although the Check Button's
implementation of the ID_CHECK and ID_UNCHECK handlers is of course completely
different.
If the sender of the SEL_UPDATE message were some completely different
Widget, it would simply ignore the return message.
By sending messages instead of calling a member function directly, the function above does not need to know what type of Widget sent the SEL_UPDATE message; it just sends a message back; if the sender of the message does not understand the message, nothing happens. Note that it is guaranteed that the sender of a message is always an object derived from FXObject.
The FXSEL macro composes to 16 bit unsigned short numbers into one 32 bit unsigned int. Composing the message types and message id's this way allows for more efficient matching of messages.
Message Handler Return Values
|
You may have noticed that some message handlers return 1, and
some return 0. The general convention is, that if the message
can be considered handled, i.e. it is normally processed,
the handler should return 1. Otherwise, it should
return 0.
Properly returning the correct return value will allow for intelligent
message routing through your application. For messages directly resulting
from a user-input event, such as button presses etc., FOX will use the
return value of the message handler to determine if the GUI needs to be
refreshed.
For example, if the system sent a SEL_LEFTBUTTONPRESS to your Widget, and your Widget's handler returned 1, it is considered handled; next time the system goes into idle processing, all the GUI Widgets in the application will be updated again as it is assumed that by handling the button message, something may have changed. If your handler had returned 0, the message would have been considered unhandled, and nothing further would happen.
Message Routing and Delegation
|
Messages may be forwarded from one object to another. For example, upon receipt of a message, a target may first try to handle the message itself; then, if no match is found, it may try its luck by forwarding the message to some other object. Here's how you would code this up:
// Delegate message long MyWidget::onDefault(FXObject* sender,FXSelector key,void* data){ return delegateObject && delegateObject->handle(sender,sel,data); }
We use here the fact that we can overload the so-called default message handler, onDefault(). The default message handler is called when no message binding has been found for a message.
In the above code fragment, delegateObject is assumed to be some type of object derived from FXObject. You can use these delegation techniques very creatively.
Note that you probably want to start the message id's of the delegateObject from where the delegator MyWidget left off, i.e. make sure there is no overlap unless its intended.
In a few cases, some message which would be handled by MyWidget's base class needs to be forwarded to the delegateObject. You can easily do this by simply mapping that message to onDefault() in MyWidget's message map:
FXMAPFUNC(SEL_COMMAND,BaseWidget::ID_DOSOMETHING,MyWidget::onDefault),
Copyright © 1997-2022 Jeroen van der Zijp |