Chapter 2. Components

This chapter introduces the concept of ViewKit components: C++ classes that encapsulate sets of widgets along with convenient methods for their manipulation.

This chapter describes two ViewKit classes: VkCallbackObject and VkComponent. Figure 2-1 shows the inheritance graph for these classes.

Figure 2-1. Inheritance Graph for VkCallbackObject and VkComponent

Figure 2-1 Inheritance Graph for VkCallbackObject and VkComponent

Definition of a Component

Widget sets such as Motif provide simple, low-level building blocks, like buttons, scrollbars, and text fields. However, to create interesting and useful applications, you must build collections of widgets that work together to perform given tasks. For example, many applications support a system of menus, which are constructed from several individual widgets. Just as the user thinks of the menu bar as a single logical component of the user interface, ViewKit builds abstractions that let applications deal with a “menu” rather than the individual pieces of the menu.

C++ allows you to do exactly this: to encapsulate collections of widgets and other objects as logical entities. By creating C++ classes and providing simple, convenient manipulation functions, you can avoid the complexity of creating widgets, specifying widget locations, setting resources, assigning callbacks, and other common tasks. Furthermore, for commonly used objects like menus, you can design general-purpose classes that you can easily use in many different applications.

In ViewKit, the general user interface classes are referred to as components. A component not only encapsulates a collection of widgets, but also defines the behavior of the overall component. ViewKit components are designed to implement as many commonly used features as possible. Typically, all you need to do to use a ViewKit component is create a subclass of the appropriate ViewKit class and define any application-specific behavior. Furthermore, using the ViewKit classes as a base, you can create your own library of reusable components.

VkComponent Class

All ViewKit components are derived from the abstract base class VkComponent, which defines a basic structure and protocol for all components. When creating your own components, you should also derive them from VkComponent or one of its subclasses.

The VkComponent class enforces certain characteristics on components and expects certain behaviors of its subclasses. These characteristics and the features provided by VkComponent are discussed in detail in throughout this chapter; the more important characteristics are summarized below:

  • Widgets encapsulated by a component must form a single-rooted subtree. Components typically use a container widget as the root of the subtree; all other widgets are descendents of this widget. The root of the widget subtree is referred to as the base widget of the component.

  • You can create instances of components and use them in other components's widget subtrees. As a convenience, VkComponent defines an operator that allows you to pass a VkComponent object directly to functions that expect a widget. This operator is described further in “VkComponent Access Functions”.

  • Components take a string as an argument (typically, the first argument) in the class constructor. This string is used as the name component's base widget. You should give each instance of a component a unique name so that you can identify each widget in an application by a unique path through the application's widget tree. If each widget can be uniquely identified, X resource values can be used to customize the behavior of each widget. ViewKit resource support is described in “Component Resource Support”.

  • Components take a widget as an argument (typically, the second argument) in the class constructor. This widget is the parent of the component's base widget. Component constructors are discussed in “Component Constructors”.

  • Most components should create the base widget and all other widgets in the class constructor. The constructor should manage all widgets except the base widget, which should be left unmanaged. You can then manage or unmanage a component's entire widget subtree using the member functions described in “Displaying and Hiding Components”.

  • VkComponent provides an access function that retrieves the component's base widget. You might need to access the base widget, for example, to set constraint resources so that an XmForm widget can position the component. Normally, other widgets inside a component aren't exposed. Access functions are discussed in “VkComponent Access Functions”.

  • Components must handle the destruction of widgets within the component's widget tree. The widgets encapsulated by the component must be destroyed when the component is destroyed. Component classes must also prevent dangling references by handling destruction of the widget tree without destruction of the component. VkComponent provides mechanisms for handling widget destruction, which are described in “Handling Component Widget Destruction”.

  • Components should define any Xt callbacks required by a class as private static member functions. Using Xt callbacks in ViewKit is discussed in “Using Xt Callbacks With Components”.

  • All component classes must override the virtual className() member function so that it returns a string identifying the component's class. ViewKit uses this string for resource handling and other support functions. The className() member function is described in more detail in “VkComponent Access Functions”. “Component Resource Support” describes ViewKit resource support.

Component Constructors

The VkComponent constructor has the following form:

VkComponent(const char *name)

The VkComponent constructor is declared protected and so can be called only from derived classes. Its primary purpose is to initialize component data members, in particular _name and _baseWidget.

Each component should have a unique name, which is used as the name of the component's base widget. The VkComponent constructor accepts a name as an argument, creates a copy of this string, and assigns the address of the copy to the _name data member.

The _baseWidget data member is the base widget of the component's widget subtree. The VkComponent constructor initializes _baseWidget to NULL.

Each derived class's constructor should take at least two arguments—the component's name and a widget that serves as the parent of the component's widget tree—and perform at least these initialization steps:

  1. Pass the name to the VkComponent constructor to initialize the basic component data members.

  2. Create the component's widget subtree and assign the base widget to the _baseWidget data member. The base widget should be a direct child of the parent widget passed in the constructor, and should have the same name as the component (as stored in _name) for the ViewKit resource support to work correctly. All other widgets in the component must be children or descendents of the base widget.

  3. Immediately after creating the base widget, call installDestroyHandler() to set up a callback to handle widget destruction. This function is described further in “Handling Component Widget Destruction”.

  4. Manage all widgets except the base widget, which should be left unmanaged.

  5. Perform any other needed class initialization.

As an example, consider a user-defined component called StartStopPanel that implements a simple control panel containing Start and Stop buttons. The code fragment in Example 2-1 shows a possible constructor for this class.

Example 2-1. Component Constructor

/////////////////////////////
// StartStopPanel.h
/////////////////////////////
 
// Declare StartStopPanel as a subclass of VkComponent
 
class StartStopPanel: public VkComponent {
 
  public:
    StartStopPanel (const char *, Widget);
    ~StartStopPanel();
    // ...
 
  protected:
    Widget _startButton;
    Widget _stopButton;
    // ...
}
 
/////////////////////////////
// StartStopPanel.c++
/////////////////////////////
 
 
// Pass the name to the VkComponent constructor to initialize the
// basic component data members.
 
StartStopPanel::StartStopPanel(const char *name, Widget parent):VkComponent(name)
{
  // Create an XmRowColumn widget as the component's base widget
  // to contain the buttons. Assign the widget to the _baseWidget
  // data member.
  _baseWidget = XmCreateRowColumn ( parent, _name, NULL, 0 );
  // Set up callback to handle widget destruction
 
  installDestroyHandler();
 
  XtVaSetValues(_baseWidget, XmNorientation, XmHORIZONTAL, NULL);
 
  // Create all other widgets as children of the base widget.
  // Manage all child widgets.
 
  _startButton = XmCreatePushButton ( _baseWidget, "start", NULL, 0);
  _stopButton  = XtCreatePushButton ( _baseWidget, "stop", NULL, 0);
 
  XtManageChild(_startButton);
  XtManageChild(_stopButton);
 
  // Perform any other initialization needed (omitted in this example)
}

In this example, the StartStopPanel constructor passes the name argument to the VkComponent constructor to initialize the _name data member. The VkComponent constructor also initializes the _baseWidget data member to NULL. It then creates a RowColumn widget as the base widget to manage the other widgets in the component. The constructor uses the _name data member as the name of the base widget, uses the parent argument as the parent widget, and assigns the RowColumn widget to the _baseWidget data member. Immediately after creating the base widget, the constructor calls installDestroyHandler(). Then, it creates the two buttons as children of the base widget and manages the two child widgets.

A real constructor would then perform all other initialization needed by the class, such as setting up callbacks for the buttons and initializing any other data members that belong to the class. “Using Xt Callbacks With Components” describes how you should set up Xt callbacks when working with ViewKit components.

Component Destructors

The virtual VkComponent destructor performs the following functions:

  1. Triggers the VkComponent::deleteCallback ViewKit callback for that component. ViewKit callbacks are described in “ViewKit Callback Support”, and the VkComponent::deleteCallback is described in “Predefined ViewKit Callbacks”.

  2. Removes the widget destruction handler described in “Handling Component Widget Destruction”.

  3. Destroys the component's base widget, which in turn destroys the component's entire widget subtree.

  4. Frees all memory allocated by the VkComponent constructor.

  5. Sets to NULL all the data members defined by the VkComponent constructor.

The destructor for a derived class need free only the space that was explicitly allocated by the derived class, but of course it can perform any other cleanup your class requires.

For example, if your class allocates space for a string, you should free that space in your destructor, as shown in Example 2-2.

Example 2-2. Freeing Space in a Component Destructor

MyComponent: public VkComponent {
 
  public:
    MyComponent(const char *, Widget);
    ~MyComponent();
    // ...
 
  private:
    char *_label;
    //...
}
MyComponent::MyComponent(const char *name, Widget parent) : VkComponent(name)
{
    _label = strdup( label );
   // ...
}
 
MyComponent::~MyComponent()
{
    free ( _label );
}

Even if you don't need to perform any actions in a class destructor, you should still declare an empty one. If you don't explicitly declare a destructor, the C++ compiler creates an empty inline destructor for the class; however, because the destructor in the base class, VkCallbackObject, declares the destructor as virtual, the C++ compiler generates a warning because a virtual member function can't be inlined. The compiler then “un-inlines” the destructor and, to ensure that it's available wherever needed, puts a copy of it in every file that uses the class. Explicitly creating an empty destructor for your classes avoids this unnecessary overhead.

VkComponent Access Functions

VkComponent provides access functions for accessing some of the class's data members.

The name() function returns the name of a component as pointed to by the _name data member. This is the same as the name that you provided in the component's constructor. The syntax of the name() function is

const char * name() const

The className() function returns a string identifying the name of the ViewKit class to which the component belongs. The syntax of className() is

virtual const char *className()

All component classes should override this virtual function to return a string that identifies the name of the component's class. ViewKit uses this string for resource handling and other support functions. The class name for the VkComponent class is “VkComponent.”

For example, if you create a StartStopPanel class, you should override the StartStopPanel::className() function as follows:

class StartStopPanel: public VkComponent {
  public:
    // ...
    virtual const char *className();
    // ...
}
 
const char* StartStopPanel::className()
{
    return "StartStopPanel";
}

The baseWidget() function returns the base widget of a component as stored in the _baseWidget data member:

Widget baseWidget() const

Normally, components are as encapsulated as possible, so you should avoid operating directly on a component's base widget outside the class. However, certain operations might require access to a component's base widget. For example, after instantiating a component as a child of an XmForm widget, you might need to set various constraint resources, as shown below:

Widget form = XmCreateForm(parent, "form", NULL, 0);
StartStopPanel *panel = new StartStopPanel("panel", form);
XtVaSetValues(panel->baseWidget(), XmNtopAttachment, XmATTACH_FORM, NULL);

As a convenience, VkComponent defines a Widget operator that allows you to pass a VkComponent object directly to functions that expect a widget. By default, the operator converts the component into its base widget. However, the operator is defined as a virtual function so that derived classes can override it to return a different widget. Note that you must use an object, not a pointer to an object, because of the way operators work in C++. For example, the Widget operator makes the following code fragment equivalent to the fragment presented above:

Widget form = XmCreateForm(parent, "form", NULL, 0);
StartStopPanel *panel = new StartStopPanel("panel", form);
XtVaSetValues(*panel, XmNtopAttachment, XmATTACH_FORM, NULL);

Displaying and Hiding Components

The virtual member function show() manages the base widget of the component, displaying the entire component. The virtual member function hide() performs the inverse operation. You can call show() after calling hide() to redisplay a component. The syntax of these commands is as follows:

virtual void show()
virtual void hide()

For example, the following lines display the component panel, an instance of the StartStopPanel:

StartStopPanel *panel = new StartStopPanel("panel", form);
panel->show();

You could hide this component with this line:

panel->hide();

If you're familiar with Xt, you can think of these functions as performing operations analogous to managing and unmanaging the widget tree; however, you shouldn't regard these functions simply as “wrappers” for the XtManageChild() and XtUnmanageChild() functions. First, these member functions show and hide an entire component, which typically consists of more than one widget. Second, other actions might be involved in showing a component. In general, the show() member function does whatever is necessary to make a component visible on the screen. You shouldn't circumvent these member functions and manage and unmanage components' base widgets directly. For example, some components might use XtMap() and XtUnmap() as well. Other components might not even create their widget subtrees until show() is called for the first time.

The VkComponent class also provides the protected virtual function afterRealizeHook(). This function is called after a component's base widget is realized, just before it's mapped for the first time. The default action is empty. You can override this function in a subclass if you want to perform actions after a component's base widget exists.

VkComponent Utility Functions

All ViewKit components provide the virtual member 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 doesn't exit. “Quitting ViewKit Applications” provides more information on how to quit a ViewKit application, and “Providing a “Safe Quit” Mechanism” describes how to override VkSimpleWindow::okToQuit() to provide a “safe quit” mechanism for a window.

In some cases you might want to check one or more components contained within a window before quitting. To do so, override the okToQuit() function for that window to call the okToQuit() functions for all the desired components. Override the okToQuit() functions for the other components to perform whatever checks are necessary.

Another utility function provided by VkComponent is the static member function isComponent():

static Boolean isComponent(VkComponent *component)

The isComponent() function applies heuristics to determine whether the pointer passed as an argument represents a valid VkComponent object. If component points to a VkComponent that has not been deleted, this function always returns TRUE; otherwise the function returns FALSE. It is possible, though highly unlikely, that this function could mistakenly identify a dangling pointer to a deleted object as a valid object. This could happen if another component were to be allocated at exactly the same address as the deleted object a pointer previously pointed to. The isComponent() function is used primarily for ViewKit internal checking, often within assert() macros.

Using Xt Callbacks With Components

Callbacks pose a minor problem for C++ classes. C++ member functions have a hidden argument, which is used to pass the this pointer to the member function. This hidden argument makes ordinary member functions unusable as callbacks for Xt-based widgets. If a member function were to be called from C (as a callback), the this pointer would not be supplied and the order of the remaining arguments might be incorrect.

Fortunately, there is a simple way to handle the problem, although it requires the overhead of one additional function call. The approach is to use a regular member function to perform the desired task, and then use a static member function for the Xt callback. A static member function does not expect a this pointer when it is called. However, it is a member of a class, and as such has the same access privileges as any other member function. It can also be encapsulated so it is not visible outside the class.

The only catch is that the static member function used as a callback needs a way to access the appropriate instance of the class. This can be provided by specifying a pointer to the component as the client data when registering the callback.

Generally, you should follow these guidelines for using Xt callbacks with ViewKit components:

  • Define any Xt callbacks required by a component as static member functions of that class. You normally declare these functions in the private section of the class, because they are seldom useful to derived classes.

  • Pass the this pointer as client data to all Xt callback functions installed for widgets. Callback functions should retrieve this pointer, cast it to the expected component type, and call a corresponding member function.

  • Adopt a convention of giving static member functions used as callbacks the same name as the member function they call, with the word “Callback” appended. For example, the static member function activateCallback() should call the member function activate(). This convention is simply meant to make the code easier to read and understand. If you prefer, you can use your own convention for components you create, but this convention is used by all predefined ViewKit components.

  • Member functions called by static member functions are often private, but they can instead be part of the public or protected section of the class. Occasionally it's useful to declare one of these functions as virtual, thereby allowing derived classes to change the function ultimately called as a result of a callback.

For example, the constructor presented in Example 2-1 for the simple control panel component described in “Component Constructors” omitted the setup of callback routines to handle the activation of the buttons. To implement these callbacks, you must follow these steps:

  1. Create regular member functions to perform the tasks desired in response to the user clicking the buttons.

  2. Create static member functions that retrieve the client data passed by the callback, cast it to the expected component type, and call the corresponding member function.

  3. Register the static member functions as callback functions in the class constructor.

Suppose that for the control panel, you want to call the member function StartStopPanel::start() when the user clicks the Start button, and to call StartStopPanel::stop() when the user clicks the Stop button:

void StartStopPanel::start(Widget w, XtPointer callData)
{
  // Perform "start" function
}
void StartStopPanel::stop(Widget w, XtPointer callData)
{
  // Perform "stop" function
}

You should then define the StartStopPanel::startCallback() and StartStopPanel::stopCallback() static member functions as follows:

void StartStopPanel::startCallback(Widget w, XtPointer clientData,
                                   XtPointer callData)
{
  StartStopPanel *obj = ( StartStopPanel * ) clientData;
  obj->start(w, callData);
}
 
void StartStopPanel::stopCallback(Widget w, XtPointer clientData,
                                  XtPointer callData)
{
  StartStopPanel *obj = ( StartStopPanel * ) clientData;
  obj->stop(w, callData);
}

Finally, you need to register the static member functions as callbacks in the constructor. Remember that you must pass the this pointer as client data when registering the callbacks. Example 2-3 shows the updated StartStopPanel constructor, which installs the Xt callbacks for the buttons.

Example 2-3. Component Constructor With Xt Callbacks

StartStopPanel::StartStopPanel(const char *name, Widget parent):VkComponent(name)
{
  // Create an XmRowColumn widget as the component's base widget
  // to contain the buttons. Assign the widget to the _baseWidget
  // data member.
 
  _baseWidget = XmCreateRowColumn ( parent, _name, NULL, 0 );
 
  // Set up callback to handle widget destruction
 
  installDestroyHandler();
 
  XtVaSetValues(_baseWidget, XmNorientation, XmHORIZONTAL, NULL);
 
  // Create all other widgets as children of the base widget.
  // Manage all child widgets.
 
  _startButton = XmCreatePushButton ( _baseWidget, "start", NULL, 0);
  _stopButton  = XtCreatePushButton ( _baseWidget, "stop", NULL, 0);
 
  XtManageChild(_startButton);
  XtManageChild(_stopButton);
 
  // Install static member functions as callbacks for the pushbuttons
 
  XtAddCallback(_startButton, XmNactivateCallback,
                &StartStopPanel::startCallback, (XtPointer) this );
 
  XtAddCallback(_stopButton, XmNactivateCallback,
                &StartStopPanel::stopCallback, (XtPointer) this );
}


Handling Component Widget Destruction

When widgets are destroyed, it's easy to leave dangling references—pointers to memory that once represented widgets, but are no longer valid. For example, when a widget is destroyed, its children are also destroyed. It's often difficult to keep track of the references to these children, so it's fairly easy to write a program that accidentally references the widgets in a class after the widgets have already been destroyed. In some cases, applications might try to delete a widget twice, which usually causes the program to crash. Calling XtSetValues() or other Xt functions with a widget that's been deleted is also an error that can occur easily in this situation.

To help protect the encapsulation of ViewKit classes, VkComponent provides a private static member function, widgetDestroyedCallback(), to register as an XmNdestroyCallback for the base widget so that the component can properly handle the deletion of its base widget. This callback can't be registered automatically within the VkComponent constructor because derived classes have not yet created the base widget when the VkComponent constructor is called.

As a convenience, rather than force every derived class to install the widgetDestroyedCallback() function directly, VkComponent provides a protected installDestroyHandler() function that performs this task:

void installDestroyHandler()

Immediately after creating a component's base widget in a derived class, you should call installDestroyHandler(). For example:

StartStopPanel::StartStopPanel(const char *name, Widget parent) :
                                                      VkComponent(name)
{
  _baseWidget = XmCreateRowColumn ( parent, _name, NULL, 0 );
  installDestroyHandler();
  // ...
}

When you link your program with the debugging version of the ViewKit library, a warning is issued for any class that does not install the widgetDestroyedCallback() function.

The widgetDestroyedCallback() function calls the virtual member function widgetDestroyed():

virtual void widgetDestroyed()

By default, widgetDestroyed() sets the component's _baseWidget data member to NULL. You can override this function in derived classes if you want to perform additional tasks in the event of widget destruction; however, you should always call the base class's widgetDestroyed() function as well.

Occasionally, you might need to remove the destroy callback installed by installDestroyHandler(). For example, the VkComponent class destructor removes the callback before destroying the widget. To do so, you can call the removeDestroyHandler() function:

void removeDestroyHandler()

Component Resource Support

The X resource manager is a very powerful facility for customizing both applications and individual widgets. The resource manager allows the user or programmer to modify both the appearance and behavior of applications and widgets.

ViewKit provides a variety of utilities to simplify resource management. Using ViewKit, you can easily

  • set resource values for a single component or an entire class of components

  • initialize data members using values retrieved from the resource database

  • programmatically set default resource values for a component

  • obtain resource values

For ViewKit resource support to work properly, you must follow these two guidelines:

  • You must override each components's virtual className() member functions, returning a string that identifies the name of each component's C++ class. For example, if you create a StartStopPanel component class, you must override StartStopPanel::className() as follows:

    const char* StartStopPanel::className()
    {
      return "StartStopPanel";
    }
    

  • You must provide a unique component name when instantiating each component. This string must be used as the name of the component's base widget. Giving each instance of a component a unique name ensures a unique path through the application's widget tree for each widget. Widgets within a component can have hard-coded names because they can be qualified by the name of the root of the component subtree.

Setting Resource Values by Class or Individual Component

The structure of ViewKit allows you to specify resource values for either an individual component or for all components of a given class.

To set a resource for an individual instance of a component, refer to the resource using this syntax:

*name*resource

In this case, name refers to the ViewKit component's name that you pass as an argument to the component's constructor, and resource is the name of the resource. A specification of this form works for setting both widget resources and “synthetic” resources that you use to initialize data member values. (“Initializing Data Members Based on Resource Values” describes a convenience function for initializing data members from resource values.)

For example, you could set a “verbose” resource to TRUE for the instance named “status” of a hypothetical ProcessMonitor class with a resource entry such as this:

*status*verbose: TRUE

To set a resource for an entire component class, refer to the resource using this syntax:

*className*resource

In this case, className is the name of the ViewKit class returned by that class's className() function, and resource is the name of the resource. A specification of this form works for setting “synthetic” resources only, not widget resources.[1]

For example, you can set a “verbose” resource for all instances of the hypothetical ProcessMonitor class to TRUE with a resource entry such as:

*ProcessMonitor*verbose: TRUE

Initializing Data Members Based on Resource Values

If you want to initialize data members in a class using values in the resource database, you can call the VkComponent member function getResources():

void getResources ( const XtResourceList resources,
                    const int numResources )

The resources argument is a standard resource specification in the form of an XtResource list, and the numResources argument is the number of resources. You should define the XtResource list as a static data member of the class to encapsulate the resource specification with the class. You should call getResources() in the component constructor after creating your component's base widget.

getResources() retrieves the specified resources relative to the root of the component's widget subtree. For example, to set the value of a resource for a particular instance of a component, you would need to set the resource with an entry in the resource database of this form:

*name.resource:  value

In this example, name is the component's name, resource is the name of the resource, and value is the resource value. To set the value of a resource for an entire component class, you would need to set the resource with an entry in the resource database of this form:

*className.resource:  value

In this example, className is the component class name, resource is the name of the resource, and value is the resource value.

Example 2-4 demonstrates the initialization of a data member, _verbose, from the resource database. A default value is specified in the XtResource structure, but the ultimate value is determined by the value of the resource named “verbose” in the resource database.

Example 2-4. Initializing a Data Member From the Resource Database

// Header file: ProcessMonitor.h
 
#include <Vk/VkComponent.h>
#include <Xm/Frame.h>
 
class ProcessMonitor : public VkComponent
{
private:
  static XtResource _resources[];
 
protected:
  Boolean _verbose;
public:
  ProcessMonitor(const char *, Widget);
  ~ProcessMonitor();
  virtual const char *className();
};
 
// Source file: ProcessMonitor.c++
 
#include "ProcessMonitor.h"
 
XtResource ProcessMonitor::_resources [] = {
  {
  "verbose",
  "Verbose",
  XmRBoolean,
  sizeof ( Boolean ),
  XtOffset ( ProcessMonitor *, _verbose ),
  XmRString,
  (XtPointer) "FALSE",
  },
};
 
ProcessMonitor::ProcessMonitor(Widget parent, const char *name):VkComponent(name)
{
  _baseWidget = XtVaCreateWidget ( _name, xmFrameWidgetClass,
                                   parent, NULL ) ;
  installDestroyHandler();
 
  // Initialize members from resource database
 
  getResources ( _resources, XtNumber(_resources) );
 
  // ...
}

So, to initialize the _verbose data member to TRUE in all instances of the ProcessMonitor class, you need only set the following resource in the resource database:

*ProcessMonitor.verbose: TRUE

To initialize _verbose to TRUE for an instance of ProcessMonitor named conversionMonitor, you could set the following resource in the resource database:

*conversionMonitor.verbose:  TRUE

Setting Default Resource Values for a Component

Often, you might want to specify default resource values for a component. A common way to accomplish this is to put the resource values in an application resource file. However, this makes the component dependent on that resource file; to use that component in another application, you must remember to copy those resources into the new application's resource file. This is especially inconvenient for classes that you reuse in multiple applications.

A better method of encapsulating default resources into a component is to use a ViewKit facility that allows you to specify them programmatically and then merge them into the resource database during execution. Although the resources are specified programmatically, they can be overridden by applications that use the class, or by end users in resource files. However, the default values are specified by the component class and cannot be separated from the class accidentally. If you later want to change the implementation of a component class, you can also change the resource defaults when necessary, knowing that applications that use the class will receive both changes simultaneously.

The VkComponent class provides the setDefaultResources() function for storing a collection of default resources in the application's resource database. The resources are loaded with the lowest precedence, so that these resources are true defaults. They can be overridden easily in any resource file. You should call this function in the component constructor before creating the base widget in case any resources apply to the component's base widget.

The setDefaultResources() function has the following syntax:

void setDefaultResources ( const Widget w,
                           const String *resourceSpec )

The first argument is a widget; you should always use the parent widget passed in the component's constructor.

The second argument is a NULL-terminated array of strings, written in the style of an X resource database specification. Specify all resources in the list relative to the root of the component's base widget, but do not include the name of the base widget. If you want to apply a resource to the base widget, simply use the name of the resource preceded by an asterisk (*). When resources are loaded, the value of _name is prefixed to all entries, unless that entry begins with a hyphen (-). As long as you use unique names for each component that you create of a given class, this results in resource specifications unique to each component. If you precede a resource value in this list with a hyphen (-), setDefaultResources() does not qualify the resource with the value of _name. This is useful in rare situations where you want to add global resources to the database.

You should declare the resource list as a static data member of the class. This encapsulates the set of resources with the class.


Note: Generally, setting resources using setDefaultResources() is most appropriate for components that you plan to reuse in multiple applications. In particular, it is a good method for setting resources for widget labels and other strings that your component displays. You should not use setDefaultResources() to set widget resources, such as orientation, that you would normally set programmatically. Typically you don't need to change these resources when you use the component in different applications, and so you save memory and execution time by not using setDefaultResources() to set these resources.

Example 2-5 builds on the StartStopPanel constructor from Example 2-3 to specify the default label strings “Start” and “Stop” for the button widgets.

Example 2-5. Setting a Component's Default Resource Values

// StartStopPanel.h
 
class StartStopPanel: public VkComponent {
 
  public:
    StartStopPanel (const char *, Widget);
    ~StartStopPanel();
    // ...
 
  private:
    static String _defaultResources[];
    // ...
}
 
// StatStopPanel.c++
 
String StartStopPanel::_defaultResources[] = {
  "*start.labelString:  Start",
  "*stop.labelString:   Stop",
  NULL
};
 
StartStopPanel::StartStopPanel(const char *name, Widget parent):VkComponent(name)
{
  // Load class-default resources for this object before creating base widget
 
  setDefaultResources(parent, _defaultResources );
 
  _baseWidget = XmCreateRowColumn ( parent, _name, NULL, 0 );
 
  installDestroyHandler();
 
  XtVaSetValues(_baseWidget, XmNorientation, XmHORIZONTAL, NULL);
 
  _startButton = XmCreatePushButton ( _baseWidget, "start", NULL, 0);
  _stopButton  = XtCreatePushButton ( _baseWidget, "stop", NULL, 0);
 
  // ...
}


Convenience Function for Retrieving Resource Values

ViewKit also provides VkGetResource(), a convenience function for retrieving resource values from the resource database. VkGetResource() is not a member function of any class. You must include the header file <Vk/VkResource.h> to use VkGetResource().

VkGetResource() has two forms. The first is as follows:

char * VkGetResource( const char * name,
                      const char * className )

This form returns a character string containing the value of the application resource you specify by name and class name. This function is similar to XGetDefault(3X) except that this form of VkGetResource() allows you to retrieve the resource by class name whereas XGetDefault() does not.


Note: Do not attempt to change or delete the value returned by VkGetResource().

The second form of VkGetResource() is as follows:

XtPointer VkGetResource( Widget w,
                         const char *name,
                         const char *className,
                         const char *desiredType,
                         const char *defaultValue)

This second form is similar to XtGetSubresource(3Xt) in that it allows you to retrieve a resource relative to a specific widget. You can specify the resource as a dot-separated list of names and classes, allowing you to retrieve “virtual” sub-resources. You can also specify a target type. VkGetResource() converts the retrieved value, or the default value if no value is retrieved, to the specified type.


Note: Do not attempt to change or delete the value returned by VkGetResource().

For example, suppose that you want to design an application for drawing an image and you want to allow the user to select various aspects of the style in which the image is drawn, such as color and fill pattern (a pixmap). You could specify each aspect of each style as a resource and retrieve the values as follows:

Widget canvas = XmCreateDrawingArea(parent, "canvas", NULL, 0);
Pixel fgOne = (Pixel) VkGetResource(canvas,
                                    "styleOne.foreground", "Style.Foreground",
                                    XmRString, "Black");
Pixel fgTwo = (Pixel) VkGetResource(canvas,
                                    "styleTwo.foreground", "Style.Foreground",
                                    XmRString, "Black");
 
Pixel bgOne = (Pixel) VkGetResource(canvas,
                                    "styleOne.background", "Style.Background",
                                    XmRString, "White");
Pixel bgTwo = (Pixel) VkGetResource(canvas,
                                    "styleTwo.background", "Style.Background",
                                    XmRString, "White");
 
Pixmap pixOne = (Pixmap) VkGetResource(canvas,
                                       "styleOne.pixmap", "Style.Pixmap",
                                       XmRString, "background");
Pixmap pixTwo = (Pixmap) VkGetResource(canvas,
                                       "styleTwo.pixmap", "Style.Pixmap",
                                       XmRString, "background");

Another common technique used in ViewKit programming is to use a string to search for resource value and, if no resource exists, use the string as the value. You can do this easily if you pass the string to VkGetResource() as the default value. For example, consider the following code:

char *timeMsg = "Time";
// ...
char *timeTitle = (char *) VkGetResource(_baseWidget, timeMsg, "Time",
                                         XmRString, timeMsg);

In this case, VkGetResource() searches for a resource (relative to the _baseWidget widget) whose name is specified by the character string timeMsg. If no such resource exists, VkGetResource() returns the value of timeMsg as the default value.

If you use this technique, you should not pass a string that contains embedded spaces or newlines.

ViewKit Callback Support

All ViewKit components support ViewKit member function callbacks (also referred to simply as ViewKit callbacks). ViewKit callbacks are analogous to Xt-style callbacks supported by widget sets, but ViewKit callbacks are in no way related to Xt.

The ViewKit callback mechanism allows a component to define conditions or events, the names of which are exported as public static string constants encapsulated by that component. Any other component can register any of its member functions to be called when the condition or event associated with that callback occurs.

Unlike the case when registering ViewKit functions for Xt-style callbacks, the functions you register for ViewKit callbacks must be regular member functions, not static member functions.

ViewKit callbacks are implemented by the VkCallbackObject class. VkComponent is derived from VkCallbackObject, so all ViewKit components can use ViewKit callbacks. If you create a class for use with a ViewKit application, that class must be derived from VkCallbackObject or one of its subclasses (such as VkComponent) for you to be able to use ViewKit callbacks with that class.

Registering ViewKit Callbacks

The addCallback() function defined in VkCallbackObject registers a member function to be called when the condition or event associated with a callback occurs.


Note: When registering a ViewKit callback, remember to call the addCallback() member function of the object that triggers the callback, not the object that is registering the callback.

The format of addCallback() for registering a member function is as follows:

void addCallback(const char *name,
                 VkCallbackObject *component,
                 VkCallbackMethod callbackFunction,
                 void *clientData = NULL)

The following are the arguments for this function:

name 

The name of the ViewKit callback. You should always use the name of the public static string constant for the appropriate callback, not a literal string constant. (For example, use VkComponent::deleteCallback, not “deleteCallback”.) This allows the compiler to catch any misspellings of callback names.

component 

A pointer to the object registering the callback function.

callbackFunction 


The member function to invoke when the condition or event associated with that callback occurs.

clientData 

A pointer to data to pass to the callback function when it is invoked.

For example, consider a member of a hypothetical Display class that instantiates another hypothetical component class, Control. The code fragment below registers a function to be invoked when the value set by the Control object changes and the Control object triggers its valueChanged callback:

Display::createControl()
{
  _control = new Control(_baseWidget, "control");
  _control->addCallback(Control::valueChanged, this,
                      (VkCallbackMethod) &Display::newValue);
}

In this example, the Display object requests that when the Control object triggers its valueChanged callback, it should call the Display::newValue() function of the Display object that created the Control object. The “(VkCallbackMethod)” cast for the callback function is required.

All ViewKit callback functions must have this form:

void memberFunctionCallback(VkCallbackObject *obj,
                            void *clientData,
                            void *callData)

The obj argument is the component that triggered the callback, which you must cast to the correct type to allow access to members provided by that class. The clientData argument is the optional client data specified when you registered the callback, and the callData argument is optional data supplied by the component that triggered the callback.

For example, you would define the Display::newValue() callback method used above as follows:

class Display : VkComponent {
  private:
    void newValue(VkCallbackObject *, void *, void *);
    // ...
};
 
void Display::newValue(VkCallbackObject* obj,
                       void *clientData,
                       void *callData);
{
  Control *controlObj = (Control *) obj;
  // Perform whatever operation is needed to update
  // the Display object. You can also access member
  // functions from the Control object (controlObj).
  // The clientData argument contains any information
  // you provided as clientData when you registered
  // this callback; cast it to the proper type to use it.
  // If the Control object passed the new value as the
  // callData argument, you can cast that to the proper
  // type and use it.
}

There is also a version of addCallback() for registering non-member functions. Its syntax is as follows:

void addCallback(const char *name,
                 VkCallbackFunction callbackFunction,
                 void *clientData = NULL)

The arguments for this version are as follows:

name 

The name of the ViewKit callback. You should always use the name of the public static string constant for the appropriate callback, not a literal string constant.

callbackFunction 


The non-member function to invoke when the condition or event associated with that callback occurs.

clientData 

A pointer to data to pass to the callback function when it is invoked.

The form of your non-member ViewKit callback functions must be as follows:

void functionCallback(VkCallbackObject *obj,
                      void *clientData,
                      void *callData)

For example, suppose you have a non-member function errorCondition():

void errorCondition(VkCallbackObject *obj,
                    void *clientData,
                    void *callData)
{
    // Handle error condition
}

You could register it for a ViewKit callback with the line such as this:

sample->addCallback(SampleComponent::errorCallback,
                    (VkCallbackFunction) &errorCondition);

The (VkCallbackFunction) cast for the callback function is required.

Removing ViewKit Callbacks

The removeCallback() function provided by the VkCallbackObject class removes previously registered callbacks. The following version of removeCallback() removes a member function registered as a callback:

void removeCallback(char *name,
                    VkCallbackObject *otherObject,
                    VkCallbackMethod memberFunction,
                    void *clientData = NULL)

The following version of removeCallback() removes a non-member function registered as a callback:

void removeCallback(const char *name,
                    VkCallbackFunction callbackFunction,
                    void *clientData = NULL)

To remove a callback, you must provide the same arguments specified when you registered the callback. For example, the following line removes the Control callback registered in the previous section:

_control->removeCallback(Control::valueChanged, this,
                      (VkCallbackMethod) &Display::newValue);

The removeAllCallbacks() function removes multiple ViewKit callbacks:

void removeAllCallbacks()
void removeAllCallbacks(VkCallbackObject *obj)

If you don't provide an argument, this function removes all callbacks from an object, regardless of which components registered the callbacks. If you provide a pointer to a component, removeAllCallbacks() removes from an object all ViewKit callbacks that were set by the specified component. For example, the following would remove from the Control object _control all callbacks that the Display object had set:

_control->removeAllCallbacks(this);

Defining and Triggering ViewKit Callbacks

To create a ViewKit callback for a component class, define a public static string constant as the name of the callback. For clarity, you should use the string's name as its value. For example, the following defines a callback, StartStopPanel::actionCallback, for the hypothetical StartStopPanel class discussed earlier in this chapter:

class StartStopPanel : public VkComponent {
 
  public:
    static const char *const actionCallback;
    // ...
}

const char *const StartStopPanel::actionCallback = "actionCallback";

The callCallbacks() member function triggers a specified callback, invoking all member functions registered for that callback:

callCallbacks(const char *callback, void *callData)

The first argument specifies the name of the callback. You should always use the name of the public static string constant for the appropriate callback, not a literal string constant. (For example, use StartStopPanel::startCallback, not “startCallback”.) This allows the compiler to catch any misspellings of callback names.

The second argument is used to supply any additional data that might be required.

For example, you could define the StartStopPanel::start() and StartStopPanel::stop() functions to trigger the actionCallback and pass an enumerated value as call data to indicate which button the user clicked:

enum PanelAction { START, STOP };
 
class StartStopPanel : public VkComponent {
 
  public:
    static const char *const actionCallback;
    // ...
}
const char *const StartStopPanel::actionCallback = "actionCallback";
 
void StartStopPanel::start(Widget w, XtPointer callData)
{
  callCallbacks(actionCallback, (void *) START);
}
 
void StartStopPanel::stop(Widget w, XtPointer callData)
{
  callCallbacks(actionCallback, (void *) STOP);
}; 

Predefined ViewKit Callbacks

The VkComponent class, and therefore all derived classes, includes the ViewKit callback deleteCallback, which is invoked when the component's destructor is called. You can use this callback to prevent dangling pointers when maintaining pointers to other components. The code fragment in Example 2-6 shows an example of this.

Example 2-6. Using the Predefined deleteCallback ViewKit Callback

class MainComponent : VkComponent {
  // ...
  AuxComponent *_aux;
  void createAux();
  void auxDeleted(VkCallbackObject *, void *, void *);
  // ...
};
 
// ...
 
void MainComponent::createAux()
{
_aux = new AuxComponent(_baseWidget, "auxilliary");
_aux->addCallback(VkComponent::deleteCallback, this,
              (VkCallbackMethod) &MainComponent::auxDeleted);
}
 
void MainComponent::auxDeleted(VkCallbackObject*,
                               void *, void *)
{
  _aux = NULL;
}

In the function MainComponent::createAux(), the MainComponent class creates an instance of the AuxComponent and then immediately registers MainComponent::auxDeleted() as a callback to be invoked when the AuxComponent object is deleted.

The auxDeleted() callback definition simply assigns NULL to the AuxComponent object pointer. All other MainComponent functions should test the value of _aux to ensure that it is not NULL before attempting to use the AuxComponent object. This eliminates the possibility that the MainComponent class would try to access the AuxComponent object after deleting it, or attempting to delete it a second time.

In most cases you should not need to use this technique of registering deleteCallback callbacks. It is necessary only if you need to create multiple pointers to a single object. In general, you should avoid multiple pointers to the same object, but VkComponent::deleteCallback provides a way to control situations in which you must violate this guideline.

Deriving Subclasses to Create New Components

This section demonstrates how to use the VkComponent class to create new components. It includes guidelines to follow when creating new components, an example of creating a new component, and an example of subclassing that component to create yet another component class.

Subclassing Summary

The following is a summary of guidelines for writing components based on the VkComponent class:

  • Encapsulate all of your component's widgets in a single-rooted subtree. While some extremely simple components might contain only a single widget, the majority of components must create some type of container widget as the root of the component's widget subtree; all other widgets are descendents of this one.

  • When you create your class's base widget, assign it to the _baseWidget data member inherited from the VkComponent class.

  • In most cases, create a component's base widget and all other widgets in the class constructor. The constructor should manage all widgets except the base widget, which should be left unmanaged. You can then manage or unmanage a component's entire widget subtree using the show() and hide() member functions.

  • Accept at least two arguments in your component's constructor: a string to be used as the name of the base widget, and a widget to be used as the parent of the component's base widget. Pass the name argument to the VkComponent constructor, which makes a copy of the string. Refer to a component's name using the _name member inherited from VkComponent or the name() access function. Refer to a component's base widget using the _baseWidget member inherited from VkComponent or the baseWidget() access function.

  • Override the virtual className() member function for your component classes to return a string consisting of the name of the component's C++ class.

  • Define all Xt callbacks required by a component class as private static member functions. In exceptional cases, you might want to declare them as protected so that derived classes can access them.

  • Pass the this pointer as client data to all Xt callback functions. Callback functions should retrieve this pointer, cast it to the expected component type and call a corresponding member function. For clarity, use the convention of giving static member functions used as callbacks the same name as the member function they call, with the word “Callback” appended. For example, name a static member function startCallback() if it calls the member function start().

  • Call installDestroyHandler() immediately after creating a component's base widget.

  • If you need to specify default resources for a component class, call the function setDefaultResources() with an appropriate resource list before creating the component's base widget.

  • If you need to initialize data members from values in the resource database, define an appropriate resource specification and call the function getResources() immediately after creating the component's base widget.

Creating a New Component

To illustrate many of the features of the VkComponent base class, this chapter has shown how to build a simple class called StartStopPanel, which implements a control panel containing two buttons. Figure 2-2 shows the default appearance of a StartStopPanel object.

Figure 2-2. Default Appearance of a StartStopPanel Component

Figure 2-2 Default Appearance of a StartStopPanel Component

Example 2-7 lists the full implementation of this class.

Example 2-7. Simple User-Defined Component

//////////////////////////////////////////////////////////////
// StartStopPanel.h
//////////////////////////////////////////////////////////////
 
#ifndef _STARTSTOPPANEL_H
#define _STARTSTOPPANEL_H
#include <Vk/VkComponent.h>
 
enum PanelAction { START, STOP };
 
class StartStopPanel : public VkComponent {
  public:
    StartStopPanel (const char *, Widget);
    ~StartStopPanel();
    virtual const char *className();
 
    static const char *const actionCallback;
 
  protected:
    virtual void start(Widget, XtPointer);
    virtual void stop(Widget, XtPointer);
 
    Widget _startButton;
    Widget _stopButton;
 
  private:
    static void startCallback(Widget, XtPointer, XtPointer);
    static void stopCallback(Widget, XtPointer, XtPointer);
    static String _defaultResources[];
};
 
#endif
/////////////////////////////////////////////////////////////
// StartStopPanel.c++
/////////////////////////////////////////////////////////////
 
#include "StartStopPanel.h"
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
 
// These are default resources for widgets in objects of this class.
// All resources will be prefixed by *<name> at instantiation,
// where <name> is the name of the specific instance, as well as the
// name of the baseWidget. These are only defaults, and may be
// overriden in a resource file by providing a more specific resource
// name.
 
String StartStopPanel::_defaultResources[] = {
  "*start.labelString:  Start",
  "*stop.labelString:   Stop",
  NULL
};
 
const char *const StartStopPanel::actionCallback = "actionCallback";
 
StartStopPanel::StartStopPanel(const char *name, Widget parent):VkComponent(name)
{
  // Load class-default resources for this object before creating base widget
 
  setDefaultResources(parent, _defaultResources );
 
  // Create an XmRowColumn widget as the component's base widget
  // to contain the buttons. Assign the widget to the _baseWidget
  // data member.
 
  _baseWidget = XmCreateRowColumn ( parent, _name, NULL, 0 );
  // Set up callback to handle widget destruction
 
  installDestroyHandler();
 
  XtVaSetValues(_baseWidget, XmNorientation, XmHORIZONTAL, NULL);
 
  // Create all other widgets as children of the base widget.
  // Manage all child widgets.
 
  _startButton = XmCreatePushButton ( _baseWidget, "start", NULL, 0);
  _stopButton  = XmCreatePushButton ( _baseWidget, "stop", NULL, 0);
 
  XtManageChild(_startButton);
  XtManageChild(_stopButton);
 
  // Install static member functions as callbacks for the buttons
 
  XtAddCallback(_startButton, XmNactivateCallback,
                &StartStopPanel::startCallback, (XtPointer) this );
 
  XtAddCallback(_stopButton, XmNactivateCallback,
                &StartStopPanel::stopCallback, (XtPointer) this );
}
 
StartStopPanel::~StartStopPanel()
{
  // Empty
}
 
const char* StartStopPanel::className()
{
  return "StartStopPanel";
}
 
void StartStopPanel::startCallback(Widget w, XtPointer clientData,
                                   XtPointer callData)
{
  StartStopPanel *obj = ( StartStopPanel * ) clientData;
  obj->start(w, callData);
}
void StartStopPanel::stopCallback(Widget w, XtPointer clientData,
                                  XtPointer callData)
{
  StartStopPanel *obj = ( StartStopPanel * ) clientData;
  obj->stop(w, callData);
}
void StartStopPanel::start(Widget, XtPointer)
{
  callCallbacks(actionCallback, (void *) START);
}
void StartStopPanel::stop(Widget, XtPointer)
{
  callCallbacks(actionCallback, (void *) STOP);
}


Using and Subclassing a Component Class

Example 2-7 slightly changes the StartStopPanel class from previous examples by declaring the member functions StartStopPanel::start() and StartStopPanel::stop() as virtual functions. This allows you to use the StartStopPanel in two different ways: using the component directly and subclassing the component.

Using a Component Class Directly

The simplest way to use the StartStopPanel class is to register callbacks with StartStopPanel::actionCallback. To do so, instantiate a StartStopPanel object in your application and register as a callback a member function that tests the value of the call data and performs some operation based on the value. This option avoids the additional work required to create a subclass of StartStopPanel. This technique of using a component class is most appropriate if the class already has all the functionality you require.

Example 2-8 shows a simple example of using the StartStopPanel directly. The PanelWindow class is a simple subclass of the VkSimpleWindow class, which is discussed in Chapter 4, “ViewKit Windows.” It performs the following activities in its constructor:

  1. It instantiates a StartStopPanel object named “controlPanel” and assigns it to the _controlPanel variable.

  2. It specifies a vertical orientation for the StartStopPanel object.

  3. It installs PanelWindow::statusChanged() as a ViewKit callback function to be called whenever StartStopPanel::actionCallback triggers. In this example, PanelWindow::statusChanged() simply prints a status message to standard output whenever it is called.

  4. It installs the _controlPanel object as the window's “view.” Showing the PanelWindow object will now display the _controlPanel object. (“Creating the Window Interface” describes how to create window interfaces.)

    Example 2-8. Using a Component Directly

    //////////////////////////////////////////////////////////////
    // PanelWindow.h
    //////////////////////////////////////////////////////////////
     
    #ifndef _PANELWINDOW_H
    #define _PANELWINDOW_H
     
    #include "StartStopPanel.h"
    #include <Vk/VkSimpleWindow.h>
     
    // Define a top-level window class
     
    class PanelWindow: public VkSimpleWindow {
     
      public:
        PanelWindow(const char *name);
        ~PanelWindow();
        virtual const char* className();
      protected:
        void statusChanged(VkCallbackObject *, void *, void *);
     
        StartStopPanel * _controlPanel;
    };
     
    #endif
     
    //////////////////////////////////////////////////////////////
    // PanelWindow.c++
    //////////////////////////////////////////////////////////////
     
    #include "PanelWindow.h"
    #include <iostream.h>
     
    PanelWindow::PanelWindow(const char *name) : VkSimpleWindow (name) 
    {
        _controlPanel = new StartStopPanel( "controlPanel",
                                            mainWindowWidget() );
     
        XtVaSetValues(_controlPanel->baseWidget(),
                      XmNorientation, XmVERTICAL, NULL);
     
        _controlPanel->addCallback( StartStopPanel::actionCallback, this,
                             (VkCallbackMethod) &PanelWindow::statusChanged );
     
        addView(_controlPanel);
    }
     
    const char * PanelWindow::className()
    {
        return "PanelWindow";
    }      
     
    PanelWindow::~PanelWindow()
    {
        // Empty
    }
    void PanelWindow::statusChanged(VkCallbackObject *obj,
                                    void *, void *callData)
    {
     
        StartStopPanel * panel = (StartStopPanel *) obj;
        PanelAction action = (PanelAction) callData;
        switch (action) {
            case START:
                cout << "Process started\n" << flush;
                break;
            case STOP:
                cout << "Process stopped\n" << flush;
                break;
            default:
                cout << "Undefined state\n" << flush;
        }
    }
    


The following simple program displays the resulting PanelWindow object (Chapter 3, “The ViewKit Application Class,” discusses the VkApp class):

//////////////////////////////////////////////////////////////
// PanelTest.c++
//////////////////////////////////////////////////////////////
 
#include <Vk/VkApp.h>
#include "PanelWindow.h"
 
// Main driver. Just instantiate a VkApp and the PanelWindow,
// "show" the window and then "run" the application.
 
void main ( int argc, char **argv )
{
    VkApp        *panelApp = new VkApp("panelApp", &argc, argv);
    PanelWindow  *panelWin = new PanelWindow("panelWin");
 
    panelWin->show();
    panelApp->run();
}

Figure 2-3 shows the resulting PanelWindow window displayed by this program.

Figure 2-3. Resulting PanelWindow Window

Figure 2-3 Resulting PanelWindow Window

Using a Component Class by Subclassing

Another way to use the StartStopPanel class is to derive a subclass and override the StartStopPanel::start() and StartStopPanel::stop() functions. This technique of using a component class is most appropriate if you need to expand or modify a component's action in some way.

Example 2-9 creates ControlPanel, a subclass of StartStopPanel that incorporates the features implemented in the PanelWindow class shown in Example 2-8.

Example 2-9. Subclassing a Component

//////////////////////////////////////////////////////////////
// ControlPanel.h
//////////////////////////////////////////////////////////////
 
#ifndef _CONTROLPANEL_H
#define _CONTROLPANEL_H
#include "StartStopPanel.h"
 
class ControlPanel : public StartStopPanel {
 
  public:
    ControlPanel (const char *, Widget);
    ~ControlPanel();
    virtual const char *className();
  protected:
    virtual void start(Widget, XtPointer);
    virtual void stop(Widget, XtPointer);
};
#endif
//////////////////////////////////////////////////////////////
// ControlPanel.c++
//////////////////////////////////////////////////////////////
 
#include "ControlPanel.h"
#include <iostream.h>
ControlPanel::ControlPanel (const char *name , Widget parent) :
                                             StartStopPanel (name, parent)
{
    XtVaSetValues(_baseWidget, XmNorientation, XmVERTICAL, NULL);
}
 
ControlPanel::~ControlPanel()
{
    // Empty
}
 
 
const char* ControlPanel::className()
{
    return "ControlPanel";
}
 
void ControlPanel::start(Widget w, XtPointer callData)
{
    cout << "Process started\n" << flush;
    StartStopPanel::start(w, callData);
}
 
void ControlPanel::stop(Widget w, XtPointer callData)
{
    cout << "Process stopped\n" << flush;
    StartStopPanel::stop(w, callData);
}

The ControlPanel constructor uses the StartStopPanel constructor to initialize the component, creating the widgets and initializing the component's data members. Then, the ControlPanel constructor sets the orientation resource of the RowColumn widget, which is the component's base widget, to VERTICAL.

The ControlPanel class also overrides the virtual functions start() and stop() to perform the actions handled previously by the PanelWindow class. After performing these actions, the ControlPanel::start() and ControlPanel::stop() functions call StartStopPanel::start() and StartStopPanel::stop(), respectively. While this may seem unnecessary for an example this simple, it helps preserve the encapsulation of the classes. You could now change the implementation of the StartStopPanel class, perhaps adding a status indicator to the component that the StartStopPanel::start() and StartStopPanel::stop() functions would update, and you would not have to change the start() and stop() function definitions in derived classes such as ControlPanel.

The following simple example creates a VkSimpleWindow object, adds a ControlPanel as the window's view, and then displays the window:

//////////////////////////////////////////////////////////////
// PanelTest2.c++
//////////////////////////////////////////////////////////////
 
#include <Vk/VkApp.h>
#include <Vk/VkSimpleWindow.h>
#include "ControlPanel.h"
 
// Main driver. Instantiate a VkApp, a VkSimpleWindow, and a
// ControlPanel, add the ControlPanel as the SimpleWindow's view,
// "show" the window and then "run" the application.
 
void main ( int argc, char **argv )
{
    VkApp *panelApp = new VkApp("panel2App", &argc, argv);
    VkSimpleWindow *panelWin = new VkSimpleWindow("panelWin");
    ControlPanel *control = new ControlPanel("control",
                                             panelWin->mainWindowWidget() );
    panelWin->addView(control);
    panelWin->show();
    panelApp->run();
}

VkNameList Class

The VkNameList class provides a convenient way to maintain a list of character strings. Member functions allow you to add and delete items, and sort, reverse, and otherwise manipulate the list. See the VkNameList(3x) reference page for more details.

VkNameList Constructor and Destructor

The VkNameList constructor has three overloaded versions:

  • VkNameList(void)

    Initializes an empty list.

  • VkNameList(char * name)

    Creates a list with name as the initial member.

  • VkNameList(const VkNameList& givenList)

    Creates a clone of an existing VkNameList object.

The following is the VkNameList destructor, which frees all memory allocated by a VkNameList object:

void ~VkNameList (void)

VkNameList Member Functions

These functions add and delete items from the list:

  • VkNameList::add() adds an item or a VkNameList to the list:

    void add (char *item)
    void add (const VkNameList& list)
    

  • VkNameList::getIndex() returns the index of the first occurrence of the given item:

    int getIndex (const char *item) const
    

    If the item is not on the list, getIndex() returns -1.

  • VkNameList::remove() deletes from the list the first occurrence, if any, of the given item:

    void remove (char *item)
    

    A second version of remove() deletes items index through index + count -1:

    void remove (int index, int count = 1) 
    

    To remove a number of items, beginning with a specified item, use remove(getIndex(item), count).

  • VkNameList::operator=() assigns the members of one list to another:

    VkNameList& operator=(const VkNameList& givenList)
    


    Note: This function frees any strings that were in the object on the left side of the equation. For instance in the following code fragment, any strings that were previously in A are freed, and any references to the strings in A now point to freed memory:


    VkNameList *A = new VkNameList();
    VkNameList *B = new VkNameList();
     
    ...
     
    A = B;
    

These functions manipulate the list:

  • VkNameList::sort() sorts the list alphanumerically:

    void sort (void)
    

  • VkNameList::reverse() reverses the order of the items on the list:

    void reverse (void)
    

  • VkNameList::removeDuplicates() deletes from the list all exact duplicates:

    void removeDuplicates (void)
    

These functions access the list:

  • VkNameList::size() returns the number of items in the list:

    int size (void)
    

  • VkNameList::exists() checks to see if a specified string is in the list:

    int exists (char *item)
    

    If the string is not in the list, exists() returns 0.

  • VkNameList::operator==() tests two VkNameList objects for equivalence:

    int operator==(const VkNameList& givenList)
    

    operator==() returns success only if the lists have identical contents, in the same order.

  • VkNameList::mostCommonString() returns a copy of the most common string in the list:

    char* mostCommonString ((void)
    

    The returned string must be freed by the caller.

  • VkNameList::completeName() returns a VkNameList object containing all strings in the original object that could be completions of the specified string:

    VkNameList* completeName (char *name, char &*completed name, 
                              int& numMatching) 
    

    When the function returns, the completedName argument contains the longest matched substring common to all members of the returned list. numMatching contains the number of matching elements found.

  • VkNameList::getString() retrieves a copy of the item at position index in the list:

    char* getString (int index)
    

    The returned string must be freed by the caller.

  • VkNameList::getSubStrings() returns a pointer to a list of items from the original list that match the given substring:

    VkNameList* getSubStrings (char *substring) 
    

    The VkNameList returned by getSubStrings() must be deleted by the caller.

  • VkNameList::getStringTable() returns a pointer to the members of the VkNameList object in the form of an array of strings:

    char** getStringTable (void)
    


    Note: You must free the returned array itself, not the individual strings in the array.


  • VkNameList::getXmStringTable() returns a pointer to the members of the VkNameList object in the form of an array of compound strings:

    XmStringTable getXmStringTable (void)
    

    The returned XmStringTable must be freed by the caller using freeXmStringTable().

  • VkNameList::freeXmStringTable() frees the memory returned by getXmStringTable():

    static void freeXmStringTable (XmStringTable)
    

Using VkNameList

Example 2-10 demonstrates the use of the VkNameList class to construct a list incrementally and display the results in reverse-sorted order in a Motif XmList widget.

Example 2-10. Manipulating a List of Strings Using the VkNameList Class

#include <Vk/VkApp.h>
#include <Vk/VkSimpleWindow.h>
#include <Xm/List.h>
#include <Vk/VkNameList.h>
 
// Define a top-level window class
 
class MyWindow: public VkSimpleWindow {
 
protected:
 
    Widget _list;    // Hang on to widget as a data member
 
public:
 
    MyWindow ( const char *name );
    ~MyWindow();
    virtual const char* className();  // Identify this class
};
 
// The MyWindow constructor provides a place in which
// to create a widget tree to be installed as a
// “view” in the window.
 
MyWindow::MyWindow ( const char *name ) : VkSimpleWindow ( name )
{
    _list =  XmCreateList ( mainWindowWidget(), “list”, NULL, 0 );
 
    // Create a name list object
 
    VkNameList *items = new VkNameList();
    // Add some items
 
    items->add(“One”);
    items->add(“Two”);
    items->add(“Three”);
    items->add(“Four”);
    items->add(“One”);
 
    items->removeDuplicates();  // Get rid of duplications
    items->sort();              // Sort the list
    items->reverse();           // Now reverse it
    // Display the items in the list widget
 
    XtVaSetValues(_list, XmNitems, (XmStringTable) (*items),
                  XmNitemCount, items->size(), NULL);
 
    addView(_list);
}
 
const char * MyWindow::className()
 
{
    return “MyWindow”;
}
 
MyWindow::~MyWindow()
{
    // Empty
}
 
// Main driver. Just instantiate a VkApp and a
// top-level window, “show” the window and then
// “run” the application.
 
void main ( int argc, char **argv )
{
    VkApp     *app = new VkApp(“Hello”, &argc, argv);
    MyWindow  *win = new MyWindow(“hello”);
 
    win->show();
    app->run();
}





[1] You can set resources for widgets within a component when you specify a component's name because the name of component's base widget is the same as the name of the component; the X resource manager can successfully determine a widget hierarchy based on widget names. On the other hand, a component's class name has no relation to its base widget's class name. If you use a component class name in a resource specification, the X resource manager cannot determine the widget hierarchy for widgets in the component.