OTA - Forms with Published Properties Visible in the Object Inspector

Creating a new TForm descendant that will show new properties in the Object Inspector is not as easy as creating a component with new properties. Since the IDE is intimately involved with a Form at design time there is a bit of overhead that must be done before the IDE can interact with a new TForm descendant.

  First define the new Form as you would any other class, deriving from TCustomForm (or TForm if you want all properties of TForm exposed in the new Form).

type
  TCustomNewDecendantForm = class(TCustomForm)
  private
  FNewProperty: Integer;
  protected
  property NewProperty: Integer read FNewProperty write FNewProperty;
  end;

  TNewDescendantForm = class(TCustomNewDescendantForm)
  published
  property Caption;
  property NewProperty;
  property PixelsPerInch; // PixelsPerInch is necessary at Runtime.
  //... publish any other TCustomForm property that is appropriate here
  end;

  Next the IDE needs to have the new object class registered so it can interact with it. This relies on a mysterious undocumented procedure

procedure RegisterCustomModule(ComponentBaseClass: TComponentClass; CustomModuleClass: TCustomModuleClass);

  Now all we need to do is register our new class, telling the IDE that it is a Custom Module

procedure Register
begin
  RegisterCustomModule(TNewDescendantForm, TCustomModule);
end;

  There is one catch here if needing to support older compilers. Starting with D6 TCustomModule is defined in DesignEdtiors.pas. The easiest way to handle compiler issues is to use the Compilers.inc file included with most of the demos.

uses
 {$IFDEF COMPILER_6_UP}
  DesignIntf, // DsgnIntf renamed to DesignIntf in D6
  DesignEditors, // TCustomModule moved to DesignEditors in D6
  {$ELSE}
  DsgnIntf,
 {$ENDIF}
  ToolsApi;

Ok, all looks good so far. There is a catch though, do you see it? Actually there are two problems.

  • How does the Register procedure get called in the first place?

  • How do we create a new Form of this type?

  To create a new Form you go to the Object Repository (or use a short cut to it) and select New Form. Are you seeing where this is going? Yes, we need to make a Wizard that registers the new Form Module and creates a new item in the Object Repository so the user can create an instance of it.

  If you have not seen the pattern yet you will be happy to know that a lot of the Module Creator and File interface implementation is repetitive so why don't we create some classes that can handle the details and only needs one or two methods overridden to use. You may look through the CreatorUtilities.pas file in the the OTADescendantForm demo for the details.

  The Wizard is the standard template used in the Repository Wizard. Since a most of the methods in this implementation usually need to be overridden keeping a copy of a template is worth the effort. What is notable in Repository Wizard in the OTADescendantForm demo is the implementation of the Execute method:

 procedure TDescendantFormWizard.Execute;
begin
  (BorlandIDEServices as IOTAModuleServices).CreateModule(TNewDescendantCreator.Create);
end;

This is just simply using a Form Creator when the user selects our Repository object, but what is a TNewDescendantCreator object? TNewDescendantCreator is defined in the demo by:

uses
  CreatorUtilites;

interface
  //
  // A New TModuleCreatorFile descendant to make add our DescendantForm unit
  // to the uses clause of the file
  //
  TNewDescendantFile = class(TModuleCreatorFile)
  public
  function GetSource: string; override;
  end;

  //
  // A New TFormCreatorModule descendant to make our new Form class the ancestor
  // Form, and to use our new TModuleCreatorFile class
  //
  TNewDescendantCreator = class(TFormCreatorModule)
  public
  function GetAncestorName: string; override;
  function GetImplFile: TModuleCreatorFileClass; override;
  end;

implementation

{ TNewDescendantCreator }

function TNewDescendantCreator.GetAncestorName: string;
begin
  Result := 'NewDescendantForm'
end;

function TNewDescendantCreator.GetImplFile: TModuleCreatorFileClass;
begin
  // Tell the Creator to use our new DescendantFile object
  Result := TNewDescendantFile
end;

{ TNewDescendantFile }

function TNewDescendantFile.GetSource: string;
begin
  Result := inherited GetSource;
  // Add the DescendantForm unit to the uses clause
  // NOTE: This is dependant on Graphics not being the last uint in the uses clause
  Result := StringReplace(Result, 'Graphics,', 'Graphics, DescendantForm,', [rfIgnoreCase]);
end;

All we had to do was to override the GetAncestorName to have the Creator use our new Form class as the ancestor. The inherited TFormCreatorModule does the rest. Now create a package and add the wizard files to it and intall the package into the IDE.

 

  The resulting code in the IDE looks like this:

unit Unit1;

// This code was generated by the Mustangpeak OTA Wizard
// Demo.
// www.Mustangpeak.net
//
// Don't forget if a Custom Form with an ancestor that
// is not TForm is created the uses clause must include
// the unit that contains the Custom Form source code
// and the path to that source code must be on the IDE's
// path.

interface

uses
 Windows, Messages, SysUtils, Classes, Graphics, DescendantForm,
 Controls, Forms;
type
  TNewDescendantForm1 = class(TNewDescendantForm)
  private
  { Private declarations }
  public { Public declarations }
end;

var
  NewDescendantForm1: TNewDescendantForm1;

implementation

  {$R *.dfm}

end.

And if we look in the Object Inspector we see our new Property!

Custom Form Property in OI

  Creating Forms that can have new properties in the Object Inspector takes a bit if work but with a few reusable classes it can be accomplished in only a few minutes.

  Back to OpenTools API page.

 


mustangpeak.net

  Last Modified on: