Complex graphical user interfaces can have hundreds of Controls. Applications typically have a number of different states, and not every Control is applicable in every state. Some Buttons or other Controls may even be dangerous in some states. Controls which are not appropriate in certain application states are typically made unavailable to the user by desensitizing them (``grey-out''), or hiding them, or some other means.
Another issue is that Controls are often used to reflect the application's state, rather than just control it; for example, CheckButtons are shown as checked, Sliders indicate by their position the currently selected value, and so on.
The application programmer is faced with the issue of making sure that each and every Control is properly set in each state of the application. As each action from the user could potentially cause a state change in every Control in the application, the situation quickly becomes a combinatorial nightmare.
A new type of message called the GUI-Update message can solve this problem. The quintessential idea is that Controls update themselves by interrogating the application state.
Thus, rather than N action messages updating M controls (involving an amount of programming proportional to N*M), we just have N messages from Controls to perform some sort of action changing the application state, and M update messages from Controls to inquire about the new application state.
Clearly, the amount of programming involved is simply proportional to
M+N. For realistic size projects, this is a big win.
GUI Updating Example.
An example of GUI Updating is given in the ScribbleApp program.
The ScribbleApp program allows lines to be drawn in some canvas.
To clear the canvas, the users invokes the Clear
Button. The Clear Button
is to be available only when something has been scribbled; otherwise, it
is to be grayed out or desensitized. One can accomplish this in FOX
as follows:
// Construct the Clear Button
new FXButton(buttonFrame,"&Clear",NULL,app,ScribbleApp::ID_CLEAR,FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,0,0,0,0,10,10,5,5);This constructs a new Button object, which will send a message ID_CLEAR to the application object (app).
In the application object, we catch two message types
from the Clear button:
// Message Map for the Scribble App class
FXDEFMAP(ScribbleApp) ScribbleAppMap[]={
FXMAPFUNC(SEL_LEFTBUTTONPRESS, ScribbleApp::ID_MOUSE, ScribbleApp::onMouseDown),
FXMAPFUNC(SEL_LEFTBUTTONRELEASE, ScribbleApp::ID_MOUSE, ScribbleApp::onMouseUp),
FXMAPFUNC(SEL_MOTION, ScribbleApp::ID_MOUSE, ScribbleApp::onMouseMove),
FXMAPFUNC(SEL_COMMAND, ScribbleApp::ID_CLEAR, ScribbleApp::onCmdClear),
FXMAPFUNC(SEL_UPDATE, ScribbleApp::ID_CLEAR, ScribbleApp::onUpdClear),
};The SEL_COMMAND message indicates that the Clear button has been pressed; its action will be to clear the drawing canvas:
// Handle the clear message
long ScribbleApp::onCmdClear(FXObject*,FXSelector,void*){
canvas->clearWindow();
dirty=0;
return 1;
}The SEL_UPDATE message is sent when the Clear Button updates itself. The GUI-Update handler determines whether the Clear Button should be sensitized or not depending on whether any drawing has taken place in the canvas; this is kept track of through a flag variable dirty:
// Handle the GUI Update from the Clear button
long ScribbleApp::onUpdClear(FXObject* sender,FXSelector,void*){
FXButton* button=(FXButton*)sender;
// Button is available when canvas is dirty only
dirty ? button->enable() : button->disable();
return 1;
}Note that in this case we know the origin of the message (the sender) to be of the type FXButton, so we can simply cast the sender object down to the appropriate type.
// Update sender
long ScribbleApp::onUpdClear(FXObject* sender,FXSelector,void*){
FXuint msg=dirty ? ID_ENABLE : ID_DISABLE;
sender->handle(this,MKUINT(msg,SEL_COMMAND),NULL);
return 1;
}Many FOX Widgets understand the ID_ENABLE, and ID_DISABLE messages; if however, a message is sent to a sender that doesn't, nothing bad will happen as no message handler will be associated with the message.
When is GUI Updating Performed?
In FOX, automatic GUI Updating is performed automatically after handled
events. This is why it is important to return 1 or 0
in your message handlers.:
As part of the GUI Update of each widget, layout is performed also.
Thus, many layout changes may be peformed during regular (non gui-update)
message handling without any additional overhead:- FOX not only delays
painting, but also layout computation until idle-processing time.
This makes FOX extremely fast.
However, since layout computation is performed during GUI-Update processing,
GUI Update message handlers should avoid making changes to Controls that
could cause layout computation, as these changes will not be handled till
the next GUI Update cycle.
Automatic Gray Out or Hide.
FOX also has the option to automatically gray out or hide certain Widgets.
Both options work very similar, and differ only visually. When automatic
grayout is in effect, the Widget will be automatically grayed out (disabled,
or desensitized to user inputs), when one of the following is true:
If the above is not true, the Widget will be sensitized or enabled again.
Why is this useful? If a Widget's target is an object that performs some sort of message delegation (for example, FXMDIClient and FXMDIChild do this), then the ability to handle a certain SEL_UPDATE message may depend on the delegate object that is in effect at the time the update message is sent. If the particular delegate in effect does not handle the update message, there is no handler to make a Widget assume the correct state.
With automatic gray out, however, the absence of a handler for the SEL_UPDATE message can be turned into an action to gray out the Widget instead. This will keep the GUI consistent even in the absence of update message handlers.
The automatic gray out technique is of particular significance when using MDI (Multiple Document Interface) widgets as both FXMDIClient and FXMDIChild perform message delegation. Messages from the pulldown menus are typically sent to the FXMDIClient, and then subsequently forwarded by the FXMDIClient to the active FXMDIChild (Sometimes, there are no FXMDIChild windows, and the message can not be forwarded and then the message handler returns 0).
As automatic gray out of Widgets will cause a gray out if no handler for the SEL_UPDATE message is found, it is imperative that the SEL_UPDATE must always be handled when the Widget should be sensitive. The update message handler does not necessarily have to do anything, it just needs to return 1.
To handle this common situation, FXWindow defines just such a message
handler for you: FXWindow::onUpdYes() will do nothing
but return a 1. You can simply append this to your message map as
in:
FXMAPFUNC(SEL_UPDATE,ID_MYMENU,FXWindow::onUpdYes)
That will take care of it. Of course if the update message should
do something, you should write your own handler and make it return 1.