VkComponent Class

All ViewKit ObjectPak components are derived from the 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 the following sections. The following list summarizes the more important characteristics:

Characteristics enforced by VkComponent

  • 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. ObjectPak 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 ObjectPak is discussed in "Using Xt Callbacks with Components" .
  • All component classes should override the virtual className() member function so that it returns a string identifying the component's class. ViewKit ObjectPak 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 ObjectPak 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.

Arguments

Each derived class's constructor should take at least two arguments:

  • Component's name
  • A widget that serves as the parent of the component's widget tree

Initialization steps

Each derived class must perform at least the following 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 ObjectPak 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.

Example 2. Component Constructor

For example, consider a user-defined component called StartStopPanel that implements a simple control panel containing Start and Stop buttons. This section illustrates the code for possible constructor for this class:

Code

/////////////////////////////
// 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 ObjectPak components.

Component Destructors

The virtual VkComponent destructor performs the following functions:

  1. Triggers the VkComponent::deleteCallback ObjectPak callback for that component. ViewKit ObjectPak callbacks are described in "ViewKit ObjectPak Callback Support" , and the VkComponent::deleteCallback is described in "Predefined ObjectPak 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.

Example 3. Freeing Space in a Component Destructor

For example, if your class allocates space for a string, you should free that space in your destructor, as shown in the following code example.

Code

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 ObjectPak 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. ObjectPak 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 in the following example code:

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 are simply:

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 the 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 ObjectPak 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. "Exiting ViewKit ObjectPak Applications" provides more information on how to quit a ViewKit ObjectPak application, and "Additional Virtual Functions and Data Members" 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 ObjectPak 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.

Guidelines for using Xt callbacks

Generally, you should follow these guidelines for using Xt callbacks with ViewKit ObjectPak 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 ObjectPak 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. for the simple control panel component described in "Component Constructors" omitted the setup of callback routines to handle the activation of the buttons.

Implementing callbacks

To implement these callbacks you must perform the following tasks:

  1. Create regular member functions to perform the tasks desired in response to the user clicking on 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 on the Start button, and to call StartStopPanel::stop() when the user clicks on 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 4. Component Constructor with Xt Callbacks

This section illustrates the code for the updated StartStopPanel constructor, which installs the Xt callbacks for the buttons.

Code

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, dangling references--pointers to memory that once represented widgets, but which are no longer valid--are often left behind. For example, when a widget is destroyed, its children are also destroyed. Keeping track of references to these children is difficult, and you will often write a program that accidentally references the widgets in a class after the widgets have already been destroyed. Sometimes, applications try to delete a widget twice, causing the program to crash. In this situation, calling XtSetValues() or other Xt functions with a widget already deleted also occurs easily.

Protecting encapsulation of ViewKit classes

To protect the encapsulation of ViewKit ObjectPak 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 cannot 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, 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 ObjectPak 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()