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).

barcode_01

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:

properties

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.

Tags: ,

Partnerships




Top