Search code examples
classdelphipropertiescomponentsdelphi-2009

Delphi 2009: Component Object property default value


How to set the default of a component object property value?

Component Class Code:

unit CustomClass;

interface

uses
    Classes;

type
  TCustomClass = class;

  TConfiguration = class;

  TCustomClass = class (TComponent)
  private
    FConfiguration: TConfiguration;
    procedure SetConfiguration(const Value: TConfiguration);
  published
    property Configuration: TConfiguration read FConfiguration write SetConfiguration;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

  TConfiguration = class (TPersistent)
  private
    FDelay: Integer;
    FSize: Integer;
    procedure SetDelay(const Value: Integer);
    procedure SetSize(const Value: Integer);
  published
    property Delay: Integer read FDelay write SetDelay;
    property Size: Integer read FSize write SetSize;
  public
    procedure Assign(Source: TPersistent); override;
  end;

implementation

{ TCustomClass }

constructor TCustomClass.Create(AOwner: TComponent);
begin
  inherited;
  Configuration.FileName:= 'FileName';
  Configuration.Size := 10;
end;

destructor TCustomClass.Destroy;
begin
  Configuration.Free;
  inherited;
end;

procedure TCustomClass.SetConfiguration(const Value: TConfiguration);
begin
  FConfiguration.Assign(Value);
end;

{ TConfiguration }

procedure TConfiguration.Assign(Source: TPersistent);
begin
  inherited;
  Delay := (Source as TConfiguration).Delay;
  Size := (Source as TConfiguration).Size;
end;

procedure TConfiguration.SetDelay(const Value: Integer);
begin
  FDelay := Value;
end;

procedure TConfiguration.SetSize(const Value: Integer);
begin
  FSize := Value;
end;

end.

In my IDE it will appear as the object property was modified (bold blue).

I thought in making default and stored functions on the TConfiguration class properties, like so:

TConfiguration

interface

private
  function FileNameStored: Boolean;
published
  property FileName: string read FFileName write SetFileName stored FileNameStored;
  property Size: Integer read FSize write SetSize default 10;

implementation

function TConfiguration.FileNameStored: Boolean;
begin
  Result := FileName <> 'FileName';
end;

It colors the properties of TConfiguration in normal blue, but not the Configuration property of TCustomClass and it's not there that I want to set its default values, is on the TCustomClass, since TConfiguration maybe be used in another components.

Then, I also thought:

TCustomClass

interface

private
  function ConfigurationStored: Boolean;
published
  property Configuration: TConfiguration read FConfiguration write SetConfiguration stored ConfigurationStored;

implementation

function TCustomClass.ConfigurationStored: Boolean;
begin
  Result := Configuration.FileName <> 'FileName' and
    Configuration.Size <> 10;
end;

But, this only sets the TCustomClass Configuration property color to normal blue, not its properties.

ANSWER

As @RemyLebeau pointed out (in the first and top answer), there were errors in the code. In that component and that case I decided to not set any default value to the properties.


Solution

  • There are several bugs in your code.

    1. your TCustomClass constructor is not creating the TConfiguration object. You need to add that:

      constructor TCustomClass.Create(AOwner: TComponent);
      begin
        inherited;
        FConfiguration := TConfiguration.Create; // <-- add this
        FConfiguration.FileName := 'FileName';
        FConfiguration.Size := 10;
      end;
      

      That being said, the assignment of the FileName and Size properties should probably be moved to the TConfiguration constructor rather than TCustomClass constructor.

    2. A String property cannot be defined with a user-defined default value, the default is always a blank string. So your FileName property will always appear as modified when your component is created, because your constructor is assigning a non-default value to it. Your stored approach is the correct solution to handle that. Or, simply do not assign a default FileName at all, leave it blank. If the user does not assign an explicit FileName, your code could assume a default value if needed.

      An Integer property, on the other hand, can be defined with a user-defined default value. Your Size property is not doing that, though. It should be (especially if you move the assignment of the default value into the TConfiguration constructor):

      property Size: Integer read FSize write SetSize default 10;
      
    3. your TConfiguration.Assign() method is implemented incorrectly. By calling the inherited Assign() method before copying values, your code will always raise an EConvertError exception at runtime if the caller assigns one TConfiguration object to another. The reason is because the base class TPersistent.Assign() implementation simply calls Source.AssignTo(Self), and TConfiguration does not override the AssignTo() method, so TPersistent.AssignTo() gets called, which simply calls Dest.AssignError(Self), which raises the exception. So, DO NOT call the inherited Assign() method if the Source is actually a TConfiguration object:

      procedure TConfiguration.Assign(Source: TPersistent);
      begin
        if Source is TConfiguration then
        begin
          FDelay := TConfiguration(Source).Delay;
          FSize := TConfiguration(Source).Size;
        end else
          inherited;
      end;