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 4. shows the default appearance of a StartStopPanel object.

Figure 4. Default Appearance of a StartStopPanel Component

Example 8. A Simple User-Defined Component

The following code section lists the full implementation of this class.

Code

//////////////////////////////////////////////////////////////
// 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);
}

Examples of Using and Subclassing a Component Class

Example 8. slightly changes the StartStopPanel class from previous examples by declaring the member function 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 9. 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 an ObjectPak 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 9. Using a Component Directly

Code

//////////////////////////////////////////////////////////////
// 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 (Refer to Chapter 3--Application Class for more information about the VkApp:

//////////////////////////////////////////////////////////////
// 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 5. shows the resulting PanelWindow window displayed by this program.

Figure 5. 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 10. creates ControlPanel, a subclass of StartStopPanel that incorporates the features implemented in the PanelWindow class shown in Example 9.

Example 10. Subclassing a Component

Code

//////////////////////////////////////////////////////////////
// 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:

Code

//////////////////////////////////////////////////////////////
// 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();
}