Delphi Developers' Handbook Chapter 15: Other Delphi Extensions
Delphi Developers' Handbook Chapter 15: Other Delphi Extensions
Delphi
2005
Workshop
Happy
Birthday,
Delphi Click here for advertising on marcocantu.com
All the techniques we’ve described so far in this part of the book, with very few exceptions, are "official" ways of customizing the Delphi dev
environment (even though some of them aren’t properly documented). In this last section of the chapter, we’ll explore a few totally
techniques. To figure out how these techniques works, we had to do some hacking.
Here’s the basic idea: when you write a property editor, component editor, or DCU-based wizard (but not a DLL-based wizard or a VCS DLL
actually adding classes and code to the Delphi environment itself. Since the Delphi environment is a Delphi application, you can apply all the
programming techniques we’ve discussed to the environment itself. For example, consider what happens if you write the following within on
extensions to the Delphi environment:
The title of the Delphi main window and of the TaskBar icon will display the new string. You’ve basically changed the title of Delphi itself, an
message boxes displayed by the system will use this new title. However, this shouldn’t surprise you, since Borland used Delphi to build Delp
In this section, we’ll use similar techniques to build some useful tools. We’ll first build a tool to explore the structure of Delphi’
components; then we’ll create a Delphi customization tool that will allow a user to turn the Component Palette into a multiline TabControl, to
the font of the Object Inspector, and to add new menu items to some windows. At the end we’ll use a similar technique to call the internal e
handlers for Delphi menu items directly and even change them!
Delphi Exposed
Before we start examining the details of modifying the Delphi environment, we must know some of the implementation details, such as the
the components and forms Borland uses. Fortunately, getting this information is quite simple: our Delphi Exposed Wizard creates a text file
outlines the relationships between the components Delphi uses internally. Of course, we can list only the components in use at a given mom
some of the additional Delphi forms aren’t always open.
The Delphi Exposed Wizard is part of a separate package (not the package for this chapter) stored in the DelphExp directory. The package i
this standard wizard, which defines a simple Execute method showing a separate form:
procedure TDelphiExposedWizard.Execute;
begin
Application.Title := ''Delphi 3 (Exposed)';
DelExForm := TDelExForm.Create (Application);
try
DelExForm.ShowModal;
finally
DelExForm.Free;
end;
end;
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 2 de 13
You’ll notice that, as a side effect, the wizard changes Delphi’s own title! The secondary form, displayed by the wizard, contains a TreeView
component and four buttons that do the following:
The first two buttons fill the tree view with the structure of the Delphi environment, arranging components in either parent/child or owner/o
order. In the first case, the wizard starts by adding the forms stored in the Screen object:
You can see that for every form, the wizard calls the AddChild method, a custom method we’ve added to the form. This method simply lists
controls of the current form, and then recursively lists the controls for every TWinControl descendant:
procedure TDelExForm.AddChild (
Node: TTreeNode; Control: TWinControl);
var
I: Integer;
ChildNode: TTreeNode;
begin
for I := 0 to Control.ControlCount - 1 do
begin
ChildNode := TreeView.Items.AddChild (Node,
Format ('%s (%s)', [
Control.Controls[I].Name,
Control.Controls[I].ClassName]));
if Control.Controls[I] is TWinControl then
AddChild (ChildNode, TWinControl (
Control.Controls[I]));
end;
end;
Figure 15.12 shows the output of this wizard with the list of the Delphi main windows (for a given situation). You’ll notice that this list doesn
the names of the nonvisual components. By the way, in the directory of this project you’ll find the file Parent.txt, which we generated by sa
parent hierarchy to a text file (using the third button).
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 3 de 13
Figure 15.12: The list of Delphi’s own forms, along with all the controls they contain, produced by the Delphi Ex
Wizard.
In contrast, when you click the Ownership button, the wizard begins with the Application object and navigates the object hierarchy scanning
component’s Components array, again using a recursive method:
procedure TDelExForm.AddOwned (
Node: TTreeNode; Component: TComponent);
var
I: Integer;
ChildNode: TTreeNode;
begin
for I := 0 to Component.ComponentCount - 1 do
begin
ChildNode := TreeView.Items.AddChild (Node,
Format ('%s (%s)', [
Component.Components[I].Name,
Component.Components[I].ClassName]));
AddOwned (ChildNode, Component.Components[I]);
end;
end;
The result of this operation is a much bigger tree, which includes (among other things), all of the Delphi main menu items. It also includes m
strange and unnamed objects, used internally by Delphi but not visible in the development environment.
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 4 de 13
Using this information, we can hook into the system again, change it, and thereby make it more flexible and configurable.
What we really want to do is create a single wizard that we can use to customize the Delphi environment in many different ways. We haven
add all the interesting customizations to this wizard, but once you understand the foundations, you’ll be able to extend it as you see fit.
My Custom Delphi Wizard is another simple wizard; you’ll find it in the Comps directory and as part of the Chapter 15 package. When you a
wizard, it simply creates and displays a form, based on a PageControl component:
procedure TMyDelphiWizard.Execute;
begin
MyDelphiForm := TMyDelphiForm.Create (Application);
try
MyDelphiForm.ShowModal;
finally
MyDelphiForm.Free;
end;
end;
The various pages of this form allow the user to customize different areas of the Delphi environment.
If you’ve ever attended a conference presentation on Delphi programming, you’ll know that the presenter can easily set the code editor fon
everyone, including people sitting far in the back, can read it. Unfortunately, there’s no corresponding way to change the Object Inspector f
people in the back of the room won’t be able to see the names and the values of the properties you’re setting.
Solving this problem is actually quite simple, because the Object Inspector form adapts itself very well to larger fonts. As a result, just a few
code will do the trick. By the way, the form defines a variable that refers to the Object Inspector form:
private
Inspector: TForm;
Now when the user presses the Font button of the Inspector page, the wizard simply changes the font (which you can see in Figure 15.13):
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 5 de 13
Figure 15.13: The My Delphi Wizard can allows you to change the font of the Object Inspector.
An even more interesting trick is to toggle the value of the Multiline property of the TabControl component, which hosts Delphi
private
Palette: TTabControl;
Once we’ve retrieved a reference to the TabControl component, using the two lines of code we’ve just shown, we can add a check box to th
to perform this operation. The code is quite simple:
The only problem with this code is that part of the palette will be hidden from view when it changes from single-line to multiline mode. The
size for the palette depends on the number of lines it displays, the width of your screen, and the font the palette uses. It’s possible to write
all this, but we decided to keep the example simple, and let the user resize the Component palette using an UpDown component connected
only edit box:
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 6 de 13
end;
Simply setting the height of the Component palette isn’t enough, though. The Delphi main Window has a fixed size, automatically computed
the size of the components it hosts. For this reason, after we change the height of the palette, we just ask Delphi’s main window
(Application.MainForm) to resize itself. Delphi will ignore the size we ask for and simply reapply its own sizing rules. This does the trick.
In Figure 15.14 you can see the multiline Component palette in Delphi, displayed at the proper size, and the wizard page that relates to its
There’s also a Font button for the palette, but this doesn’t work very well with bigger fonts. Finally, there’s another button that refreshes the
Component palette popup menu.
Figure 15.14: Delphi’s Component palette is now multiline! This is very handy when you have more palette pag
your screen can host.
The last addition we’ll perform with this wizard is to change the Component palette’s popup menu. Our wizard adds new items to this menu
user select a page of the palette. The code, executed when the wizard starts and when the user presses the Refresh button, simply remove
menu items we may have added before, and then adds a new menu item for each tab of the Palette TabControl component:
Once you’ve executed this code, the local menu of the palette will resemble Figure 15.15. You’ll notice that for every menu item we create,
associate a common OnClick event handler. The event handler code changes the current tab to the page having the same text as the select
item (the Sender):
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 7 de 13
Palette.TabIndex := Palette.Tabs.IndexOf (
(Sender as TMenuItem).Caption);
Palette.OnChange (self);
end;
Figure 15.15: The extended popup menu of the Component palette allows a user to move to a new page faster.
After we’ve set the index of the new page, the wizard must explicitly call the OnChange event handler (which doesn’t execute automatically
Delphi to update the current palette page properly.
The final improvement we want to make is that we’d like to set the multiline flag and the palette height once, and then let the wizard restor
values when we run Delphi in the future. In other words, we want to make these settings persistent. The best solution is to store the new v
the Windows Registry, using the standard Delphi entry. Here’s the updated version of the wizard’s Execute method, showing the code we
the Registry values as you exit from the form:
procedure TMyDelphiWizard.Execute;
var
Reg: TRegistry;
Palette: TTabControl;
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 8 de 13
begin
MyDelphiForm := TMyDelphiForm.Create (Application);
try
MyDelphiForm.ShowModal;
finally
MyDelphiForm.Free;
end;
// saves the status in the registry
Reg := TRegistry.Create;
Reg.OpenKey (ToolServices.GetBaseRegistryKey +
'\MyDelphiWizard', True);
Palette := Application.MainForm.
FindComponent ('TabControl') as TTabControl;
Reg.WriteBool ('Multiline', Palette.Multiline);
Reg.WriteInteger ('PaletteHeight', Palette.Height);
Reg.Free;
end;
As we mentioned above, this method uses Delphi’s own Registry key (retrieved by calling the ToolServices global object’s GetBaseRegistryK
method), and then either creates or updates a custom key for the wizard, based on the Boolean OpenKey parameter. The values we add or
relate only to the multiline style and the height of the Component palette, but you can easily modify the method to store other options for t
and other settings.
If it makes sense to update these values after invoking the wizard, then it also makes sense to update the current setting without any user
intervention. The only method in the wizard’s unit called when Delphi loads the unit is the Register procedure. For this reason, we
procedure as follows:
procedure Register;
var
Palette: TTabControl;
Reg: TRegistry;
begin
RegisterLibraryExpert(TMyDelphiWizard.Create);
// load the status from the registry
Reg := TRegistry.Create;
Reg.OpenKey (ToolServices.GetBaseRegistryKey +
'\MyDelphiWizard', True);
Palette := Application.MainForm.
FindComponent ('TabControl') as TTabControl;
if Reg.ValueExists ('Multiline') then
Palette.Multiline := Reg.ReadBool ('Multiline');
if Reg.ValueExists ('PaletteHeight') then
Palette.Height := Reg.ReadInteger ('PaletteHeight');
// force resize
SendMessage (Application.MainForm.Handle,
wm_Size, 0, 0);
Reg.Free;
end;
The last wizard we’ll build in this chapter is a bit strange. We’ve called it the Rebuild Wizard, and it started out as a tool built by Marco to m
compilation of his Mastering Delphi 3 book examples. Although it might not be terribly useful for other purposes, it demonstrates a few low
interesting tricks, as well as a couple of generic algorithms, so it should be worth studying the code.
The purpose of this wizard is simple: we want to list all the Delphi project files in a given directory and its subdirectories, and then be able t
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 9 de 13
Build All the Projects command that will build each project, one by one. Creating the list of Delphi project files isn’t too difficult. The wizard
form displays an edit box to choose the initial directory, and a list box that will display the names of the projects we find. By the way, when
double-click on the edit box, the wizard calls the SelectDirectory VCL global procedure to let you choose a directory graphically:
Once we’ve set the starting directory, the user can press the List button to fill in the list box with the names of the available project files. Th
event handler of this button uses a hidden FileListBox component to list all the DPR files in the directory. Then we use the same component
the subdirectories and repeat the process for each of them. Here’s the complete code:
procedure TRebWizForm.ExamineDir;
var
FileList: TStrings;
I: Integer;
CurrDir: string;
begin
// examining .dpr files
FileListBox1.Mask := '*.dpr';
FileListBox1.FileType := [ftNormal];
FileList := TStringList.Create;
try
FileList.Assign(FileListBox1.Items);
// for each file, add its path to the list
for I := 0 to FileList.Count - 1 do
begin
ListBoxFiles.Items.Add (FileListbox1.Directory +
'\' + FileList[I]);
end;
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 10 de 13
FileListbox1.Directory := CurrDir;
finally
FileList.Free;
end;
end;
Once you have the project file list (shown in Figure 15.16) you can easily open any project in the Delphi environment:
Figure 15.16: A list of projects selected in the Rebuild Wizard. You can compile them all with a single mouse cli
At this point, how do we compile the projects? We want to call the Rebuild All command, but there isn’t a shortcut key for it, so we can
the keystroke as we did for the Run menu command of the AddIn component editor built in Chapter 13. There are two alternatives: you can
for the necessary menu item and activate its OnClick event handler, or you can locate the appropriate event handler directly. We
method soon, but for now let’s look at the second and more complex one.
All the event handlers for a form are published methods, and so Delphi must export them as part of the class’s RTTI information. Therefore
use the TObject class’s MethodAddress method to return the address of the appropriate event handler. But how do you call this method onc
have its address? You must pass the method address and the associated object to a TMethod record, and then cast it and call it as you wou
other event handler. Sound complex? It is, but there isn’t much code:
procedure TRebWizForm.DoCompile;
var
ObjDelphi: TWinControl;
Meth: TMethod;
Evt: TNotifyEvent;
P: Pointer;
begin
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 11 de 13
ObjDelphi := Application.MainForm;
P := ObjDelphi.MethodAddress ('ProjectBuild');
Meth.Code := P;
Meth.Data := ObjDelphi;
Evt := TNotifyEvent (Meth);
Evt (ObjDelphi);
end;
To write this code we simply had to know the name of the event handler, something we found by inspecting the Delphi system (although th
Exposed tool we’ve shown you doesn’t deliver this particular piece of information). Now you can compile a specific project or all of them:
If you have the Delphi environment option Show Compiler Progress active, you’ll be asked to press the message box’s OK button aft
compiling every project, but you’ll also be able to keep track of any errors easily. If you disable this option, the wizard will compile a
projects without any manual intervention.
Now there are two remaining issues to resolve. The first is that the wizard should be a modeless form, because this allows us to determine
create and destroy the form. The other issue is that it would be nice to open this wizard when the user selects the Build All command on the
menu!
All the wizards we’ve built up to now have displayed modal forms. In this case, we need to display a modeless form, because we want to be
open and work on several projects, and keep the wizard’s form open. The Execute method creates the form only if it we haven
procedure TRebuildWiz.Execute;
begin
// the actual code
if not Assigned (RebWizForm) then
RebWizForm := TRebWizForm.Create (nil);
RebWizForm.Show;
end;
We’ll destroy the form later when the user closes it:
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 12 de 13
However, the user might happen to close Delphi or remove a package without closing the wizard first. In either case we can solve the probl
adding a finalization section to the wizard’s unit:
initialization
RebWizForm := nil;
finalization
if Assigned (RebWizForm) then
RebWizForm.Free;
The final step is to alter the Rebuild All menu item’s event handler. The biggest problem here is to determine when we should perform this
Using the wizard unit’s initialization and finalization sections might work, as well as using the Register procedure for the startup code as we
last wizard. There’s a third alternative, which is probably more sound from an OOP perspective: add a constructor and a destructor to the w
class:
type
TRebuildWiz = class (TIExpert)
private
OriginalBuildClick: TNotifyEvent;
BuildMenu: TMenuItem;
public
constructor Create;
destructor Destroy; override;
procedure BuildClick (Sender: TObject);
// standard methods
function GetStyle: TExpertStyle; override;
function GetName: string; override;
...
You’ll notice that we haven’t marked the constructor with the override keyword. This is because the TIExpert class’s constructor isn
However, this isn’t a problem, because the only instance of the wizard object that will exist is the one we create in the Register proc
The constructor simply searches for the appropriate menu item component, saves the original event handler, and then installs a new event
while the destructor restores the original event handler. (Having a Delphi menu with an event handler that refers to a method in a destroye
would result in a very serious error!). Here’s the code:
constructor TRebuildWiz.Create;
begin
inherited;
// change the event handler of the Build menu item
BuildMenu := Application.MainForm.
FindComponent ('ProjectBuildItem') as TMenuItem;
OriginalBuildClick := BuildMenu.OnClick;
BuildMenu.OnClick := BuildClick;
end;
destructor TRebuildWiz.Destroy;
begin
// restore the event handler
BuildMenu.OnClick := OriginalBuildClick;
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005
DDH: Chapter 15 Página 13 de 13
inherited;
end;
Now let’s look at the code of the new event handler. It simply asks the user whether to execute the wizard or the standard Rebuild All func
the user chooses the wizard, they have the option of rebuilding all the selected projects automatically:
Of course there’s no guarantee that this code will work in a new version of Delphi (internal event handler names may change dramatically),
also the case for the other wizards in the final portion of this chapter. However, it’s worth noticing that we’ve hooked into the Delphi menu
much simpler manner than by using the ToolsAPI menu item interfaces. The only significant drawback is that this code is available only for D
add-ins, and not for external DLLs (although it does work from within a package, which is not very different from a DLL).
[Chapter 15 Index]
© Copyright Marco Cantù, Wintech Italia Srl 1995-2005, All rights reserved
file://C:\Temp\Marco%20Cantu\hackinkg%20delphi%20IDE.htm 12-04-2005