September 1997

Building components, part 1

by Sam Azer

The Borland C++Builder Component palette is a toolbox of powerful classes. It's similar to the compiled function libraries of yesteryear, but with the advantage that you can manipulate each class visually. After you've had a chance to discover how valuable the components are, you'll want to roll your own. In many cases, your components will be extensions to existing ones; in other cases, you'll implement entirely new functionality. In all cases, implementing a component is surprisingly simple, once you know what goes on behind the scenes. In this article, we'll give you a peek.

What's a component?

A component is a class that you must write in such a way that it performs two jobs. First, when you drop a component from the Component palette onto a form, the component talks to the Object Inspector and the form editor in what is called design state. In this state, you edit properties using the Object Inspector and tie in additional event-handling code. The form editor collects property values and writes them to the form file, which is later bound as a resource to the final program. This process allows each component to use persistent data (property values) to adjust its behavior. All these interactions affect the second job that a component must do: perform its run-time program-related duties. To make this process possible, C++Builder enhances ANSI C++ with a few language extensions and adds runtime support code through the Visual Component Library (VCL). Let's take a look.

Language extensions

Three major language extensions support component writing in C++ Builder: __property, __published, and __closure. We'll begin by examining __property and __published.

__property and __published

The __published keyword is another version of the usual C++ public member-access specifier. The two are identical, except that __published properties appear in the Object Inspector at design time. The __property keyword offers another way to control access to member data within a class. However, its most important functions are related to the Object Inspector and support for the persistent data feature of objects in C++Builder.

In its simplest usage, a property is another name for a class member. The value assigned to the member at design time (through the Object Inspector or otherwise) is stored in the form file by default:

 protected:      
   AnsiString FFileName;
 __published:
   __property AnsiString FileName = 
      {read = FFileName, write=FFileName };
This code allows you to set the value of FFileName through the Object Inspector at design time. The value is then saved in the form file. At runtime, the VCL loads the value back into FFileName after construction. If you don't want the value to be persistent, you can disable storage by adding stored=false, as follows:
__property AnsiString FileName = {read =
FFileName, write=FFileName, stored=false }; 
Components in the real world need to offer quite a few options to maintain flexibility. They often have long lists of properties with usable default values. Storing all this information in the form file wastes space, and reading it back takes time--which is not desirable, considering that in most cases, few of the defaults change.

To minimize the volume of data in the form file, you can specify a default value for each property. Note that doing so doesn't set the default value--the constructor is responsible for doing that. When writing to the form file, the form editor skips any properties whose values haven't been changed:

 __property AnsiString FileName = {read = 
   FFileName, write=FFileName, 
   default="MyFile.txt", stored=true };
The __property mechanism also allows you to specify getter and setter member functions to be called instead of directly accessing member data. For simple properties, the Get function takes no arguments, but returns a value of the property's type. The Set function takes an argument of the property's type, but returns no value. You usually use these functions to check for invalid data, as in this example code:
int FMy123;
void SetMy123(int x) { if (x >=1 and x <=3) 
FMy123=x; } // else do nothing...
int GetMy123(void) { return FMy123; };
__property int My123 = { read=GetMy123, 
write=SetMy123 };
However, you can also use them for more complex operations, such as database lookups and updates.
Events and __closure
Generally, components are designed to support a concept without actually handling all the details. For example, C++Builder's System Timer component is designed to call an event at a specified interval. The concept behind the design is that some task must be performed on a regular basis--the component doesn't know or care what the task is. To handle this situation, the component contains a pointer to the code to be executed: the event handler. At design time, you code a new handler and set the pointer. At runtime, the component checks the pointer. If it's null, the component does something sensible--in most cases, it ignores the event. If the pointer isn't null, the component calls the handler.

In C or ANSI C++, you'd normally use a function pointer for this purpose. But in C++Builder, doing so would cause problems, because most event handlers are methods in an instance of a form class. To call them, a pointer must be passed to the form object as a hidden parameter to the method--something a function pointer can't do. To get around this problem, C++Builder introduces pointers of type __closure. These objects contain two pointers: one that points to the class object, and another that points to the method being called. In all other respects, __closure pointers are the same as function pointers.

You'll want to follow a few conventions regarding closures--they aren't enforced, but respecting them is a good idea. First, use a typedef for each event you want to support in your component; the name you use should start with a T and end with the word Event. Next, declare a private or protected member whose name starts with the letter F. Finally, if you want to control access to this member or if you want to publish it in the Object Inspector, create a property whose name starts with On. For example, consider these lines:

 typedef void __fastcall 
   (__closure *TMyUpdateEvent)( TObject *Sender);
 ...
 TMyUpdateEvent FMyUpdate; // user proc. to 
                           // override default 
                           // update behavior
 ...
 __property TMyUpdateEvent OnMyUpdate = 
   { read=FMyUpdate, write=FMyUpdate };
You can set a closure to point to a member function in the code or through the Object Inspector. As with any pointer to a function, you should test it before calling it, like this:
if ( FMyUpdate )   // if not null
FMyUpdate(this); // then call it

Design and run

Now that you know what language tools are available, let's look at what happens when the component is being used. We'll begin by examining the Component palette.

The Component palette

To add a component, C++Builder compiles the component's source code and then links the resulting object code into the palette. The default Component palette is a DLL called CMPLIB32.CCL; Figure A shows this palette in the Environment Options dialog box. Figure A: C++Builder opens the default Component palette on the Palette tab of the Environment Options dialog box.

Figure A

When C++Builder opens a Component palette, it calls a registration function, Register(), for each component.

Register() calls RegisterComponents() to actually install the component in the IDE. If the directory from which the component was loaded includes a resource (RES) file that has the same name as the component file and that contains a 24-bit square icon, then RegisterComponents() uses that icon to represent the component in the palette. Otherwise, it uses a default icon.

The Register() function can also install new property editors for the Object Inspector and component editors for the form editor. Component editors are listed in a component's speed menu. Usually, they're forms that help the programmer configure the component.

Note that all the registration functions have the same name: Register(). You differentiate among them by the namespace in which they're defined, which must have the same name as the header file for the component. For example, if the header file is MyHeader.H, the namespace must be MyHeader.

TComponent and RTTI

VCL includes an important class called TComponent, which handles most of the interfacing between a component and the IDE. This class is defined to use Run Time Type Identification (RTTI); so, any class that inherits TComponent will also use RTTI automatically. As a result, the compiler will collect information about your component class that can be presented in a human-readable form. For example, if you click on an event field in the Object Inspector, the IDE will create a blank function with the correct name and parameter list. This bit of magic is possible because of the RTTI on the closure.

The Object Inspector also uses type information to determine the correct property editor to use. Default editors are available for a variety of types, and you can register any new editors as needed.

Design state

When you drop a component on a form, the form editor creates an instance of the component. This instance is actually running at design time--your constructor and property getter/setter functions will certainly be called, as may other functions. In some cases, this behavior can be a problem. You may need to detect the design state in your code to have the component behave properly. There are different ways to express this test, but they all basically check the csDesigning flag in the ComponentState member of TComponent, as follows:
// do nothing if this instance is being 
// edited on a form
if (ComponentState.Contains(csDesigning)) return;

Runtime

As we mentioned, the form editor calls TComponent to get property values and writes them to the form file. At runtime, the component will eventually be constructed. Later, functions in TComponent will load property values from the form, which has been bound by the linker as a resource in the EXE file. To allow last-minute initialization after all properties have been loaded, TComponent defines a virtual function, Loaded(), which you can override. Remember to declare the function as virtual in your component's class, and don't forget to call any ancestors!

Wrap-up

Components are an important part of C++Builder. In a future article, we'll demonstrate just how easy it is to build a useful component: a smarter TDBComboBox. After that, we'll examine property and component editors.