April 1999
Version: 1.0, 3.0, and 4.0

Enumerating components

by Kent Reisdorph

There are several questions that pop up over and over again on the Inprise newsgroups. One such question (in various incarnations) is "How do I clear the text of all edit controls on a form?" This type of question is a common query from Visual Basic programmers moving to C++Builder. Those programmers are accustomed to using VB component arrays and want to accomplish the same thing in C++Builder. This article will explain how to use the VCL's Components property to enumerate all components on a form. We'll also show you how to use the dynamic_cast operator to determine the type of each component on the form.

The components array

The TForm class has two properties that can be used to enumerate a form's components. The ComponentCount property is a read-only property that returns the number of components owned by the form. This includes components placed on the form at design time, and components (visual and non-visual) that are created at runtime. The Components property is an array property. It contains a pointer to every component owned by the form. If you look at the declaration for the Components property, you'll find that it contains a list of TComponent pointers. TComponent is, ultimately, the base class for all components. By storing a list of TComponent pointers, the pointers can be dealt with at their lowest common denominator. In your code you can cast the pointer to any type you want, in order to access the properties of a particular type of component. We'll explain exactly how to do that a bit later in the article.

The Components array is used internally by the VCL. Among other things, the array is used by the VCL when it deletes all owned objects for a form, usually when the form itself is deleted.

Enumerating a form's components

A form's components can easily be enumerated using a simple for loop. The following code enumerates the components on a form and places the component names in a memo:
Memo1->Lines->Clear();
for (int i=0;i<ComponentCount;i++) {
	String S = Components[i]->Name;
	Memo1->Lines->Add(S);
}
This code is mostly self-explanatory. The loop starts at 0 (the component array is 0-based) and stops at ComponentCount minus 1. Each component's Name property is read and is added to the memo. As is our practice, we've broken the code down to make it easier to understand. Most of the time you would write the code as follows:
for (int i=0;i<ComponentCount;i++) 
	Memo1->Lines->Add(Components[i]->Name);
Keep in mind that dynamically created components will have a blank Name property, unless you've specifically provided a name when you created the component. For example:
TLabel* label = new TLabel(this);
label->Top = 20;
label->Left = 20;
label->Parent = this;
label->Caption = "Hello";
In this case, the label's Name property isn't assigned a value and will contain an empty string. You can use the pointer returned from the Components array directly if you only need to access properties of the component that are found in the TComponent class. For example, let's say you want to enumerate just those components that have their Tag property set to 1. In that case, the code would look like this:

 

for (int i=0;i<ComponentCount;i++)
	if (Components[i]->Tag == 1)
		Memo1->Lines->Add(Components[i]->Name);
Here, only those components with a Tag property equal to 1 are added to the memo. If you want to access the properties of a particular type of component (a TEdit, for example), then you'll need to cast the TComponent pointer. Let's look at that next.

Casting the TComponent pointer

In a real-world application, you'll likely need to cast the pointer returned from the Components array in order to access the individual properties of a particular type of component. Let's say, for example, that you want to clear the text in all edit controls on a form. In that situation, you'll have to cast the TComponent pointer to a TEdit pointer. There's a right way and a wrong way to perform this cast.

The wrong way

The wrong way is to use a C-style cast. This code illustrates the wrong way of casting the pointer returned from the Components array:
for (int i=0;i<ComponentCount;i++) 
	((TEdit*)Components[i])->Text = "";
This code will work if, and only if, all components on the form are TEdits. If a component other than a TEdit is encountered, an access violation will almost certainly occur when you attempt to access the Text property. Given that, you should never use a C-style cast in this case. (In fact, we would argue that you should never use C-style casts in a C++ program.)

The right way

The right way to perform the cast is with the dynamic_cast operator. The dynamic_cast operator will return a pointer to the object if the cast can be performed, or 0 if the cast can't be performed. The basic idea when enumerating the components on the form is to cast every component to the type you want and see if the cast fails or succeeds. If the cast fails, then the component isn't of the type you're looking for. If the cast succeeds, then you can use the pointer returned from dynamic_cast to carry out some action.

Clearing the text in all edit controls

Now let's look at how dynamic_cast can be used to clear the text in all edit controls on a form. Here's the code:
for (int i=0;i<ComponentCount;i++) {
	TEdit* edit;
	edit = dynamic_cast<TEdit*>(Components[i]);
	if (edit)
		edit->Text = "";
}
Each component in the Components array is cast to a TEdit. If the cast fails, then the loop continues without further processing. If the cast succeeds, then we use the pointer returned from dynamic_cast to set the Text property to an empty string. Let's look at another example. This code changes the font of all labels on the form to bold:
for (int i=0;i<ComponentCount;i++) {
	TLabel* label = dynamic_cast
		<TLabel*>(Components[i]);
	if (label)
		label->Font->Style = 
			TFontStyles() << fsBold;
}
This code also illustrates the proper way to set a font's style. See the article, "Dealing with sets," for more information on the correct way of adding values to a set. You can put any number of dynamic_cast calls within your enumeration loop. This code, for example, combines the two operations we've seen up to this point:

 

for (int i=0;i<ComponentCount;i++) {
	TEdit* edit = dynamic_cast
		<TEdit*>(Components[i]);
	if (edit)
		edit->Text = "";
	TLabel* label = dynamic_cast
		<TLabel*>(Components[i]);
	if (label)
		label->Font->Style = 
			TFontStyles() << fsBold;
}
If a TEdit is found, then its text is cleared. If a TLabel is found, its font style is changed to bold. All other components on the form are ignored.

Conclusion

Enumerating components on a form is a trivial task once you understand how the Components property works. Using dynamic_cast to cast the pointer returned from the Components array gives you full control over all components on your forms.