Component Development: Adding nested properties to the Object Inspector
When developing components it always comes to the point where you say: Do I really need that property? Does it make sense to offer even more customization?
I am always a fan of keeping the amount of customization limited to things that really will be used, but – let’s be honest – sometimes you can hardly imagine what people will be able to do with your components…
Thus, while developing a VCL component that generates a barcode using the Barcode4Me webservice API, I noticed that when adding all my properties the component looks rather cluttered in the Object Inspector. I have to admit that I never use the category arrangement for propties – ever since it has been introduced in Delphi 3. I never got used to it as the property was never in the category I expected it to be in. Thus, I always arrange “by Name”.
I might add that this post will be nothing out of the ordinary for the skilled Delphi developer. However, as it has been quite some time since I developed a VCL component that had a “nesting” of this sort, I needed to read up on the topic again. Here’s a write up…
The component offers context menu support and also a whole lot of properties to support the complete Barcode4Me API (link).
Note the property called “Properties” of type “TBarcodeProperties”. This property allows me to configure the properties of the Barcode and not the visual aspects of the component. The object inspector “magically” offers the nesting and sub-properties in a way:
This magic happens if you rememer one basic rule:
Derive your “nested” types from TPersistent
Sounds easy enough. What is the reason for this? Well, Delphi needs to be able to serialize the object instance into the form file (dfm). In order to do that, your class needs to be derived from TPersistent . The IDE starts with your form and serializes all “owned” components on the form, one after another.
TBarcodeProperties and TQRProperties are defined as follows:
type TBarcodeProperties = class(TPersistent) private // private field declarations omitted // private methods omitted public procedure Assign( Source : TPersistent ); constructor Create; destructor Destroy; override; published property &Type : TBarcodeType read FType write SetType; property Stretch : Boolean read FStretch write SetStretch; property Width : Word read FWidth write FWidth; property Height : Word read FHeight write FHeight; property ShowText: Boolean read FShowText write FShowText; property TextSize: Byte read FTextSize write FTextSize; property Value: String read FValue write FValue; property Border: Boolean read FBorder write FBorder; property InvertColors: Boolean read FReverseColors write FReverseColors; property QR : TQRProperties read FQRProperties write SetQRProperties; end;
It is important to provide the method Assign as values need to be assigned by value. For that reason we also use Assign for more nested types:
procedure TBarcodeProperties.Assign(Source: TPersistent); begin // check for type of Source, call Assign of inherited class if needed if (Source is TBarcodeProperties) then begin FStretch := (Source as TBarcodeProperties).Stretch; FType := (Source as TBarcodeProperties).&Type; FHeight := (Source as TBarcodeProperties).Height; // assign nested types FQRProperties.Assign((Source as TBarcodeProperties).QR); /// omitted.... end else inherited Assign( Source ); end;
If we simply used FQRProperties := (Source as TBarcodeProperties).QR we would be in big memory management trouble …
Thus, the component class also uses assign for the property “Properties”:
procedure TBarcodeProperties.SetQRProperties(const Value: TQRProperties); begin FQRProperties.Assign(Value); end;
With memory management in mind we also need to instantiate “Properties” and free it when the component is freed:
constructor TBarcodeProperties.Create; begin inherited Create; // ... FQRProperties := TQRProperties.Create; end; destructor TBarcodeProperties.Destroy; begin FQRProperties.Free; inherited; end;
Not that difficult after all, but yielding a much better developer experience when using the component as you can structure your properties in logical units. As far as I know, every professional component library with very customizable components uses this means to structure the properties.