How to write Delphi Wizards
TGenericExpert: Hello, World!
If we want to derive our own Wizard, say TGenericExpert, we have to derive it from the abstract base class TIExpert, which has seven or nine abstract member functions (GetStyle, GetName, GetComment, GetGlyph, GetState, GetIDString and GetMenuText, and for the 32-bits versions of Delphi also GetAuthor and GetPage) and one member procedure Execute. Since TIExpert is an abstract base class, we need to override every function we need for any particular Wizard.
unit Generic;
interface
uses
Windows, ExptIntf;
Type
TGenericExpert = class(TIExpert)
public
{ Expert Style }
function GetStyle: TExpertStyle; override;
{ Expert Strings }
function GetIDString: string; override;
function GetName: string; override;
function GetAuthor: string; override;
function GetMenuText: string; override;
function GetState: TExpertState; override;
function GetGlyph: HICON; override;
function GetComment: string; override;
function GetPage: string; override;
{ Expert Action }
procedure Execute; override;
end;
procedure Register;
implementation
uses
Dialogs;
{ The implementation details of TGenericExpert will follow in the text }
procedure Register;
begin
RegisterLibraryExpert(TGenericExpert.Create)
end {Register};
end.
Let's have a closer look at our generic Wizard from this listing.
Since TIExpert is an abstract base class, we need to override every function we need for our TGenericExpert. First of all, we need to specify the style of the Wizard with the GetStyle method that can return one of three (or four) possible values: esStandard to tell the IDE to treat the interface to this Wizard as a menu item on the Help menu, esForm to tell the IDE to treat this Wizard interface in a fashion similar to form templates, or esProject to tell the IDE to treat this interface in a fashion similar to project templates. For 32-bits Delphi Wizards only, we can also return esAddIn here, to indicate that this is a special klind of Wizard that handles all its own interfaceing to the IDE through the TIToolServices interface. For our TGenericExpert, a Standard type Wizard that only shows a MessageDlg to say hello to the world, we can use the esStandard style.
function TGenericExpert.GetStyle: TExpertStyle;
begin
Result := esStandard
end {GetStyle};
The GetIDString should be unique to all Wizards that could be installed.
By convention, the format of the string is: CompanyName.ExpertFunction, like Borland.Expert or DrBob.GenericExpert.
function TGenericExpert.GetIDString: String;
begin
Result := 'DrBob.TGenericExpert'
end {GetIDString};
After we've set the style of the Wizard, all we need to do is fill the other options accordingly. The GetName must return a unique descriptive name identifying this Wizard, like 'Generic Wizard'.
function TGenericExpert.GetName: String;
begin
Result := 'Generic Wizard'
end {GetName};
If the style is esForm or esProject, then - for 32-bits versions of Delphi only - we need to return a valid name for the Author. In this case, the style is esStandard, so we can return an empty string instead. For an esForm or esProject style Wizard the name would be displayed in the Object Repository of the 32-bits versions of Delphi.
{$IFDEF WIN32}
function TGenericExpert.GetAuthor: String;
begin
Result := 'Bob Swart (aka Dr.Bob)' { although not needed for esStandard }
end {GetAuthor};
{$ENDIF}
If style is esForm or esProject then GetGlyph should return a handle to a bitmap (for Delphi 1) or icon (for Delphi 2.0x and 3) to be displayed in the form or project list boxes or dialogs. This bitmap should have a size of 60x40 pixels in 16 colours. The icon should be 32x32 in 16 colours. Again, since the style is just esStandard for our TGenericExpert, we can return 0 here. We can even combine the 16- and 32-bit version of GetGlyph here (0 is a valid value to indicate that an icon or bitmap is empty). Note that if we return a 0 when a bitmap or icon is needed, Delphi will use the default image.
{$IFDEF WIN32}
function TGenericExpert.GetGlyph: HICON;
{$ELSE}
function TGenericExpert.GetGlyph: HBITMAP;
{$ENDIF}
begin
Result := 0 { not needed for esStandard }
end {GetGlyph};
If style is esForm or esProject then GetComment should return a 1 or 2 line sentence describing the function of this Wizard. Since the style is esStandard, we can return an empty string.
function TGenericExpert.GetComment: String;
begin
Result := '' { not needed for esStandard }
end {GetComment};
If style is esForm or esProject then - only for 32-bits versions of Delphi - using GetPage we can specify the name of the page in the Object Repository where to place our Wizard. If we don't specify a name here, then the Wizard just gets added to the Default Form or Project page. Since we're writing an esStandard Expert, we don't need to supply a page name, so we can return an empty string again.
{$IFDEF WIN32}
function TGenericExpert.GetPage: String;
begin
Result := '' { not needed for esStandard }
end {GetPage};
{$ENDIF}
If style is esStandard then GetMenuText should return the actual text to display for the menu item, like 'Generic Wizard'. Since this function is called each time the parent menu is pulled-down, it is even possible to provide context sensitive text.
function TGenericExpert.GetMenuText: String;
begin
Result := '&Generic Wizard...'
end {GetMenuText};
If the style is esStandard then GetState returning esChecked will cause the menu to display a checkmark. This function is called each time the Wizard is shown in a menu or listbox in order to determine how it should be displayed. We just leave it esEnabled for now.
function TGenericExpert.GetState: TExpertState;
begin
Result := [esEnabled]
end {GetState};
Finally, the Execute method is called whenever this Wizard is invoked via the menu, form gallery dialog, or project gallery dialog. Note that Execute is never called for an esAddIn style Wizard (this kind of Wizard will handle all its own interfacing to the IDE through the upcoming TIToolServices interface). The style will determine how the Wizard was invoked. In this case, we just call a MessageDlg in the Execute method to indicate that the Wizard is actually alive.
procedure TGenericExpert.Execute;
begin
MessageDlg('Hello Nashville!', mtInformation, [mbOk], 0)
end {Execute};
To install our first Wizard, all we need to do is act like it's a new component: For Delphi 1.0, pick Options | Install, for Delphi 2.0x and 3 select Component | Install, and add it to the list of installed components.
Delphi 1 and 2 simply add the Wizard the the Component Library, but Delphi 3 needs to add it to a package - the DCLUSR30 package by default:
After we click onthe OK-button to add the generic unit with our GenericExpert to the DCLUSR30 package, we need to confirm that Delphi needs to rebuild the package:
After the package is rebuilt and installed into the Delphi 3 IDE again, we can inspect the Package Editor and see that the generic unit is now part of it. This simple example already illustrates that packages are not limited to components, but can contain Wizards as well.
When Delphi is done with compiling and linking COMPLIB.DCL or DCLUSR30.DPL, you can find our first new Wizard in the Help menu:
Just select the "Generic Wizard" and it will show the world that it's alive:
As we can see, only the Execute method contains any significant code, and this will remain so for all Wizards to come. In order to avoid that we have to print a long listing in this paper for an Wizard where only one method is relevant, I'll propose the following technique: let's use a table to define the nine methods, and only specify the Execute method in detail. Our TGenericExpert would then become the following:
| GetStyle: | esStandard |
| GetIDString: | DrBob.TGenericExpert |
| GetName: | Generic Wizard |
| GetAuthor (win32): | Bob Swart (aka Dr.Bob) |
| GetMenuText: | &Generic Wizard... |
| GetState: | [esEnabled] |
| GetGlyph: | 0 |
| GetPage (win32): | |
| GetComment: | |
With only the Execute method outlined in detail (see previous listing). We will use this notation in the rest of this session.