Component Development: Adding AutoSize to your component

TImage  is one of the most famous graphical components of the VCL that offers ‘Auto Size’. The size of the component depends on its content. Perfect to make things easy for the developer.

Once again, I refer to my demo barcode component – which will be available for download rsn. Wouldn’t it be neat if the component adjusted its size according to the barcode stored?

In my last post I wrote about selecting ‘a good’ ancestor class. Digging in the ancestor classes of TGraphicControl  one quickly finds out that AutoSize is already being taken care of to make sure that every VCL component names and implements it the same way using a property called ‘AutoSize’.

TControl = class(TComponent)
private
  // ..

protected
  // ..
  property AutoSize: Boolean read FAutoSize write SetAutoSize default False;

end;

TControl  defines AutoSize in its protected -region ripe for us to make use of:

  TFlxVclBarcode = class(TGraphicControl)
  private
    { Private declarations }
    FAutoSize: Boolean;

    procedure UpdateSize;

  protected
    { Protected declarations }
    procedure Loaded; override;
    procedure SetAutoSize(Value: Boolean);  override;

  published
    { Published declarations }
    property AutoSize:Boolean read FAutoSize write SetAutoSize;

Mind that the listings only contain properties that are relevant for implementing AutoSize. Firstly, we need a setter-method so that we can react to the developer changing the property.

This yields that we cannot simply publish the inherited property, but must also introduce a field called FAutoSize  to store the boolean value.

We call the setter-methodSetAutoSize and define a protected method accordingly. Note that we can use override as the method is already defined in TControl .

The necessity to override Loaded  will be explained later.

So what happens if the developer changes the setting of AutoSize? If AutoSize is false, the developer has full reign how to set ClientWidth and ClientHeight of the component. However, as soon as AutoSize is true, these values are controlled by the barcode that is stored in the component. If there is not barcode stored in the component for whatever reason, the component will use the values of the barcode property for Width and Height. The values are designed to actually request a barcode of a certain size. However, the BarCode4Me API does not allow for individual sizes for QR codes and thus the barcode ‘might’ have a different size when returned by the web service. However, this is a good starting point and also a good way to make sure that the component does not ‘vanish’.

This is actually the golden rule of using AutoSize:

Make sure that AutoSize has always a means to present the component with a visible/clickable size! If there is no means to determine an AutoSize use the ComponentState property in order to determine that the developer is designing the component in the forms editor and thus needs ‘default’ values.

Applying the golden rule to the barcode example: If there is no barcode stored inside the component, i.e. before data has been fetched, the bitmap has the dimensions 0x0. This would make the component very clumsy to select in the forms editor. Thus, I decided to use the values that are most likely to be expected after the web request as a starting point in the IDE.

We start by implementing the setter method:

procedure TFlxVclBarcode.SetAutoSize(Value: Boolean);
begin
  if Value <> FAutoSize then
  begin
    FAutoSize := Value;

    UpdateSize;
  end;
end;

Looks like nothing special: Make sure there has been an actual change and then change the field value to the new value. We then call another method called “UpdateSize “.

This is where we will modify the size of the component according to the setting of AutoSize and the size of the bitmap inside the component:

procedure TFlxVclBarcode.UpdateSize;
begin
  // only change size if AutoSize is used
  if ( FAutoSize ) then
  begin    
    if Assigned(FBitmap) then
    begin
      // use dimensions of actual barcode
      self.ClientWidth := FBitmap.Width;
      self.ClientHeight := FBitmap.Height;
    end
    else
    begin
      // use dimensions of barcode properties
      self.ClientWidth := self.Properties.Width;
      self.ClientHeight := self.Properties.Height;
    end;
  end;
end;

The comments explain what is going on quite detailed and explained before listing the code. We make sure that AutoSize is enabled. If it is enabled either use the size of the actual barcode stored inside the component for the size of the component or the specified height and width of the barcode properties.

Does this suffice?

It will work as expected when you change the AutoSize property in the designer. However, it will fail if…

  • …the size of the component is changed in the forms editor or otherwise, as UpdateSize is not called again.
  • …the form is being deserialized, the component is initialized with the values stored in the dfm file, but UpdateSize is not called.

Handling Resizing

Yet again the ancestor helps us by providing a method called ‘Resize’. This is actually the method that is being called when we resize a component and takes care of calling our own Resize-event – if we are ever so courageous to define one (stay tuned…). So, let’s override the Resize method using the following definition…

  protected
    { Protected declarations }
  
    procedure Resize; override;

…and implementation:

procedure TFlxVclBarcode.Resize;
begin
  UpdateSize;

  inherited;
end;

This way we call UpdateSize  after the component has been resized and make sure that the ancestor stuff is being taken care of. Do not forget inherited!

Inherited is a means by which computer programming reminds you to take care of your parents… just a helper-sentence that might help you to remember calling inherited all the time.

If AutoSize is set to true this implementation will also make sure that your resizing is immediately voided. The component will not be resized – it will be fixed on the form. But, you will still be able to move it. Just as an aside, if you meddle at the wrong places of the ancestor, nasty side effects might hit you.

This takes care of issue number 1. Now on to number 2.

Using Loaded

The issue we have is that we can initialize our component using Create  and would be able to call UpdateSize  then. However, after Create  the component is being filled with the stored values from the dfm file and thus, we get no more chances to access the properties before the component is drawn on the form. Or do we?

That is exactly what Loaded  is to be used for. It gives you an opportunity to execute code right after the component has been read from the dfm file and to update your component state accordingly:

procedure TFlxVclBarcode.Loaded;
begin
  inherited Loaded;

  UpdateSize;
end;

Two lines that make a huge difference. Again, as we override, we need to call inherited.

That’s it. We have a component that fulfills all the requirement of a AutoSize-enabled component.

As this post was rather long and detailed, the next post will cover some real basics to make use of things that are already ‘there’ in ancestors and are not used automatically…

Tags: , ,

Partnerships




Top