Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Designing a Cross-Platform GUI


April 1995/Designing a Cross-Platform GUI

Designing a Cross-Platform GUI

Laszlo Zeke


Laszlo Zeke has been supervising, designing, and developing GUI-based applications over 12 years. He has an M.A. in mathematics. He resides in Herndon, Virginia, and is the Director of Kernel Product Engineering for INCODE Corporation. Laszlo can be reached via e-mail at [email protected] or via CompuServe at 76667,1776.

Introduction

Sooner or later you're bound to meet a customer who likes your GUI-based application but wants it to run in his own GUI environment, different from yours. You may wish to accomodate customers such as this, but find it too expensive to maintain different code sets on different platforms.

A common solution is to package the GUI-dependent pieces into an object library; the application will then use the objects implemented in this GUI library only, instead of the native GUI interfaces.

You have two basic choices in implementing this type of object library: either purchase a third-party multi-platform toolkit (there are plenty), or develop your own, using the native APIs. Both approaches have their merits, as well as proponents.

In this article I demonstrate the latter approach by implementing a GUI library using the native APIs of Windows/NT, OS/2, and X-Windows. This is just a sample library since the source code of a full-blown, commercial-strength library would run well over 10,000 lines — obviously beyond the space available here.

This article presents the class design for the platform-independent portion of the GUI, plus a sample application. I also briefly describe the platform-specific parts of the code. The platform-dependent code is included on this month's code disk, and is available from the on-line sources listed on page 3.

Overall Library Features

An application that uses this library will have the following capabilities:

  • It can have a number of windows.
  • Each window may have a different background color and frame decorations.
  • Each window can have a number of "children:" static text elements, buttons, entry fields, lines, and ellipses.
  • If a window is resized, its children are automatically resized.
  • The activation of every button and entry field causes the execution of an application-defined function.

Library Design

Ideally, I might have based my design solely on its ability to meet a given set of criteria, such as the features presented above. In reality, any design will be influenced by the tools at hand, in this case, the compilers available for the various platforms.

Tools

I selected the following tools for these three target environments:

  • Windows NT/Window 3.1: Visual C++, plus Win32s 1.2 (Win32s 1.2 is required to run applications built with this library under Windows 3.1)
  • OS/2: Borland C++ 1.5
  • AIX: Motif 1.2, X11R5: XL C++, the C++ compiler for the AIX/RISC-6000 machines
(Note: I am not suggesting that these tools are better than others; they are simply the tools I'm familiar with.)

The code presented in this article will compile and run on each of these platforms. Since error checking would double the size of the code, I have left it out entirely.

The General Application Framework

The library tries to make use of the common elements in the native toolkits. When there is nothing in common, the library tries to bridge the differences with the least possible effort.

Although each toolkit is different, any application that uses such a toolkit will follow the same general pattern, or framework:

1. some initialization of the toolkit

2. creation of one or more windows along with their children

3. processing messages in a loop that retrieves and dispatches different messages to the different windows and to their children based upon the user's actions

4. termination with some possible cleanup

To encapsulate this initialize-message loop-cleanup model, I introduce a class, GUI_APPLICATION (See Listing 1, a GUI-independent header file for the library). The application will create an object of this class; this object should be created first, and destroyed last in any application.

The main entities an application creates are windows. These are implemented in the class GUI_WINDOW. A window in this context has a frame, with some possible decorations (title, maximize button, sizable-border, etc.), and usually has one or more children. This library implements the child types static text, button, entry field, line, and ellipse. The corresponding classes are GUI_TEXT, GUI_BUTTON, GUI_ENTRY, GUI_LINE, and GUI_ELLIPSE.

These classes naturally fall into two groups: those implemented in the native toolkit as special native windows, and those that must be created by some drawing APIs. The two groups descend from two different base classes, since they behave differently in many respects. (For instance, most of the native windows are repainted automatically as needed, the other type are not.)

The two base classes are GUI_WINDOW_OBJECT and GUI_GRAPHICS_OBJECT. GUI_WINDOW_OBJECT is the common base class for GUI_TEXT, GUI_BUTTON, GUI_ENTRY, and GUI_WINDOW, whose objects are native windows. GUI_GRAPHICS_OBJECT is the common base class for GUI_LINE and GUI_ELLIPSE, whose objects must be drawn explicitly by the application.

Though they behave differently, these two base classes share some common elements. For instance, all of them have x and y coordinates, they have width and height, etc. To extract these and some other common characteristics, I introduce a common base class, GUI_OBJECT. Figure 1 shows the class heirarchy just described.

Processing User Input

Most of the objects mentioned above must be able to accept user input and execute some functions based upon it. For instance, the button objects must accept a LEFT MOUSE BUTTON SINGLE CLICK event and execute an application-defined function. There are two ways to accomplish this.

The first way is to store event-handler function pointers in the above-mentioned objects. The application will call the desired function through one of these pointers when a given event takes place. The other way to provide event responses is to create some virtual functions in the base class, to be overridden by an application-derived class. I chose the second approach, since it seems more modular and cleaner to me.

I provide a default LeftButtonSingleClick function for every class, which function does nothing. Thus, a mouse click on a button of type GUI_BUTTON causes no harm, but not much excitement either. If you derive a class MY_BUTTON from GUI_BUTTON, and override LeftButtonSingleClick, then this new overridden function will be called whenever a MY_BUTTON is clicked. I do not use C++'s virtual function call mechanism to determine which button was pushed. To do so would require a new derived button class for practically every button in the program. Rather, I find out which button was activated by introducing a numeric ID field for every GUI_OBJECT, enabling a switch statement to be based upon this ID.

These user input functions could be implemented in the GUI_OBJECT class, but I decided to implement them in a base class for GUI_OBJECT, CALLABLE_OBJECT. (If my class hierarchy were to grow, a new object class may not necessarily be derived from GUI_OBJECT, but I still might want to be able to process user input for that class.)

Display Features

My library assumes a window-relative MAX_X by MAX_Y coordinate system, with the upper left corner as (0,0). In such a coordinate system, a window with a width of MAX_X spans the whole width of the screen, while a button with a width of MAX_X spans the whole width of its parent window. The particular values for MAX_X and for MAX_Y are not really important but they should be finer than the actual display resolution. This library uses 10,000 for both MAX_X and MAX_Y, which is a safe choice for most contemporary displays.

Matching appropriate colors among the different GUI environment is a subject worth a book in itself. Therefore, I chose a very simple approach and implemented the 16 VGA colors. Font handling is another issue that naturally arises, but its complexity is beyond the scope of this article. (However, a subset of available fonts could be easily managed in all of the mentioned environments.)

Description of Classes

In this section I describe the platform-independent portion of the class library. Figure 1 shows the class hierarchy. The top three layers of the hierarchy consist of abstract classes. The CALLABLE_OBJECT class declares several functions for responding to user input, but these are all virtual functions, hence they must be overridden and "filled in" in descendant classes. The next lower class, GUI_OBJECT, introduces a data member id, which stores an ID number for each object. Every GUI_OBJECT must have a unique ID. I also briefly describe class GUI_APPLICATION here, though it is not a member of the hierarchy.

GUI_APPLICATION

The GUI_APPLICATION class has no descendants. It serves as a wrapper around the application and maintains the main message loop. A GUI_APPLICATION object has four basic functions: a constructor to initialize the GUI toolkit, a destructor to clean up, a MainLoop function to handle the main message loop, and a Terminate function which breaks the message loop. This class maintains a single-linked list of GUI_WINDOW objects, the windows of the application.

GUI_WINDOW_OBJECT

GUI_WINDOW_OBJECT serves as an abstract base class for GUI_WINDOW. A GUI_WINDOW object maintains a handle to its own native window, in protected member window of GUI_WINDOW_OBJECT. Each GUI_WINDOW object also maintains a single-linked list of GUI_OBJECT objects comprising its children, such as buttons, text elements, lines, etc. (Note that these "children" are not descendants of GUI_WINDOW; rather, they are children in the sense that they are owned by a GUI_WINDOW object.) When a GUI_WINDOW object is created it does not appear immediately, therefore the application has a chance to create its children first. Calling GUI_WINDOW's Show function finally makes it appear. GUI_WINDOW also has a function Message to put up a message box, and a function Question to ask the user a yes/no question. GUI_WINDOW's GetText/SetText function pair enables an application to dynamically get/set the title of its window.

GUI_TEXT, GUI_BUTTON, and GUI_ENTRY are descendants of GUI_WINDOW_OBJECT whose objects function as children of GUI_WINDOW objects. A GUI_TEXT object contains a non-modifiable piece of text to be displayed in the window. The user cannot change its contents. A GUI_BUTTON object represents a pushbutton. A GUI_ENTRY object represents a single-line, specified-text-length, autoscrolling entry field. These are the only two classes in this library that override the user-input functions declared in CALLABLE_OBJECT.

Specifically, a GUI_BUTTON object calls the following functions:

  • Activate. The button object calls this function if the user activates the button, either through the mouse or through the keyboard
  • LeftClick. Called when the GUI_BUTTON is clicked with the left mouse button
  • LeftDoubleClick. Called when the GUI_BUTTON is double-clicked with the left mouse button
  • RightClick. Called when the GUI_BUTTON is clicked with the right mouse button
  • RightDoubleClick. Called when the GUI_BUTTON is double-clicked with the right mouse button
A GUI_ENTRY object calls the Activate function if the entry field is losing its focus.

Supplying a height parameter of zero to a GUI_TEXT, GUI_BUTTON, or GUI_ENTRY object will cause it to be created with the default font height. It is also possible to dynamically get/set these objects' text via the GetText/SetText function pair.

GUI_GRAPHICS_OBJECT

A GUI_GRAPHICS_OBJECT is an abstract base class for objects that can't be represented as native windows. It has two descendants, GUI_ELLIPSE and GUI_LINE. A GUI_ELLIPSE object represents a filled/outlined color-specified ellipse. A GUI_LINE object represents a color-specified straight line.

Class Implementations

I've divided the source for this class library (about 750 lines on every platform) into two parts: gui.c (Listing 2) contains functions that don't use native APIs; native.c, provided separately for each platform, contains functions that do use native APIs. Due to space limitations, the three native.c files are not shown here, but are available on this month's code disk and CUJ online sources (see p. 3 for more information on online sources).

At a structural level, each native implementation shares some common features. For example, each implementation must perform some sort of registration of a window or application with the operating system. Each implementation must interact with a main message loop and respond to messages from the system. Finally, each implementation must explicitly draw graphic objects of type GUI_LINE and GUI_ELLIPSE, rather than rely upon the native system to do the work.

The greatest difference in implementations occurs between the Windows look-alikes (Windows 3.1, Windows NT, and OS/2) and X-Window. For example, X-Window requires an application to register callback functions for painting and resizing windows, while the look-alikes do this in response to system messages (e.g. WM_PAINT). Another significant difference appears in the way the two implementations manage to catch all mouse events for nongraphical windows. The Windows look-alikes do so by "subclassing the window," overriding the built-in window procedure for buttons. The X implementation catches such events by registering an event handler with the system.

More details, as well as documentation, are provided on this month's code disk.

A Short Demo

The application presented here (Listing 3) , while not extremely "useful," demonstrates how easy it is to build an application using the library. Figure 2 is a screen capture of the demo running on AIX/X-Windows. The application window's Exit button terminates the application, while its Next button creates an identical window, shifted a little to the upper left and displayed in a different color. The application window's entry field determines the title of the window. Both of the window's buttons react to right-mouse-button clicks with popup messages.

Conclusion

Constructing a multi-platform GUI is not a simple task. Many problems will naturally arise that this article does not address: drawing optimization; creation of menu bars, accelerator keys, and popup menus; implementation of drag and drop operations, and z-order for children of a window; font management and national language support, just to mention a few. Some of these features can be added with relative ease (menus, optimized drawing, z-order), while others may require considerable effort (font management, drag and drop). However, if you're the kind of programmer who likes to experiment, or "roll your own," this class library should give you a good place to start.

Information Sources

Borland C++ for 0S/2 Ver 1.5, on CD-ROM. Produced by Borland International, 1994.

Microsoft Development Platform, on CD-ROM. Produced by Microsoft Corp., 1995.

O'Reilly, Tim, ed. X series, Release 4 and Release 5, vols. 0 - 8. O'Really & Associates, 1990-1994.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.