Skip to main content

Implementing Custom Property Editor

  • 4 minutes to read

The Delphi IDE provides an Object Inspector to edit the values of published properties at design time for the components placed on a form.

The purpose of the cxRTTIInspector control is the same as for the IDE Object Inspector but for runtime.

To bind the cxRTTIInspector runtime Object Inspector to the desired component at runtime use the TcxRTTIInspector.InspectedObject property as shown in the code snippet below:

//...
RTTIInspector.InspectedObject := DBVerticalGrid;

When applied the user can browse and edit the values of the published properties of the bound component at runtime:

The runtime Object Inspector enables the editing of properties values as defined by the TcxPropertyEditor descendants for the appropriate types of properties. You can provide your own alternate editor for specific properties by implementing and registering the TcxPropertyEditor descendant.

The example below shows how to implement a combo box editor for selecting the image index in the row header. This example is based on the ExpressVerticalGrid RTTIInspector Demo shipped with this product.

First define the image index editor represented by the TcxImageIndexProperty class, which is derived from the TcxPropertyEditor family. Override the following inherited methods to provide the proper behavior:

  • AdjustInnerEditProperties – this is a protected method of the TcxPropertyEditor class and is responsible for customizing the editing properties;

  • SetValue specifies the edited property value.

Define a new virtual method GetImages to provide the image list of the bound component, this method can be overridden by TcxImageIndexProperty descendants.

Register the TcxImageIndexProperty editor with the cxRegisterPropertyEditor and cxRegisterEditPropertiesClass procedures. The former procedure creates association between the property editor, the property type and the latter creates association between the property editor and the editor properties.

unit ImageIndexPropertyEditor;
interface
uses cxOI, cxEdit, ImgList, cxImageComboBox, Controls;
type
  TcxImageIndexProperty = class(TcxIntegerProperty)
  public
    procedure AdjustInnerEditProperties(AProperties: TcxCustomEditProperties); override;
    // override GetImages function in descendant to provide another image list
    function GetImages: TCustomImageList; virtual;
    procedure SetValue(const Value: string); override;
  end;
implementation
uses SysUtils, cxVGrid;
resourcestring
  sNoImage = 'no image';
{ TcxImageIndexProperty }
procedure TcxImageIndexProperty.AdjustInnerEditProperties(
  AProperties: TcxCustomEditProperties);
var
  i: Integer;
  AComboBoxItem: TcxImageComboBoxItem;
  AImageComboBoxProperties: TcxImageComboBoxProperties;
  AImages: TCustomImageList;
  procedure AssignImages;
  begin
    if AImages.Height = 16 then
      AImageComboBoxProperties.Images := AImages
    else
      AImageComboBoxProperties.LargeImages := AImages
  end;
  function GetImageList: TCustomImageList;
  begin
    if AImageComboBoxProperties.Images <> nil then
      Result := AImageComboBoxProperties.Images
    else
      Result := AImageComboBoxProperties.LargeImages;
  end;
begin
  AImageComboBoxProperties := AProperties as TcxImageComboBoxProperties;
  AImages := GetImages;
  if AImages <> nil then
  begin
    AssignImages;
    AImageComboBoxProperties.Items.Clear;
    for i:= 0 to GetImageList.Count - 1 do
    begin
      AComboBoxItem := AImageComboBoxProperties.Items.Add as TcxImageComboBoxItem;
      AComboBoxItem.ImageIndex := i;
      AComboBoxItem.Value := i;
      AComboBoxItem.Description := IntToStr(i);
    end;
  end;
  AComboBoxItem := AImageComboBoxProperties.Items.Add as TcxImageComboBoxItem;
  AComboBoxItem.ImageIndex := -1;
  AComboBoxItem.Value := Integer(-1);
  AComboBoxItem.Description := sNoImage;
end;
procedure TcxImageIndexProperty.SetValue(const Value: string);
begin
  if Value = sNoImage then
    SetOrdValue(-1)
  else inherited SetValue(Value);
end;
function TcxImageIndexProperty.GetImages: TCustomImageList;
begin
  Result := nil;
  if GetComponent(0) is TcxCaptionRowProperties then
  // it assumes the image list is bound at design time
     Result := TcxCaptionRowProperties (GetComponent(0)).Row.VerticalGrid.Images;
end;
initialization
cxRegisterPropertyEditor(TypeInfo(TImageIndex), TcxCaptionRowProperties, 'ImageIndex', TcxImageIndexProperty);  cxRegisterEditPropertiesClass(TcxImageIndexProperty, TcxImageComboBoxProperties);
end.

Now there is the main unit where the TcxDBVerticalGrid and TcxRTTIInspector objects are defined. Two rows – the PerformanceAttributes category row and the HP editor row – will be the inspected components in this application.

Just one method, the DBVerticalGridClick, is defined. It binds a row that gets focus to the runtime Object Inspector.

unit RTTIInspectorDemoMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, cxStyles, cxGraphics, cxEdit, cxVGrid, cxOI, cxControls, cxInplaceContainer, cxDBVGrid, DB, ADODB, ImgList, cxClasses, StdCtrls, cxDropDownEdit, cxContainer, cxTextEdit, cxMaskEdit, cxImageComboBox;
type
  TRTTIInspectorForm = class(TForm)
    DBVerticalGrid: TcxDBVerticalGrid;
    RTTIInspector: TcxRTTIInspector;
    DataSource: TDataSource;
    Cars: TADOTable;
    ImageList: TCustomImageList;
    PerformanceAttributes: TcxCategoryRow;
    HP: TcxDBEditorRow;
    procedure DBVerticalGridClick(Sender: TObject);
 private
     { Private declarations }
 public
    { Public declarations }
 end;
var
  RTTIInspectorForm: TRTTIInspectorForm;
implementation
{$R *.dfm}
// using the Object Inspector assign this method at design time to the DBVerticalGrid OnClick event as an event handler
procedure TRTTIInspectorForm.DBVerticalGridClick(Sender: TObject);
begin
  RTTIInspector.InspectedObject := DBVerticalGrid.FocusedRow;
end;
end.

Here is the output for the code listed above: