Writing Property Editors

Top  Previous  Next

 

When you select a component in the designer its properties are displayed in the object inspector. You can create your own editor for any property. The “Font” property, for example, has an editor : if this property is selected a '...' button appears to the right of the line; open the standard "font properties" dialogue by clicking this button. Another example is the “Color” property. It opens the standard colors and color specimens names in a drop-down list.

 

“TfrxPropertyEditor” is the base class for all property editors and is declared in the “frxDsgnIntf” unit:

 

TfrxPropertyEditor = class(TObject)

protected

procedure GetStrProc(const s: String);

function GetFloatValue: Extended;

function GetOrdValue: Integer;

function GetStrValue: String;

function GetVarValue: Variant;

procedure SetFloatValue(Value: Extended);

procedure SetOrdValue(Value: Integer);

procedure SetStrValue(const Value: String);

procedure SetVarValue(Value: Variant);

public

constructor Create(Designer: TfrxCustomDesigner); virtual;

destructor Destroy; override;

function Edit: Boolean; virtual;

function GetAttributes: TfrxPropertyAttributes; virtual;

function GetExtraLBSize: Integer; virtual;

function GetValue: String; virtual;

procedure GetValues; virtual;

procedure SetValue(const Value: String); virtual;

procedure OnDrawLBItem(Control: TWinControl; Index: Integer;

                        ARect: TRect; State: TOwnerDrawState); virtual;

procedure OnDrawItem(Canvas: TCanvas; ARect: TRect); virtual;

property Component: TPersistent readonly;

property frComponent: TfrxComponent readonly;

property Designer: TfrxCustomDesigner readonly;

property ItemHeight: Integer;

property PropInfo: PPropInfo readonly;

property Value: String;

property Values: TStrings readonly;

end;

 

You also can inherit from any of the following classes, which provide some basic functionality for working with a property of the corresponding type:

 

TfrxIntegerProperty = class(TfrxPropertyEditor)

TfrxFloatProperty = class(TfrxPropertyEditor)

TfrxCharProperty = class(TfrxPropertyEditor)

TfrxStringProperty = class(TfrxPropertyEditor)

TfrxEnumProperty = class(TfrxPropertyEditor)

TfrxClassProperty = class(TfrxPropertyEditor)

TfrxComponentProperty = class(TfrxPropertyEditor)

 

 

Several properties are defined in the TfrxPropertyEditor class:

 

- Componentlink to parent component (not to property itself!) to which the given property belongs
- frComponentthe same, but cast to TfrxComponent type (convenient in some cases)
- Designer        link to the report designer
- ItemHeight        item height, in which property is displayed; it can be useful in OnDrawXXX
- PropInfolink to PPropInfo structure, which contains information about the edited property
- Value        property value displayed as a string
- Values        list of values; this property is to be filled by the “GetValue” method, if the “paValueList” attribute is defined (see below)

 

The following are system methods. They can be used to get or set the edited property value.

 

function GetFloatValue: Extended;

function GetOrdValue: Integer;

function GetStrValue: String;

function GetVarValue: Variant;

procedure SetFloatValue(Value: Extended);

procedure SetOrdValue(Value: Integer);

procedure SetStrValue(const Value: String);

procedure SetVarValue(Value: Variant);

 

You should use the methods appropriate to the property type. So, use “GetOrdValue” and “SetOrdValue” methods if the property is of “Integer” type, etc.. These methods are also used for working with properties of “TObject" type, since these properties contain a 32-bit object address. In these cases, just use a cast like: MyFont := TFont(GetOrdValue).

 

To create your own editor, inherit from the base class and override some methods declared in the public section (which methods is dependent on the property type and functionality you wish to implement). One of the methods that will need to be overridden is “GetAttributes” which returns a set of property attributes. Attributes are defined in the following way:

 

TfrxPropertyAttribute = (paValueList, paSortList, paDialog, paMultiSelect,

                        paSubProperties, paReadOnly, paOwnerDraw);

TfrxPropertyAttributes = set of TfrxPropertyAttribute;

 

Attribute meanings are:

 

- paValueList property represents a  drop-down list of values (for example the “Color” property);
if present then the “GetValues” method should be overridden
- paSortListsorts list values; it is used together with paValueList
- paDialogproperty has an editor; if present a '...' button is displayed to the right of the line; Edit method is called by clicking on it
- paMultiSelectallows more than one object to be selected at the same time; some properties (such as “Name”, etc) do not have this attribute
- paSubPropertiesproperty is object of TPersistent type and has its own properties, which should also be displayed (for example the “Font” property)
- paReadOnlyvalue cannot be changed in the editor; some properties, of “Class” or “Set” types, have this attribute
- paOwnerDrawproperty value is drawn by the “OnDrawItem” method; if the “paValueList” attribute is present then a drop-down list is drawn by the OnDrawLBItem method

 

The Edit method is called in two ways: either by selecting a property and double-clicking its value or (if property has paDialog attribute) by clicking the '...' button. This method should return “True” if property value was modified.

 

The “GetValue” method should return the property value as a string (it will be displayed in the object inspector). If you inherit from the TfrxPropertyEditor base class this method must be overridden.

 

The “SetValue” method is to set the property value, transferred as a string. If you inherit from the TfrxPropertyEditor base class this method must be overridden.

 

The “GetValues” method should be overridden if the “paValueList” attribute is set. This method should fill the “Values” property with values.

 

The following three methods allow manual drawing of the property value (the Color property editor works in the same way). These methods are called if the “paOwnerDraw” attribute is set.

 

The “OnDrawItem” method is called when the property value is drawn in the object inspector (when the property is not selected; otherwise its value is simply displayed in the editing line). For example, the Color property editor draws a rectangle to the left of property value, filled with the corresponding color.

 

The “GetExtraLBSize” method is called if the “paValueList” attribute is set. This method returns the number of pixels by which to adjust the “Drop-Down List” width to display the list. By default this method returns the value corresponding to the cell height. If you need to draw picture, with width larger than its height, the given method should be overridden.

 

The “OnDrawLBItem” method is called when drawing a string in a drop-down list if the paValueList attribute is set. This method is actually the TListBox.OnDrawItem event handler and has the same set of parameters.

 

Property editor registration is performed by the procedure defined in the frxDsgnIntf file:

 

procedure frxPropertyEditors.Register(PropertyType: PTypeInfo;

                                     ComponentClass: TClass;

                                     const PropertyName: String;

                                     EditorClass: TfrxPropertyEditorClass);

 

- PropertyTypeinformation about the property type, returned by the “TypeInfo” system function, for example TypeInfo(String)
- ComponentClasscomponent name, with property you want to edit (may be nil)
- PropertyNamename of property you want to edit (may be a blank string)
- EditorClass        property editor name

 

Only the “PropertyType” parameter needs to be specified. The “ComponentClass” and/or “PropertyName” parameters may be blank. This allows the editor to be registered either to any property of PropertyType type, to any property of concrete ComponentClass components and its descendants, or to a PropertyName specific property of a specific component (or to any component if the ComponentClass parameter is blank).

 

Let's look at three property editor examples. FastReport requires the Editor code to be placed in a file having the same name as the file having the component code, with 'Editor' suffixed to it.

 

--------------------------------------------------

 

{ TFont property editor displays editor button('...') }

{ inherit from ClassProperty }

type

 TfrxFontProperty = class(TfrxClassProperty)

public

  function Edit: Boolean; override;

  function GetAttributes: TfrxPropertyAttributes; override;

end;

 

function TfrxFontProperty.GetAttributes: TfrxPropertyAttributes;

begin

 { property has nested properties and  editor;

   it cannot be edited manually }

 Result := [paMultiSelect, paDialog, paSubProperties, paReadOnly];

end;

 

function TfrxFontProperty.Edit: Boolean;

var

 FontDialog: TFontDialog;

begin

 { create standard dialogue }

 FontDialog := TFontDialog.Create(Application);

try

   { take  property value }

   FontDialog.Font := TFont(GetOrdValue);

   FontDialog.Options := FontDialog.Options + [fdForceFontExist];

   { display dialogue }

   Result := FontDialog.Execute;

   { bind new value }

  if Result then

     SetOrdValue(Integer(FontDialog.Font));

finally

   FontDialog.Free;

end;

end;

 

{ registration }

frxPropertyEditors.Register(TypeInfo(TFont), nil, '', TfrxFontProperty);

 

--------------------------------------------------

 

{ TFont.Name property editor displays a drop-down list

 of available fonts;

 inherit from StringProperty, as property is of string type }

type

 TfrxFontNameProperty = class(TfrxStringProperty)

public

  function GetAttributes: TfrxPropertyAttributes; override;

  procedure GetValues; override;

end;

 

function TfrxFontNameProperty.GetAttributes: TfrxPropertyAttributes;

begin

 Result := [paMultiSelect, paValueList];

end;

 

procedure TfrxFontNameProperty.GetValues;

begin

 Values.Assign(Screen.Fonts);

end;

 

{ registration }

frxPropertyEditors.Register(TypeInfo(String), TFont,

                           'Name', TfrxFontNameProperty);

 

 

--------------------------------------------------

 

{ TPen.Style property editor displays a picture,

 which is an example of the selected style }

type

 TfrxPenStyleProperty = class(TfrxEnumProperty)

public

  function GetAttributes: TfrxPropertyAttributes; override;

  function GetExtraLBSize: Integer; override;

  procedure OnDrawLBItem(Control: TWinControl; Index: Integer;

     ARect: TRect; State: TOwnerDrawState); override;

  procedure OnDrawItem(Canvas: TCanvas; ARect: TRect); override;

end;

 

function TfrxPenStyleProperty.GetAttributes: TfrxPropertyAttributes;

begin

 Result := [paMultiSelect, paValueList, paOwnerDraw];

end;

 

{ method draws thick horizontal line with selected style }

procedure HLine(Canvas: TCanvas; X, Y, DX: Integer);

var

 i: Integer;

begin

with Canvas do

begin

   Pen.Color := clBlack;

  for i := 0 to 1 do

  begin

     MoveTo(X, Y - 1 + i);

     LineTo(X + DX, Y - 1 + i);

  end;

end;

end;

 

{ drawing drop-down list }

procedure TfrxPenStyleProperty.OnDrawLBItem

              (Control: TWinControl; Index: Integer;

               ARect: TRect; State: TOwnerDrawState);

begin

with TListBox(Control), TListBox(Control).Canvas do

begin

   FillRect(ARect);

   TextOut(ARect.Left + 40, ARect.Top + 1, TListBox(Control).Items[Index]);

 

   Pen.Color := clGray;

   Brush.Color := clWhite;

   Rectangle(ARect.Left + 2, ARect.Top + 2,

             ARect.Left + 36, ARect.Bottom - 2);

 

   Pen.Style := TPenStyle(Index);

   HLine(TListBox(Control).Canvas, ARect.Left + 3,

         ARect.Top + (ARect.Bottom - ARect.Top) div 2, 32);

   Pen.Style := psSolid;

end;

end;

 

{ drawing property value }

procedure TfrxPenStyleProperty.OnDrawItem(Canvas: TCanvas; ARect: TRect);

begin

with Canvas do

begin

   TextOut(ARect.Left + 38, ARect.Top, Value);

 

   Pen.Color := clGray;

   Brush.Color := clWhite;

   Rectangle(ARect.Left, ARect.Top + 1,

             ARect.Left + 34, ARect.Bottom - 4);

 

   Pen.Color := clBlack;

   Pen.Style := TPenStyle(GetOrdValue);

   HLine(Canvas, ARect.Left + 1,

         ARect.Top + (ARect.Bottom - ARect.Top) div 2 - 1, 32);

   Pen.Style := psSolid;

end;

end;

 

{ return picture width }

function TfrxPenStyleProperty.GetExtraLBSize: Integer;

begin

 Result := 36;

end;

 

{ registration }

frxPropertyEditors.Register(TypeInfo(TPenStyle), TPen,

                           'Style', TfrxPenStyleProperty);