Software Development Kit
to the Delphi-Win32 and Free Pascal compilers
Home > Wiki > Creating attributes

Creating attributes

english (en) | português (br)

User created attributes are created when there is a need to distribute the same functionality to different classes, or when there is need for a customization not provided by PressObjects' Data Type framework.

Examples: It's possible to have a TTelephone attribute that formats a number with parentheses, space and dash; or TCPF that validates the input for correct digits and formats the output; or for container attributes (Master-Details), such that the methods point to the correct class and takes advantage of the strong typing of Object Pascal.

Contents

Creating an attributes

Definition of an attributes is done in three simple steps:

  • Create a class, inheriting from an existing attribute
  • Overload the virtual methods to change the attribute's behavior
    • For container attributes (Parts and References, the method ValidObjectClass always has to be implemented)
  • Register the method in the unit's initialization section and unregister in the finalization section

Simple attributes

These are all the attributes that do not descent from TPressStructure, in other words, it should not be neither aggregates or compositions (foreign keys or master-detail)

Verifing user input

The following example creates an attribute that verifies the user input:

TCPF = class(TPressString)
protected
  procedure SetValue(const AValue: string); override;
end;

...

procedure TCPF.SetValue(const AValue: string);
begin
  if not IsValidCPF(AValue) then
    // erro
  inherited;
end;

...

initialization
  TCPF.RegisterAttribute;

finalization
  TCPF.UnregisterAttribute;

To use the new attribute in a business class:

TClient = class(TPressObject)
  _CPF: TCPF;
protected
  class function InternalMetadataStr: string; override;
end;

...

class function InternalMetadataStr: string;
begin
  Result := 'TClient IsPersistent (' +
   'CPF: TCPF;' +
   ')';
end;

From now on, whenever CPF is changed (by the MVP or by assigning a new value directly to the object), the routine will be called to valid the input.

Formatting data for display

To format for display, follow the steps in the previous section and overload the GetDisplayText method, like in the following example:

TTelefone = class(TPressString)
protected
  function GetDisplayText: string; override;
end;

...

function TTelefone.GetDisplayText: string;
begin
  Result := FormatPhone(Value);
end;

Beyond registering the component and using the business object properly. Whenever the component has focus the formatted content will be used instead of the native content.

Container attributes

One of the great benefits of registering a container attribute is the ability to use the compiler's strong type to validate the use of the methods to maintain the container. For example:

var
  VItem: TOrderItem;
begin
  VItem := AnOrder.Items.Add;

The compiler will complain because the method Add returns a TPressObject, which is the base class for all the business objects. Since we are manipulating an Order Item, we know about it, we need to cast it so that the compiler validates the line. Or instead of this, it's possible to create a new class that understands our container:

TOrderItemParts = class(TPressParts)
public
  function Add: TOrderItem;
  class function ValidObjectClass: TPressObjectClass; override;
end;

...

function TOrderItemParts.Add: TOrderItem;
begin
  Result := inherited Add as TOrderItem;
end;

class function TOrderItemParts.ValidObjectClass: TPressObjectClass;
begin
  Result := TOrderItem;
end;

...

initialization
  TOrderItemParts.RegisterAttribute;

finalization
  TOrderItemParts.UnregisterAttribute;
end;

Note: the method ValidObjectClass has to be overriden to return the correct target class.

Just use the attribute in the business class. Note that the declaration in the metadata and the attribute accessor in its own class:

TOrder = class(TPressObject)
  _Items: TOrderItemParts;
protected
  class function InternalMetadataStr: string;
public
  property Items: TOrderItemParts read _Items;
end;

...

class function TOrder.InternalMetadataStr: string;
begin
  Result := 'TOrder IsPersistent (' +
   'Items: TOrderItemParts;' +
   ')';
end;