Documentation: Automatic GUI Updating

| Documentation: Automatic GUI Updating [Remove Frame]

What is Automatic GUI Updating?

Traditionally, Controls such as Buttons and Sliders have been used to provide notifications to the application code that a certain command is to be performed, or some value has changed. For example, if one moves a Slider a little bit, a notification message informs the application that a new value is available; at the same time, the Slider's position will give a visual cue about the value being transmitted to the application code.

But suppose one were to add a TextField to control the same variable. If we type into the TextField, the new value will be transmitted to the application just fine, but since the slider was not moved, the visual position of the slider now no longer represents the value that the program is actually working with.

Traditionally, GUI developers have solved this problem roughly like this:

- When receiving a message from the Slider Control, the program accepts the new value, and then turns around and sets the new value in the TextField.
- When receiving a message from the TextField, the program similarly accepts the new value from the TextField and now updates the Slider.

The above pattern seems eminently reasonable. Up till now, this was how it was done [although certain toolkits didn't make even this simple approach very easy!].

However, now imagine a large program being implemented by several developers, and a graphical user interface that has hundreds, perhaps even thousands of Controls. All these controls manipulate a bunch of data in the application program.
We can see the problem with this approach: how is developer A supposed to know that the Dialog panel being designed by developer B is to reflect the new values for the variables being modified by developer A's code? Clearly, this problem can grow into a combinatorial nighmare.

The GUI Updating facility implemented in FOX can largely eliminate this problem. In a nutshell, the idea is that in FOX, the GUI Controls are bi-directional:

- A Control can notify the application program to inform it that the Control has been given a new value by the user. One could call this a ``push.''
- A Control can also interrogate the application about the current state of the application and its data structures, so that the Control may properly reflect this graphically. This one could call a ``pull.''

Why is this good? Because it compartmentalizes large scale GUI design, and simplifies coding. In the above example, developer B wouldn't even need to talk to developer A. He would simply implement [for each Control in his Dialog panel] not only the ``command'' messages which notify his routines about user inputs, but also the ``update'' messages by which the Controls ask for the values they should be displaying.

Coding complexity is reduced because instead of N command messages each updating M Controls [for a total of N x M combinations], the developer would only have to implement N command messages and M update messages [just N + M combinations]. Complexity is also reduced because command handlers just perform their operation on the application data structures, and update handlers simply update the corresponding controls given the state in which they found these data structures.

A common use of the GUI Updating mechanism is the disabling or ``graying-out'' of controls when they're not applicable under certain conditions. For example, a Save File Button may be made unavailable when the application hasn't loaded a file yet. Other common uses include hiding or showing of Controls in the GUI based on context or which ``mode'' an application is in.

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*){
      FXDC *dc=canvas->DC();

      // Erase the canvas
      dc->begin(canvas);
      dc->setForeground(FXRGB(255,255,255));
      dc->fillRectangle(0,0,canvas->getWidth(),canvas->getHeight());
      dc->end();

      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. In general however, we may not always know [the only thing we know is that the sender is of type FXObject]. In that a case, the GUI Update handler should send a message back to the sender. We can safely do this since all FOX objects are derived from FXObject, and FXObject's can be sent messages. This leads to the following code:

    // Update sender
    long ScribbleApp::onUpdClear(FXObject* sender,FXSelector,void*){
      sender->handle(this,dirty ? FXSEL(SEL_COMMAND,ID_ENABLE) : FXSEL(SEL_COMMAND,ID_DISABLE),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?

There are two sides to this question:- under what conditions can a Control be updated, and when is the updating actually done. With regard to the first part, a Control can be updated whenever it is not currently being manipulated by the user. In other words, a Control is normally in a ``pull'' mode, in that it tries to interrogate the application to determine the value it needs to display graphically. As soon as the user starts to manipulate the Control, it switches to a ``push'' mode, in which the Control becomes an input of new values to the application.

As far as the second part of the question goes, the FOX library performs the GUI Updates when there isn't much else to do, that is to say, the system is about to block and wait for events from the user. Furthermore, the GUI Updates are only performed when previous events have been dispatched and handled. This is why it is important to return 1 or 0 in your message handlers.:

- If you return 1, the message is considered handled, and a GUI Update pass will be performed by FOX.
- If you return 0, the message is considered unhandled, and since unhandled messages should not have changed the application's state, no GUI Update is performed.

For increased efficiency, the system checks for new events between each GUI Update message, to prevent ``event-deafness'' for extended periods of time. Even so, it is important to restrict your GUI Update message handlers to small routines, and to not perform any major computations in GUI Update message handlers.

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:

1. The Widget's target is NULL.
2. The Widget's target does not handle the Widget's SEL_UPDATE message.

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 show and sensitize the Widget that is the sender of the message, and 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.

Delayed Repaint/Layout

Procrastination is Good Thing [even though my parents always told me otherwise :-)]. The motto is the fastest thing you can do is nothing at all. Indeed, my lowly Pentium Classic can do ``nothing ''as fast as the fastest supercomputer!

All this seems pretty obvious, but what does it mean for GUI systems? It means we should try to avoid doing the two most expensive things that GUI systems have to do:

1. Drawing onto the screen. Drawing generates X protocol, and causes context switching between client and display server, and is therefore very expensive.
2. Layout of widgets. Layout involves recursing up and down the Widget Tree, and computing a bunch of stuff such as sizes of List contents, etc. In addition, layout also causes lots of stuff to be redrawn!

So it is clear that these two are to be avoided at all cost. Here's how FOX does this:

1. Expose or repaint events are stacked up until the system has processed all user input events. Moreover, exposed regions are compounded so that in most cases only one repaint is necessary.
2. Layout is performed during the GUI Update phase; in other words, layout is delayed similarly to repainting so that only a single big layout remains to be done at the end [I believe FOX is unique in this; having scrutinized many systems, I have not found any that incorporate this feature; I think it is therefore safe to say that I invented this...].
3. Size-caching is performed by certain [complex] Widgets to avoid potentially expensive size computations; for example, resizing a ScrollWindow does not necessarily change the size of the content.

So how well does this all work? It Really Rocks! The delayed painting is important, as it prevents stacking up huge piles of expose events when for example dragging [solid-, or opaque-dragging] windows over FOX applications. By NOT doing the unnecessary work, the system actually catches up more quickly, and never falls behind more than one repaint.

The delayed layout is responsible for the extremely fast startup times of FOX applications. As hundreds of Widgets are being added during construction of an application's GUI, recalculating layout after each addition really kills startup performance.
Delayed layout benefits performance each time many GUI Widgets are added or removed, or if layout hints are changed with widespread effects. It also makes opaque resizing [supported by a few X Window Managers] quite fast.

Several advanced GUI systems have added special freeze/thaw semantics to temporarily suspend layout calculations. In FOX, this feature is automatic, and application-wide in effect.

As the delayed layout is performed as part of the GUI Update process, 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.

One more speed-feature related to use of Brian Paul's excellent Mesa graphics library. With Mesa, double buffering is implemented using a software backbuffer. Thus, after having drawn this back buffer, an expose event can repaint the dirty area simply by blitting back this back buffer, instead of re-rendering OpenGL commands [which can be very expensive indeed!].

Hence, FOX distinguishes between ``synthetic'' repaint events and ``non-synthetic'' ones. Synthetic expose events are those that originate from within the application. When receiving a synthetic expose event, the OpenGL will have to be regenerated; for non-synthetic expose events, the back buffer can be blitted back.

Copyright © 1997-2005 Jeroen van der Zijp