Managing Subclasses of Existing Components

While most components integrated into Builder Xcessory are relatively primitive, the component model that Builder Xcessory follows encourages the creation of abstract classes and subclasses of existing classes. If a class's superclass has already been integrated into Builder Xcessory, then some of the work described in Chapter 8--Modifying the WML File will be reduced.

Subclasses

Subclasses present more of an issue for editing the component's resources. "Editing Resources On Subclass Components" describes regular resources that have set/get methods provided in the sub- or superclass. However, as described in"Managing additional constructor parameters" , some resources are used as constructor parameters, which poses a difficult problem. The CreateComponent integration method for the superclass already deals with these resources. But, because that method creates an instance of our superclass, it is a practical impossibility to call the superclass's CreateComponent method in a subclass.

Writing a new CreateComponent method

One solution to this problem is to write a new CreateComponent method for the subclass that contains all the code required to deal with the superclass's constructor resources. This results in duplication of code for the superclass's constructor resources. If the superclass is changed then all the subclass integration code will need to be modified.

Writing a routine

An alternative solution to this problem is to write a routine that is called from the creation function for both the superclass and subclass. This routine can have any prototype, but at the very least will need to take an ArgList (containing a list of resources) and a Cardinal (set to the number of items in the ArgList). This routine would scan the resource list for constructor parameters and somehow provide the values to the caller. Both the superclass and subclass creation function could then use this routine to scan for the constructor parameter and so save recoding this search for every subclass that needs it.

Taking this approach, the creation function used for MyComponent in the previous example is split into two functions as shown in the following example.

Example

char *
FindMyComponentLabel(ArgList before, Cardinal before_count,
ArgList after, Cardinal *after_count)
{
// Storage for the constructor parameter value.
char *label = NULL;
// Scan the list for constructor parameters.
for (int i = 0; i < before_count; i++)
{
if (!strcmp("label", args[i].name))
{
label = (char *)args[i].value;
}
else
{
XtSetArg(after[*after_count],
before[i].name, before[i].value);
*after_count++;
}
}
return label;
}
extern "C" void *
CreateMyComponent(Widget parent, char *name,
ArgList args, Cardinal ac)
{
// So as not to pass the constructor parameter resource
// to the Edit function, we'll allocate a new resource
// list and copy in only the resources not corresponding
// to constructor parameters.
Cardinal rsc_count = 0;
ArgList resources = (ArgList)XtMalloc(ac*sizeof(Arg))
// Extract the constructor parameter from the ArgList
char *label = FindMyComponentLabel(args, ac,
resources, &rsc_count);
// Create the component with the constructor parameter
// and pass the remaining attributes to the Edit function.
MyComponent *obj = new MyComponent(name, parent, label);
EditMyComponent((void *)obj, True, resources, rsc_count);
// Free the allocated memory.
XtFree((char *)resources);
// Return the new component.
return (void *)obj;
}

Now, if we created a subclass of MyComponent that also needed to extract the label constructor parameter, its creation function would also call FindMyComponentLabel().

Editing a Component (AttributeFunction)

Editing component resources

Typically, you edit object attributes in C++ with set/get method pairs. Often, classes supply only a set method. Rare does the component supply only a get method. Builder Xcessory allows you to provide a method to do the binding between ArgList items and methods on a component by providing a single method to accomplish both set and get functions. This function should check each Arg in the ArgList to see if it matches a component resource (which will be defined in the WML file), or a base widget resource. The function supplied should match the prototype:

void EditComponent(void* object, Boolean set, ArgList args,
Cardinal arg_count);

Example Edit Method

This function is called every time an attribute is changed in the Builder Xcessory Resource Editor. The following section illustrates a sample edit method:

extern "C" void
EditMyComponent(void *object, Boolean set,
ArgList p_args, Cardinal p_ac)
{
MyComponent *obj = (MyComponent *)object;
// A loop index variable.
int i;
// Allocate two argument lists --
// one for base widget resources, used when values
// are both set and retrieved.
ArgList bw_args = (ArgList)XtMalloc(p_ac * sizeof(Arg));
Cardinal bw_ac = 0;
// and one for component level resources, used ONLY
// when values are retrieved.
ArgList c_args = (ArgList)XtMalloc(p_ac * sizeof(Arg));
Cardinal c_ac = 0;
// Loop through the parameter argument list either set or
// get the values appropriately.
for (i = 0; i < p_ac; i++)
{
if (!strcmp("myAttribute", p_args[i].name))
{
if (set)
{
// p_args[i].value contains a pointer to
// the value to set.
MyAttributeType *val =
(MyAttributeType *) p_args[i].value;
// Set the value of "myAttribute" by calling
// the corresponding method.
obj->setMyAttribute(val)
}
else
{
// Get the current value of "myAttribute"
// and store it in c_args[c_ac].value.
XtSetArg(c_args[c_ac], p_args[i].name,
obj->getMyAttribute());
c_ac++;
}
}
else
{
  // The attribute doesn't correspond to a component
  // method, so keep it to set or get on the base widget.
  XtSetArg(bw_args[bw_ac], p_args[i].name,
p_args[i].value);
  bw_ac++;
}
}
// Now take care of the base widget resources.
if (set)
{
XtSetValues(obj->baseWidget(), bw_args, bw_ac);
}
else
{
XtGetValues(obj->baseWidget(), bw_args, aw_ac);
// When we get values, we need to merge all the
// retrieved values back into the p_args ArgList.
// First put back the values fetched from the
// base widget.
for (i = 0; i < bw_ac; i++)
{
XtSetArg(p_args[i], bw_args[i].name,
bw_args[i].value);
}
// Then put back the values retrieved using
// methods of the component. Remember to start in
// the list where we left off in the last loop.
for (i = 0; i < c_ac; i++)
{
XtSetArg(p_args[bw_ac + i], c_args[i].name,
 args[i].value);
}
}
// Free the allocated memory.
XtFree((char *)bw_args);
XtFree((char *)c_args);
}

This function deals with one component attribute called "myAttribute". This resource will be described in the WML as a resource in standard resource format. The resource name in the WML code could be XmNmyAttribute.


Note: Builder Xcessory strips off the prefix "XmN", and allows any prefix that you want to use before the first N. For example, "BcN," "DtN," "XiN," and so forth.


This function performs the following tasks:

  • Takes in a list of arguments in the standard Xt Arg structure and cycles through them
  • Builds two destination lists one for component resources (c_args), and one for base widget resources (bw_args).

This routine behaves differently in the set and get modes. The two states are defined in the following sections.

Set mode


Note: In this case c_args is not actually used.


The argument "set" is True. For each Arg passed into the function in p_args, see if its name matches any of the component attributes (using strcmp to compare Arg.name to the attribute name). If the comparison succeeds, call the appropriate set methods to set the attribute using the value supplied in Arg.value. If all comparisons fail, add this Arg to the list of Args for the base widget.

Once each Arg has been checked, all that remains in the bw_args structure are resources to be applied to the base widget, so call XtSetValues and pass the bw_args structure.

Get mode

The argument "set" is False. In this case, we retrieve values from the component, so both bw_args and c_args are needed.

For each Arg passed into the function in p_args, see if its name matches any of the component attributes. If the comparison succeeds, call the appropriate get method to retrieve the value and assign that value to the Arg.value.

Some component attributes may not have a set/get pair. In this case, ignore the Arg and do not assign any value toArg.value, or increment the counter c_ac. It is also a good idea to use the WML construct NeverVerify = True for this resource, so that Builder Xcessory never passes it to the Edit function.

If the comparison with all component attributes fails, assign the Arg.name to the bw_args. Once all component attributes have been retrieved, the bw_args array will contain those resources to be retrieved from the component's base widget. Call XtGetValues to get the values for these resources.

Now c_args contains all the attributes from the component, and bw_args contains all the attributes from the base widget. These are now recombined into p_args to be returned to Builder Xcessory.

This code can act as template for any EditComponent function.

Documentation: