Chapter 4. ViewKit Windows

This chapter introduces the basic ViewKit classes needed to create and manipulate the top-level windows in a ViewKit application: VkSimpleWindow and VkWindow. Figure 4-1 shows the inheritance graph for these classes.

Figure 4-1. Inheritance Graph for VkSimpleWindow and VkWindow

Figure 4-1 Inheritance Graph for VkSimpleWindow and VkWindow

Overview of ViewKit Window Support

This section describes how ViewKit supports multiple top-level windows in an application, and then describes the ViewKit classes that implement these windows.

ViewKit's Multi-Window Model

There are several possible models for multi-window applications in Xt. One approach is to create a single top-level window used as the main window of the application. All other windows are then popup shells whose parent is the main window. Another approach is to create a single shell that never appears on the screen. All other windows are then popup children of the main shell. In this model, all top-level windows are treated equally, as siblings. One window may logically be the top-level window of the application, but as far as Xt is concerned, all windows are equal.

ViewKit follows the second model. The VkApp class, described in Chapter 3, “The ViewKit Application Class,” creates a single widget that serves as the parent of all top-level windows created by the program. The VkApp base widget does not appear on the screen.

ViewKit Window Classes

All top-level windows in a ViewKit application must be instances of VkSimpleWindow, VkWindow, or a subclass of one of these classes. The VkSimpleWindow class supports a top-level window that does not include a menu bar. The VkWindow class, derived from VkSimpleWindow, adds support for a menu bar along the top of the window. You must create a separate instance of VkSimpleWindow, VkWindow, or a subclass of one of these classes for each top-level window in your application.

Instantiating a VkSimpleWindow or VkWindow object creates a popup shell as a child of the invisible shell created by your application's instance of VkApp. VkSimpleWindow and VkWindow also create a XmMainWindow widget as a child of the popup shell. You define the contents of a window by creating a widget or ViewKit component to use as the work area (or view) for the XmMainWindow widget. In most cases, you will create several widgets and/or ViewKit components as children of a container widget and then assign that container widget as the view of the XmMainWindow widget. “Creating the Window Interface” describes how to assign a view to a window. Figure 4-2 shows an example of a widget hierarchy for the top-level windows of a simple ViewKit application with two top-level windows.

Figure 4-2. Widget Hierarchy of Top-Level Windows in ViewKit Applications

Figure 4-2 Widget Hierarchy of Top-Level Windows in ViewKit Applications

In most cases, directly instantiating a VkSimpleWindow or VkWindow object is not appropriate.[5] In addition to the widgets and components composing the window's interface, most windows require other data and support functions. In accordance with good object-oriented programming style, the functions and data associated with a window should be contained within that window's class. Therefore, the best practice to follow when creating a ViewKit application is to create a separate subclass for each window in your application. You can derive these subclasses from VkWindow for those windows that require menu bars, and from VkSimpleWindow for those windows that do not. “Deriving Window Subclasses” describes in detail the process of deriving window subclasses.

In addition to creating shell and XmMainWindow widgets, the VkSimpleWindow and VkWindow classes set up various properties on the shell window and provide simple hooks for window manager interactions. “Window Manager Interface” discusses the built-in window manager support.

The VkSimpleWindow and VkWindow classes provide simple functions to raise, lower, iconify, and open windows, as described in “Manipulating Windows”. The classes also provide several convenience functions for determining a window's state (for example, whether it is visible, iconified, and so on) and for retrieving other window information. These access functions are described in “Window Data Access Functions”.

The VkSimpleWindow and VkWindow classes also register their windows with the application's VkApp instance to support application-wide services such as setting the cursor for all of an application's windows, entering busy states, and manipulating all windows in an application. Chapter 3, “The ViewKit Application Class,” describes how to use these application-wide services.

Window Class Constructors

The VkSimpleWindow and VkWindow constructors both have the same form:

VkSimpleWindow(const char *name,
               ArgList args = NULL,
               Cardinal argCount = 0)
 
VkWindow(const char *name,
         ArgList args = NULL,
         Cardinal argCount = 0)

Unlike most other ViewKit components, the VkSimpleWindow and VkWindow constructors do not require a parent widget as an argument: all ViewKit windows are automatically created as children of the invisible shell created by your application's instance of VkApp. You must specify a name for your window. Optionally, you can also provide a standard Xt argument list that the constructor will use when creating the window's popup shell.

Every application has a main window. By default, the first window you create is treated as the main window. To specify a different window to use as the main window, use the VkApp::setMainWindow() function described in “Managing Top-Level Windows”.

Because the first window you create is by default the main window, the window class constructors also set some shell resources on the popup shell widget of that window. The constructors obtain the geometry of the invisible application shell created by VkApp and assign that geometry to the window's popup shell widget. The constructors also set the XmNargc and XmNargv resources on the popup shell to the values of VkApp::argc() and VkApp::argv() respectively. (“Application Data Access Functions” describes VkApp::argc() and VkApp::argv().)

Finally, for all windows, the window class constructors register a callback function to handle messages from the window manager. The default action upon receiving a WM_DELETE_WINDOW message is to delete the window object. To change this behavior, override the handleWmDeleteMessage() member function as described in “Window Properties and Shell Resources”. The default action upon receiving a WM_QUIT_APP message is to quit the application. To change this behavior, override the handleWmQuitMessage() member function as described in “Window Properties and Shell Resources”.

Window Class Destructors

The VkSimpleWindow and VkWindow destructors delete all privately allocated data and destroy the views associated with the windows. The VkWindow destructor also destroys any menu bar associated with the window, no matter how you added it (see “Menu Bar Support”). If you created a subclass, you should provide a destructor to free any space that you explicitly allocated in the derived class.

The VkSimpleWindow and VkWindow destructors also remove the window from the application's list of windows. If this window is the only window still associated with the application (for example, if it is the only window created or all other windows have also been deleted), then your application automatically calls VkApp::terminate() to quit itself. “Quitting ViewKit Applications” describes VkApp::terminate().

Creating the Window Interface

There are three methods that you can use to create the contents of a window:

  • Create a subclass of VkSimpleWindow or VkWindow and define the interface in the class constructor.

  • Create a subclass of VkSimpleWindow or VkWindow and define the interface by overriding the virtual function setUpInterface().

  • Create an instance of VkSimpleWindow or VkWindow, define the interface separately, and then add the interface as the window's view.

These methods, and the advantages and disadvantages of each approach, are discussed in the following sections.

Creating the Window Interface in the Constructor

The preferred method of defining the contents of a window is to create the interface in the constructor of a VkSimpleWindow or VkWindow subclass. In this case, you simply create the widgets and components that you want to appear in your window in your subclass constructor. Remember that each window can have only one direct child widget as a view, so in most cases you must create a container widget and then create all other widgets and components as descendents of this direct child. Manage all widgets except the container widget, which you should leave unmanaged.

The parent widget of your view's top-level widget or component must be the window's XmMainWindow widget. You can retrieve this widget by calling the mainWindowWidget() function inherited from VkSimpleWindow. “Window Data Access Functions” discusses the mainWindowWidget() function.


Note: The _baseWidget data member for VkSimpleWindow and derived classes is the window's popup shell widget. Do not assign any other widget to this data member in a derived class.

After creating your interface, call addView():

void addView(Widget w)
void addView(VkComponent *component)

addView() accepts as an argument either a widget or a pointer to a component, which addView() installs as the view for the window.


Note: Some Motif functions such as XmCreateScrolledText(3Xm) create a ScrolledWindow widget and a child widget, and then return the ID of the child widget. As a convenience for using these functions, addView() can automatically determine the correct parent widget if you provide the child widget ID instead of the ScrolledWindow ID.

Example 4-1 shows a simple example that defines ScaleWindow, which creates a window with a RowColumn widget containing three Scale widgets. Because ScaleWindow is derived from VkSimpleWindow, it does not support a menu bar. If you required a menu bar, you would instead derive this class from VkWindow.

Note that ScaleWindow includes default resources for the Scale widget labels. This encapsulation technique is a good object-oriented practice to follow when creating reusable components in ViewKit. For example, if you were to extend this class by adding callback functions to the Scale widgets, you should make the callback functions members of the ScaleWindow class.

Example 4-1. Creating a Window Interface in the Class Constructor

///////////////////////////
// ScaleWindow.h
///////////////////////////
 
#include <Vk/VkSimpleWindow.h>
 
class ScaleWindow: public VkSimpleWindow {
 
  public:
    ScaleWindow (const char *);
    ~ScaleWindow();
    virtual const char* className();
 
  private:
    static String _defaultResources[];
};
 
///////////////////////////
// ScaleWindow.c++
///////////////////////////
 
#include "ScaleWindow.h"
#include <Xm/RowColumn.h>
#include <Xm/Scale.h>
 
String ScaleWindow::_defaultResources[] = {
    "*dayScale.titleString:    Days",
    "*weekScale.titleString:   Weeks",
    "*monthScale.titleString:  Months",
    NULL };
 
ScaleWindow::ScaleWindow (const char *name) : VkSimpleWindow (name)
{
    setDefaultResources(mainWindowWidget(), _defaultResources);
    
    Widget scales = XtCreateWidget("scales", xmRowColumnWidgetClass,
                                   mainWindowWidget(), NULL, 0);
    
    Widget dayScale = XtCreateManagedWidget("dayScale", xmScaleWidgetClass,
                                            scales, NULL, 0);
    XtVaSetValues(dayScale,
                  XmNorientation, XmHORIZONTAL,
                  XmNminimum, 1,
                  XmNmaximum, 7,
                  XmNvalue, 1,
                  XmNshowValue, TRUE,
                  NULL);
 
    Widget weekScale = XtCreateManagedWidget("weekScale", xmScaleWidgetClass,
                                             scales, NULL, 0);
    XtVaSetValues(weekScale,
                  XmNorientation, XmHORIZONTAL,
                  XmNminimum, 1,
                  XmNmaximum, 52,
                  XmNvalue, 1,
                  XmNshowValue, TRUE,
                  NULL);
    Widget monthScale = XtCreateManagedWidget("monthScale", xmScaleWidgetClass,
                                              scales, NULL, 0);
    XtVaSetValues(monthScale,
                  XmNorientation, XmHORIZONTAL,
                  XmNminimum, 1,
                  XmNmaximum, 12,
                  XmNvalue, 1,
                  XmNshowValue, TRUE,
                  NULL);
 
    addView(scales);    
}
 
ScaleWindow::~ScaleWindow()
{
    // Empty
}
 
const char* ScaleWindow::className()
{
    return "ScaleWindow";
}
 
///////////////////////////
// scaleApp.c++
///////////////////////////
 
#include "ScaleWindow.h"
#include <Vk/VkApp.h>
 
void main ( int argc, char **argv )
{
    VkApp *scaleApp = new VkApp("ScaleApp", &argc, argv);
    ScaleWindow *scaleWin = new ScaleWindow("scaleWin");
 
    scaleWin->show();
    scaleApp->run();
}

Running the scaleApp program shown above displays a ScaleWindow, as shown in Figure 4-3.

Figure 4-3. Simple Example of a VkSimpleWindow Subclass

Figure 4-3 Simple Example of a VkSimpleWindow Subclass

You can also create components and add them just as you would widgets. The constructor shown in Example 4-2 creates a VkRadioBox(3x) component and installs several items.

Example 4-2. Using a Component as a Window's View

///////////////////////////
// RadioWindow.h
///////////////////////////
 
#include <Vk/VkSimpleWindow.h>
 
class RadioWindow: public VkSimpleWindow {
 
  public:
    RadioWindow (const char *);
    ~RadioWindow();
    virtual const char* className();
  private:
    static String _defaultResources[];
};
///////////////////////////
// RadioWindow.c++
///////////////////////////
 
#include "RadioWindow.h"
#include <Vk/VkRadioBox.h>
 
String RadioWindow::_defaultResources[] = {
    "*color*label*labelString:  Color",
    "*red.labelString:    Red",
    "*green.labelString:  Green",
    "*blue.labelString:   Blue",
    NULL };
 
RadioWindow::RadioWindow (const char *name) : VkSimpleWindow (name)
{
    setDefaultResources(mainWindowWidget(), _defaultResources);
    
    VkRadioBox *rb = new VkRadioBox( "color", mainWindowWidget() );
 
    rb->addItem("red");
    rb->addItem("green");
    rb->addItem("blue");
 
    addView(rb);
}
RadioWindow::~RadioWindow()
{
    // Empty
}
 
const char* RadioWindow::className()
{
    return "RadioWindow";
}
 
 
///////////////////////////
// radioApp.c++
///////////////////////////
 
#include <Vk/VkApp.h>
#include "RadioWindow.h"
 
void main ( int argc, char **argv )
{
    VkApp *radioApp = new VkApp("RadioApp", &argc, argv);
    RadioWindow *radioWin = new RadioWindow("radioWin");
 
    radioWin->show();
    radioApp->run();
}

Running the radioApp program shown above displays a RadioWindow, as shown in Figure 4-4.

Figure 4-4. Using a Component as a Window's View

Figure 4-4 Using a Component as a Window's View

Creating the Window Interface in the setUpInterface() Function

When you create your window interface in your window constructor using addView(), all setup overhead occurs when the window is instantiated. Additionally, your program allocates memory for all of the widgets created. Occasionally, you might need to instantiate a window so that your application can access some of its public functions, but not display it. If the window interface is large or complex, the time and memory consumed to create the interface is unnecessary if the user might not display it.

The ViewKit window classes provide a mechanism for delaying the creation of a window's interface until the window needs to be displayed. Rather than including the interface code in the window constructor, you can include the code in the definition of the protected virtual member function setUpInterface().

When you call show() to display a window, show() checks to see whether you have already added a view to the window (for example, in the window's constructor). If not, show() calls setUpInterface() to create the window's interface.

Using this approach, you do not allocate memory for the window interface until your application actually displays the window for the first time—and you never allocate the memory if your application never displays the window. Additionally, this approach reduces your application's startup time. The trade-off is that the first time you display this window, the response time might be slow because your application must create the interface before displaying the window.

The syntax of setUpInterface() is as follows:

virtual Widget setUpInterface(Widget parent)

show() passes the main window widget to setUpInterface() for you to use as the parent of the window's widget hierarchy. You must return a widget to be added as a view. Do not call addView() from within setUpInterface().


Note: Some Motif functions such as XmCreateScrolledText(3Xm) create a ScrolledWindow widget and a child widget, and then return the ID of the child widget. As a convenience for using these functions, setUpInterface() can automatically determine the correct parent widget if you provide the child widget ID instead of the ScrolledWindow ID.

Example 4-3 shows the RadioWindow example from Example 4-2 rewritten to use setUpInterface() instead of addView() in the constructor.

Example 4-3. Creating a Window's Interface in the setUpInterface() Function

///////////////////////////
// RadioWindow2.h
///////////////////////////
 
#include <Vk/VkSimpleWindow.h>
 
class RadioWindow: public VkSimpleWindow {
 
  public:
    RadioWindow (const char *);
    ~RadioWindow();
    virtual const char* className();
 
  protected:
    Widget setUpInterface(Widget);
 
  private:
    static String _defaultResources[];
};
 
 
///////////////////////////
// RadioWindow2.c++
///////////////////////////
 
#include "RadioWindow2.h"
#include <Vk/VkRadioBox.h>
 
String RadioWindow::_defaultResources[] = {
    "*color*label*labelString:  Color",
    "*red.labelString:    Red",
    "*green.labelString:  Green",
    "*blue.labelString:   Blue",
    NULL };
 
RadioWindow::RadioWindow (const char *name) : VkSimpleWindow (name)
{
    // Empty
}
RadioWindow::~RadioWindow()
{
    // Empty
}
 
const char* RadioWindow::className()
{
    return "RadioWindow";
}
 
Widget RadioWindow::setUpInterface (Widget parent)
{
    setDefaultResources(mainWindowWidget(), _defaultResources);
    
    VkRadioBox *rb = new VkRadioBox( "color", parent );
 
    rb->addItem("red");
    rb->addItem("green");
    rb->addItem("blue");
 
    return(*rb);
}

Note that this example uses the Widget operator defined by VkComponent to return the VkRadioBox's base widget in setUpInterface(). (See “VkComponent Access Functions” for information on the Widget operator.) If you prefer, you could explicitly call baseWidget():

return( rb->baseWidget() );

Adding a Window Interface to a Direct Instantiation of a ViewKit Window Class

There are exceptional cases for which you may choose to directly instantiate a VkSimpleWindow or VkWindow object and use addView() to associate a view with the window. For example, if you have a complex, self-contained component and need a window simply to display the component, you might find this method acceptable. Example 4-4 shows a simple example of adding a component to a direct instantation of the VkSimpleWindow class.

Example 4-4. Adding a View to a Direct Instantiation of a ViewKit Window Class

VkSimpleWindow *roloWindow = VkSimpleWindow("roloWindow");
Rolodex *rolodex = Rolodex( "rolodex", roloWindow->mainWindowWidget() );
roloWindow->addView(rolodex);

In most cases, you should not use this technique because most windows require data and support functions that should be encapsulated by the window class to follow proper object-oriented programming style.

Replacing a Window's View

Occasionally, you might want to replace the view of an existing window. To do so, you must first remove the current view using the removeView() function:

void removeView()

You should not call this function unless you have previously added a view to this window. removeView() does not destroy the view; if you no longer need the view, you should destroy it.

After removing a view, you can add another view using addView().

Manipulating Windows

The VkSimpleWindow and VkWindow classes provide simple functions to show, hide, raise, lower, iconify, and open windows. All of the following functions take no arguments and have a void return value:

show() 

Displays the window. show() has no effect if the window is currently iconified.

hide() 

Removes the window from the screen.

iconify() 

Iconifies the window.

open() 

Opens the window if it is iconified.

raise() 

Raises the window to the top of the application's window stack.

lower() 

Lowers the window to the bottom of the application's window stack.

All of these functions are declared virtual. If you override them in a subclass, you should call the corresponding base class function after performing whatever operations your subclass requires.

Window Data Access Functions

The VkSimpleWindow and VkWindow classes support several data access functions:

  • mainWindowWidget() returns the XmMainWindow widget created by the window constructor. Most frequently, you use mainWindowWidget() to obtain a parent widget for creating a view widget or component. You can also use this function to access and configure the window's XmMainWindow widget. For example, by default, the ViewKit window classes configure the window's XmMainWindow widget not to display scrollbars. You can use mainWindowWidget() to obtain the XmMainWindow widget and then use XtSetValues(3Xt) to enable the scrollbars:

    virtual Widget mainWindowWidget() const
    

  • viewWidget() returns the widget currently installed as the window's view:

    virtual Widget viewWidget() const
    

  • visible() returns TRUE if the window is currently displayed and FALSE if it is hidden:

    Boolean visible() const
    

  • iconic() returns TRUE if the window is currently iconified and FALSE if it is not:

    Boolean iconic() const
    

  • getMenu() returns the VkMenuBar object or subclass used by the window that contains the given VkComponent. This allows components to add items to the menu bars of the window in which they are placed, without a hard-coded connection between the window and the component:

    static VkMenuBar *getMenu(VkComponent *object)
    

  • getWindow() returns the VkWindow object or subclass that contains the given VkComponent. This allows components to operate on the window that contains them, without a hard-coded connection between the window and the component:

    static VkSimpleWindow *getWindow(VkComponent *component)
    static VkWindow *getWindow(VkComponent *component)
    

  • getVisualState() returns the X11 window state (as specified by the Inter-Client Communication Conventions Manual, sections 4.1.2.4 and 4.1.4, with one extension):

    int getVisualState()
    

    The ICCCM specifies WithdrawnState, NormalState, and IconicState. In actuality, when an unmapped window is mapped, it may come back as either Normal or Iconic. Therefore, Viewkit adds the following states:

    • WithdrawnNormalState—Means that the window will be in NormalState when it is mapped (same as WithdrawnState).

    • WithdrawnIconicState—Means that the window will be in IconicState when it is mapped.

Window Manager Interface

The VkSimpleWindow and VkWindow classes set up various properties on the shell window and provide simple hooks for window manager interactions.

Window and Icon Titles

The VkSimpleWindow and VkWindow classes provide easy-to-use functions to set your application's window and icon titles.

The setTitle() function sets the title of a window:

void setTitle(const char *newTitle)

The string is treated first as a resource name that setTitle() looks up relative to the window. If the resource exists, its value is used as the window title. If the resource does not exist, or if the string contains spaces or newline characters, setTitle() uses the string itself as the window title. This allows applications to change a window title dynamically without hard-coding the exact title names in the application code. Example 4-5 shows an example of setting a window title using a resource value.

You can retrieve the current window title using getTitle():

const char *getTitle()

The setIconName() function sets the title of a window's icon:

void setIconName(const char *newTitle)

The string is treated first as a resource name that setIconName() looks up relative to the window. If the resource exists, its value is used as the window's icon title. If the resource does not exist, or if the string contains spaces or newline characters, setIconName() uses the string itself as the icon title. This allows applications to dynamically change a window's icon title without hard-coding the exact title names in the application code. Example 4-5 shows an example of setting a window's icon title using a resource value.

Example 4-5. Setting Window and Icon Titles Using Resource Values

class MainWindow : public VkSimpleWindow {
 
  public:
    MainWindow (const char *);
    // ...
 
  private:
    static String _defaultResources[];
    // ...
};
 
String _defaultResources[] = {
    "*winTitle:    Foobar Main Window",
    "*iconTitle:   Foobar",
    NULL
};
 
MainWindow::MainWindow(const char *name) : VkSimpleWindow(name)
{
    setDefaultResources(mainWindowWidget(), _defaultResources);
 
    setTitle("winTitle");
    setIconName("iconTitle");
 
    // ...
}


Window Properties and Shell Resources

The window class constructors automatically set up various window properties and shell resources when you create a window. The window classes also provide some hooks to allow you to set your own properties or change the window manager message handling in a derived class.

Because the first window you create is by default the main window, the window class constructors also set some shell resources on the popup shell widget of that window. The constructors obtain the geometry of the invisible application shell created by VkApp and assign that geometry to the window's popup shell widget. The constructors also set the XmNargc and XmNargv resources on the popup shell to the values of VkApp::argc() and VkApp::argv(), respectively. (“Application Data Access Functions” describes VkApp::argc() and VkApp::argv().)

For all windows, the window class constructors register a callback function to handle WM_DELETE_WINDOW messages from the window manager. This callback function calls handleWmDeleteMessage():

virtual void handleWmDeleteMessage()

By default, handleWmDeleteMessage() calls the window's okToQuit() function. If okToQuit() returns TRUE, then handleWmDeleteMessage() deletes the window. You can override handleWmDeleteMessage() to change how your window handles a WM_DELETE_WINDOW message. In most cases, you should simply perform any additional actions that you desire and then call the base class's handleWmDeleteMessage() function.

The window class constructors also register a callback function to handle WM_QUIT_APP messages from the window manager. This callback function calls handleWmQuitMessage():

virtual void handleWmQuitMessage()

By default, handleWmQuitMessage() calls the application's quitYourself() function to quit the application. You can override handleWmQuitMessage() to change how your windows handles a WM_QUIT_APP message. In most cases, you should simply
perform any additional actions that you desire and then call the base class's handleWmQuitMessage() function to exit your application.

If you want to set any additional properties on a window, you can override setUpWindowProperties():

virtual void setUpWindowProperties()

setUpWindowProperties() is called after realizing a window's popup shell widget but before mapping it. Subclasses that wish to store other properties on windows can override this function and perform additional actions. If you override this function, you should set all desired properties and then call the base class's setUpWindowProperties() function.

Note that you should use setUpWindowProperties() to set window properties instead of VkComponent::afterRealizeHook() as described in “Displaying and Hiding Components”. The difference between the two is that setUpWindowProperties() is guaranteed to be called before the window manager is notified of the window's existence. Because of race conditions, this might not be true of afterRealizeHook().

You can also change the value of the window manager class hint stored on a window using setClassHint():

void setClassHint(const char *className)

setClassHint() sets the class resource element of the XA_WM_CLASS property stored on this window to the string you pass as an argument.

Menu Bar Support

The VkSimpleWindow class is useful for windows that require only a work area; however, windows frequently require menus. The VkWindow class extends the VkSimpleWindow class by providing support for a menu bar along the top of the window.

In ViewKit, the VkMenuBar(3x) class provides support for menu bars. Chapter 5, “Creating Menus With ViewKit,” describes in depth the process of creating and manipulating menus; “Menu Bar” describes additional functions specific to the VkMenuBar class and provides an example of constructing a menu bar for an application. This section describes only those functions provided by VkWindow for installing and manipulating a menu bar.

You install a menu bar using setMenuBar():

void setMenuBar(VkMenuBar *menuObj)
void setMenuBar(VkMenuDesc *menudesc)
void setMenuBar(VkMenuDesc *menudesc, XtPointer clientData)

If you provide a pointer to an existing VkMenuBar object, setMenuBar() installs that menu bar. If you provide a VkMenuDesc static menu description, setMenuBar() creates a menu bar from that description and then installs the menu bar.

The third version listed above allows you to control the default client data. Using this version, you can pass in zero as the client data. However, there is no way to distinguish between an explicit zero that you pass in and the zero value resulting from not initializing the client data in the VkMenuDesc structure.

Once you have installed a menu bar, menu() will return a pointer to the menu bar object:

virtual VkMenuBar *menu() const

You can add a menu pane to the menu bar using addMenuPane():

VkSubMenu *addMenuPane(const char *name)
VkSubMenu *addMenuPane(const char *name, VkMenuDesc *menudesc)

addMenuPane() creates a VkSubMenu(3x) object and adds it to the window's menu bar. If you provide a VkMenuDesc static menu description, addMenuPane() uses it to create the menu pane. Additionally, addMenuPane() automatically creates and installs a menu bar if the window does not currently have one.

You can add a menu pane that enforces radio behavior on the toggle items it contains by using addRadioMenuPane():

VkRadioSubMenu *addRadioMenuPane(const char *name)
VkRadioSubMenu *addRadioMenuPane(const char *name,
                                 VkMenuDesc *menudesc)

addRadioMenuPane() creates a VkRadioSubMenu(3x) object and adds it to the window's menu bar. If you provide a VkMenuDesc static menu description, addRadioMenuPane() uses it to create the menu pane. Additionally, addRadioMenuPane() automatically creates and installs a menu bar if the window does not currently have one.

Deriving Window Subclasses

This section summarizes how to create subclasses from the ViewKit window classes. It describes additional virtual functions and data members not covered in previous sections, provides a window creation checklist, and shows an example of deriving a window subclass.

Additional Virtual Functions and Data Members

In addition to those functions described in previous sections, the ViewKit window classes provide a number of virtual functions and data members that you can access from window subclasses. These functions and data allow you to

  • provide a “safe quit” mechanism for your window

  • determine your window's state and perform actions on state changes

  • perform actions after realizing a window

  • handle raw events not normally handled by the Xt dispatch mechanism

Providing a “Safe Quit” Mechanism

The VkComponent class provides the virtual function okToQuit() to support “safe quit” mechanisms:

virtual Boolean okToQuit()

A component's okToQuit() function returns TRUE if it is “safe” for the application to quit. For example, you might want okToQuit() to return FALSE if a component is in the process of updating a file. By default, okToQuit() always returns TRUE; you must override okToQuit() for all components that you want to perform a check before quitting. Usually, only VkSimpleWindow and its subclasses use okToQuit().

When you call VkApp::quitYourself(), VkApp calls the okToQuit() function for all registered windows before quitting. If the okToQuit() function for any window returns FALSE, the application does not exit. (“Quitting ViewKit Applications” describes VkApp::quitYourself().)

Also, the window's handleWmDeleteMessage() function calls okToQuit() when the window receives a WM_DELETE_WINDOW message from the window manager. This determines whether it is safe to delete the window. (“Window Properties and Shell Resources” describes handleWmDeleteMessage().)

If you want to perform a test to see whether it is safe to delete a window, override the window's okToQuit() function. If you want to check one or more components contained within a window, you can override the window's okToQuit() function so that it calls the okToQuit() functions for all the desired components. You can then override the okToQuit() functions for the other components so you can perform whatever checks or shutdown actions are necessary. For example, you could post a blocking dialog asking whether the user wants to save data before quitting. (Chapter 7, “Using Dialogs in ViewKit,” describes how to use ViewKit dialogs.)

Determining Window States

The ViewKit window classes provide the following protected data members for determining the current states of a window:

IconState _iconState 


Contains an enumerated constant of type IconState that describes the current iconification state of the window. This variable contains OPEN if the window is not iconified, CLOSED if it is iconified, and ICON_UNKNOWN if it is in an unknown state. (Typically, the unknown state is used only internally to the VkSimpleWindow class.)

VisibleState _visibleState 


Contains an enumerated constant of type VisibleState that describes the current visibility state of the window. This variable contains VISIBLE if the window is visible, HIDDEN if it is not visible, and VISIBLE_UNKNOWN if it is in an unknown state. (Typically, the unknown state occurs only before you add a view to your window.)

StackingState _stackingState 


Contains an enumerated constant of type StackingState that describes the current stacking state of the window relative to the application. This variable contains RAISED if the window is at the top of the application's window stack, LOWERED if it is at the bottom of the window stack, and STACKING_UNKNOWN if it is in an unknown state (the state before you make any calls to raise() or lower() on this window).

If you need to perform any operations when your window changes its iconification state, you can override stateChanged():

virtual void stateChanged(IconState newState)

stateChanged() is called whenever the window's iconification state changes, whether programmatically (by calls to iconify() and open()) or through window manager interaction. Because this function is responsible for maintaining the window's state information, if you override this function in a subclass you should call the base class's stateChanged() function before performing any additional operations.

Performing Actions After Realizing a Window

If you want to perform certain actions only after a window exists, you can override the afterRealizeHook() function inherited from VkComponent:

virtual void afterRealizeHook()

Note that you should use setUpWindowProperties() to set window properties instead of afterRealizeHook(). The difference between afterRealizeHook() and setUpWindowProperties() is that setUpWindowProperties() is guaranteed to be called before the window manager is notified of the window's existence. Because of race conditions, this might not be true of afterRealizeHook(). afterRealizeHook() is appropriate for performing actions that do not affect the window's interaction with the window manager.

Handling Raw Events

You can handle events not normally handled by the Xt dispatch mechanism by overriding the window's handleRawEvent() function:

virtual void handleRawEvent(XEvent *event)

As described in “ViewKit Event Handling”, VkApp::run() supports events not normally handled by the Xt dispatch mechanism. For example, VkApp::run() can handle client messages and events registered for non-widgets (such as a PropertyNotify event on the root window).

When run() receives an event not handled by the Xt dispatch mechanism, it calls the virtual function VkApp::handleRawEvent(), which passes the event to the handleRawEvent() function of each instance of VkSimpleWindow (or subclass) in the application. By default, these member functions are empty.

If you want a window to handle events through this mechanism, call XSelectInput(3X) to select the events that you want to receive, and override handleRawEvent() in the VkSimpleWindow subclass to implement your event processing.

Additional Data Members

The ViewKit window classes also provide the protected data member _mainWindowWidget:

Widget _mainWindowWidget

_mainWindowWidget contains the XmMainWindow widget created by the window constructor. In a subclass, you can use this data member instead of calling mainWindowWidget(), although this is not recommended.

Window Creation Summary

The following is a summary of guidelines for creating subclasses of the ViewKit window classes:

  • Decide whether this window requires a menu bar. If it does, derive your subclass from VkWindow; otherwise, derive it from VkSimpleWindow.

  • In most cases where you provide a menu bar for your window, you should create it in the window class when you create the rest of your window's interface.

  • Determine whether users will often use your application without displaying this window even after the object is instantiated. If so, and the window interface is large or complex, you might consider creating the window interface using setUpInterface() to reduce the time it takes to start your application; otherwise, create the interface in the window's constructor.

  • Implement the window interface as a single-rooted widget subtree whose parent is the window's XmMainWindow widget (obtained by the mainWindowWidget() function). While some windows might contain only a single complex component, the majority of windows must create some type of container widget as the root of the window's interface; all other widgets and components are descendents of this widget.

  • Do not assign any widget to the _baseWidget data member. The ViewKit window classes assign the window's popup shell widget to _baseWidget.

  • Wherever appropriate, use resource values to set labels, other interface characteristics, and user-configurable component behavior. Define a default resource list as a static member variable of your window class, and call setDefaultResources() to set your window's default resources before creating the window interface.

  • Override the className() function to return the name of your window's class.

  • In addition to the widgets and components composing the window's interface, encapsulate any other required data and support functions as members of your window class.

  • If you explicitly allocate any memory in your derived window class, remember to free it in the window's destructor.

  • To explicitly set your window's title or its icon's title, call setTitle() or setIconName() respectively. You can also set these characteristics using the normal resource mechanisms.

  • To provide a “safe quit” mechanism for your window, override okToQuit() to perform any checking you want to perform before deleting the window.

  • To change how your window handles a WM_DELETE_MESSAGE from the window manager, override handleWmDeleteMessage().

  • To change how your window handles a WM_QUIT_APP from the window manager, override handleWmQuitMessage().

  • To set any additional properties on your window, override setUpWindowProperties().

  • To change the value of the window manager class hint stored on a window, call setClassHint().

  • To perform certain actions only after the window exists, override afterRealizeHook().

  • To handle events not normally handled by the Xt dispatch mechanism, call XSelectInput(3X) to select the events that you want to receive, and override handleRawEvent() in your window subclass to implement your event processing.

Window Subclassing

The program in Example 4-6 creates ColorWindow, a VkSimpleWindow subclass that implements a simple utility for determining the results of mixing primary ink colors when printing. The user can use toggles to select any of the three primary colors—cyan, magenta, and yellow—and the window reports the resulting color.

Figure 4-5 shows the widget hierarchy of the ColorWindow subclass. The VkSimpleWindow constructor creates the window's popup shell and XmMainWindow widget. The ColorWindow constructor creates a Form widget to serve as the window's view. The constructor adds a VkCheckBox component as a child of the Form to provide the toggle buttons. The constructor then adds a Frame widget as a child of the Form widget, and creates two Label gadgets as children of the Frame: one to serve as a title, and one to report the resulting color. The constructor manages all of these widgets except for the top-level Form widget. (The constructor manages the VkCheckBox component by calling its show() member function.)

Figure 4-5. Widget Hierarchy of ColorWindow Subclass

Figure 4-5 Widget Hierarchy of ColorWindow Subclass

This example illustrates a number of object-oriented techniques that you should follow when programming in ViewKit. Note that all data and utility functions used by the window are declared as members of the ColorWindow class. Also note that ColorWindow uses resources to set all the text that it displays. It includes a set of default values, but you can override these values in a resource file (for example, to provide German-language equivalents for all the strings).

Example 4-6. Creating a Window Subclass

///////////////////////////
// ColorWindow.h
///////////////////////////
 
#include <Vk/VkSimpleWindow.h>
#include <Vk/VkCheckBox.h>
 
class ColorWindow: public VkSimpleWindow {
 
  public:
    ColorWindow (const char *);
    ~ColorWindow();
    virtual const char* className();
 
  private:
    void displayColor(char *);
    void colorChanged(VkCallbackObject *, void *, void *);
    static String _defaultResources[]; // Default resource values
    static String _colors[];           // Array of possible resulting colors
    Widget _resultColor;               // Label to display resulting color
    VkCheckBox *_primaries;            // Checkbox for setting colors
    int _colorStatus;                  // Bit-wise color status variable
                                       //     Bit 0: Cyan
                                       //     Bit 1: Magenta
                                       //     Bit 2: Yellow
                                       //   Also used as index into _colors[]
};
 
 
///////////////////////////
// ColorWindow.c++
///////////////////////////
 
#include "ColorWindow.h"
#include <Xm/RowColumn.h>
#include <Xm/Form.h>
#include <Xm/Frame.h>
#include <Xm/LabelG.h>
#include <Vk/VkCheckBox.h>
#include <Vk/VkResource.h>
// Default ColorWindow class resource values.
 
String ColorWindow::_defaultResources[] = {
    "*windowTitle:                  Color Mixer",
    "*iconTitle:                    Color Mixer",
    "*primaries*label*labelString:  Primary Colors",
    "*cyan.labelString:             Cyan",
    "*magenta.labelString:          Magenta",
    "*yellow.labelString:           Yellow",
    "*resultLabel.labelString:      Resulting Color",
    "*cyan:                         Cyan",
    "*magenta:                      Magenta",
    "*yellow:                       Yellow",
    "*blue:                         Blue",
    "*red:                          Red",
    "*green:                        Green",
    "*white:                        White",
    "*black:                        Black",
    NULL };
 
// Set _colors array to correspond to color values indicated by the
// bits in the _colorStatus variable.
 
String ColorWindow::_colors[] = {
    "white",
    "cyan",
    "magenta",
    "blue",
    "yellow",
    "green",
    "red",
    "black" };
 
ColorWindow::ColorWindow (const char *name) : VkSimpleWindow (name)
{
    Arg args[5];
    int n;
 
    // Set default resources for the window.
 
    setDefaultResources(mainWindowWidget(), _defaultResources);
 
    // Create a Form widget to use as the window's view.
 
    Widget _form = XmCreateForm(mainWindowWidget(), "form", NULL, 0);
 
    // Create a VkCheckBox object to allow users to select primary colors.
    // Add toggle buttons and set their intial values to FALSE (unselected).
    // The labels for the checkbox frame and the toggle buttons are set
    // by the resouce database.
    
    _primaries = new VkCheckBox( "primaries", _form );
    _primaries->addItem("cyan", FALSE);
    _primaries->addItem("magenta", FALSE);
    _primaries->addItem("yellow", FALSE);
    _primaries->addCallback(VkCheckBox::itemChangedCallback, this,
                            (VkCallbackMethod) &ColorWindow::colorChanged);
    _primaries->show();
    
    // Set constraint resources on checkbox's base widget.
    
    n = 0;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetValues(_primaries->baseWidget(), args, n);
 
    // Create a frame to display the name of the resulting blended color.
 
    n = 0;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNleftWidget, _primaries->baseWidget()); n++;
    Widget _result = XmCreateFrame(_form, "result", args, n);
    XtManageChild(_result);
    
    // Create a frame title label.  The label text is set by the resource
    // database.
    
    n = 0;
    XtSetArg(args[n], XmNchildType, XmFRAME_TITLE_CHILD); n++;
    Widget _resultLabel = XmCreateLabelGadget( _result, "resultLabel", args, n);
 
    // Create the label to display the blended color name.
    
    _resultColor = XmCreateLabelGadget( _result, "resultColor", NULL, 0);
    
    // Set intial value of _colorStatus and label string to white (all off).
    
    _colorStatus = 0;
    displayColor(_colors[_colorStatus]);
    
    XtManageChild(_resultLabel);
    XtManageChild(_resultColor);
 
    // Add the top-level Form widget as the window's view.
 
    addView(_form);
    
    // Set the window title and the icon title.
    
    setTitle("windowTitle");
    setIconName("iconTitle");
}
ColorWindow::~ColorWindow()
{
    // Empty
}
 
const char* ColorWindow::className()
{
    return "ColorWindow";
}
 
// Given a color name, update the label to display the color
 
void ColorWindow::displayColor(char *newColor)
{
    Arg args[2];
    int n;
 
    // Common resource trick in ViewKit applications.
    // Given a string, check the resource database for a corresponding
    // value.  If none exists, use the string as the value.
 
    char *_colorName = (char *) VkGetResource(_baseWidget, newColor, "Color",
                                              XmRString, newColor);
 
    // Update the label
 
    XmString _label = XmStringCreateSimple(_colorName);
    n = 0;
    XtSetArg(args[n], XmNlabelString, _label); n++;
    XtSetValues(_resultColor, args, n);
    XmStringFree(_label);
}
 
// When the user changes the value of one of the toggles, update the
// display to show the new blended color.
 
/* ARGSUSED */
void ColorWindow::colorChanged(VkCallbackObject *obj, void *, void *callData)
{
    int index = (int) (prtdiff_t)callData;
    
    // Update color status based on toggle value.  Set or rest the
    // status bit corresponding to the respective toggle.
 
    if (_primaries->getValue(index))
        _colorStatus |= 1<<index;
    else
        _colorStatus &= ~(1<<index);
 
    // Update the display to show the new blended color, using
    // _colorStatus as an index.
 
    displayColor(_colors[_colorStatus]);
}
 
///////////////////////////
// colors.c++
///////////////////////////
 
#include <Vk/VkApp.h>
#include "ColorWindow.h"
 
void main ( int argc, char **argv )
{
    VkApp *colorApp = new VkApp("ColorApp", &argc, argv);
    ColorWindow *colorWin = new ColorWindow("colorWin");
    colorWin->show();
    colorApp->run();
}

Figure 4-6 shows the ColorWindow window displayed by the colors program.

Figure 4-6. ColorWindow Window Subclass

Figure 4-6 ColorWindow Window Subclass



[5] There are exceptional cases for which you might choose to directly instantiate a VkSimpleWindow or VkWindow object and then associate a view with the window. For example, if you have a complex, self-contained component and need a window simply to display the component, you might find this method acceptable. “Adding a Window Interface to a Direct Instantiation of a ViewKit Window Class” describes how to do this.