Opening Doors: Getting Inside the IDE

By: Allen Bauer

Abstract: Allen Bauer, architect of the Open Tools API for Delphi and C++ Builder, explains docking in the IDE in his third article on OTA

Opening Doors

Getting inside the IDE
by Allen Bauer, Staff Engineer/Delphi&C++Builder R&D Manager

The recap...

OK, OK.. Before you say it... Yes I know it has been a while since my last article. I've also received several e-mails asking when the next article will be available. If you have been following the flurry of Borland announcements and press-releases, you're undoubtedly aware of several key events. Well needless to say, interesting things are happening here at Borland. The Kylix project is alive and kicking and also absorbing a large portion of my time (err.... almost all of my time). Allow me to recap my previous articles.

My first article was an introduction to the column; an explanation of the philosophy and design goals of the Open Tools API. The second article showed how to create a simple IDE Add-in using the OTA. Well, since I was unable to present the articles I had intended for January and February, I've decided to leap ahead and go straight to showing how to create a form that will be able to dock with the rest of the IDE.

Mining for Gold

First of all, since this information is based on some of the true internal pieces of the IDE, it is subject to change in future releases. In order to properly work within the IDE as a "first-class" dockable form, one must use one of the different ancestor dockable forms that the IDE itself uses. The Delphi IDE is written using Delphi itself, so this process is, in theory, easy. In past releases of Delphi (or C++Builder), these forms were buried within the Delphi32.exe (or BCB.exe) or the CorIdexx.bpl package. Technically, starting with Delphi 4/C++Builder 4, this was possible, however not easily because there was no .dcp file shipped with the product which allowed one to link with those internal forms. Starting with Delphi/C++Builder 5, those forms have now moved to a new hybrid design-time/IDE package, DsnIdexx.bpl. We definitely ship the .dcp file that corresponds to this package.

Well, what good does just having the .dcp file do? By itself, not much. However, if you happen to know with what unit to link and what the declarations for that unit are, then the fun can start. So in order to get the ball rolling, hop over to CodeCentral and grab a little zip file I put there that has the files you'll need. It contains the stubbed out interfaces and .dfm files for the files used in both docking and desktop save/restore logic. These files are in raw form (i.e.. I haven't changed the interfaces) and only have the implementation stubbed out because these files are only for information and for use while designing your IDE Add-in. (Oh all right... I did leave a few gems in there for you to explore... remember, this is actual code used within the IDE so I cannot guarantee the static nature of those files from release to release, even point releases). You'll be compiling your Add-in using packages and specifically using DsnIdexx.dcp where the real implementation is. These files simply fool the IDE into allowing you to use its Visual Form Inheritance feature.

Another thing to point out is that this is done without the use of the cleanly sanitized interface based OTA. This is bypassing any inherent safe-guards that the OTA provides. Once a dockable form is active, the functionality that form provides will probably continue use the formal OTA interfaces. While, property or component editors can certainly create forms that can be docked, I would not recommend doing this simply because of the ephemeral nature of property/component editors.

OK. Now that I've dispensed with the disclaimers and the slight overview, lets dive in and make some noise!

Sittin' by the dock of the bay...

To get started, open the DummyDockProj.dpr (you did get the files I mentioned above from CodeCentral, didn't you?) in Delphi 5. This project should be included in the same project group as the project on which you're working. Create your working project by right-clicking on the project group and selecting "Add New Project..." Next select the Package item in the dialog.

When you want to create a descendent form from, say TDockableForm, activate the DummyDockProj, select the File|New... dialog, then select the DummyDockProj tab, and finally select DockableForm. This will create a new form unit that inherits from TDockableForm only in the DummyDockProj. This is not where you want it, so save and name this new unit and form to, say, TestDock.pas and TIDETestDockForm, respectively. activate the package project you just added to the project group above and select Project|Add to Project... Browse and select the unit. You can remove now the unit from the DummyDockProj project if you wish. Select the package manager for the package and right-click on the Requires tree node. Select Add.., browse for and select the DsnIDE50.dcp file (should be in Program FilesBorlandDelphi5Lib). This tells the compiler where to get the real ancestor form units.

What all this amounts to is that you're taking advantage of how the IDE finds the ancestor form when instantiating a descendant form. It first looks in the active project, and if it isn't in there, then it searches all the currently open projects. So, now you have a form that descends from an internal IDE form! You can start adding controls to this form just as you would any other form!

Itchin' to see it work!

By now you are ready to see what this all means, so let's do it! On the package project, select the options dialog. Now set it to Design time only and enter a description of your package, press OK. In order to get your form to show, it needs to be instantiated at the right time. The standard Delphi design-time Register1 procedure is the ticket. Add the following to your Form unit:

procedure Register;

implementation {This line should already be there}

procedure Register;
begin
if IDETestDockForm = nil then
IDETestDockForm :=
TIDETestDockForm.Create(Application);
IDETestDockForm.Show;
end;

1I have heard many different speculations as to the reason why the Register procedure is case-sensitive. Reasons range from compatibility with C++Builder to just a bug. The actual reason is that with Win32, all exports from a DLL are case-sensitive. When Delphi loads a design-time package it looks in a special compiler generated resource in the package that describes its contents. This resource lists the unit names within the package. The IDE then scans through this list creating a string containing the unit name and the Register procedure then does a GetProcAddress call on that string. If it locates that entry-point it is called. Sure, we could try all possible combinations, but that would be 256 combinations checked for each unit in that package!

Now we need to make sure that the form is destroyed when the package is unloaded (like when we rebuild it, is manually removed/unloaded from the Install Packages dialog, or the IDE is shutting down). Add this to the end of the unit:

initialization
finalization
IDETestDockForm.Free;
end.

As mentioned in my previous article, this is where the power of Delphi and packages shines. Press the compile button on the package manager window to make sure it compiles. If all goes well, there should be no errors. Now, for the real test; press the installbutton. As long as the IDE didn't explode on you because of a code error, you should now see a blank form where the design form is. This form is now live and dockable. Try it. Drag the form around to any of the various dock sites available within the IDE. If you are one of those daring souls out there that dabble a little with components and property editors, the Open Tools API, or generally like to browse around see what's inside the IDE, are probably already thinking ofthe things you could do with this! Well, let's try something else..

Becoming a First Class Citizen

Suppose you wanted a form that looks and acts just like the project manager or the package manager with respect to the toolbar and showing/hiding the text labels on the buttons. Luckily, that is already available. Instead of descending from TDockableForm, try descending from TDockableToolbarForm. This will give you a form with a toolbar, splitter and all the logic to resize any toolbuttons on the toolbar and showing/hiding the text labels. The behavior should be identical to the project manager and the package managers. Also note that these forms are now first-class IDE citizens! As far as the IDE is concerned, they are just another built-in IDE form.

Take a few moments and look over the interface sections on the units in the DummyDockProj. You'll find such gems as desktop saving/restoring, access to controlling the copy/copy/paste menus and receiving their actions, and so on. Some of these things I'll try and cover in future articles and some things I'll probably just ignore for now... (remember these are internal IDE units and they're rather raw, so many items are not for general consumption.. you are, however, free to experiment and poke around little).

And That Concludes Our Show...

So far I've shown you how to use "mock-up" units to fool the IDE into letting you inherit from forms to which you don't have the full source. I showed how to use these units/forms to create and install into the IDE a first-class dockable form. When we revisit this topic again, I'll try and cover such items as desktop saving/restoring, becoming a docking host, and having the IDE instantiate your singleton2 form from the desktop file.

2A singleton form is a form whereby one and only one instance may exist. In the IDE the Project Manager, Object Inspector, Breakpoints View are all examples of singleton forms.

Where to now?

I'm sure I've left you with probably more questions than answers by this point, but that is what a good cliffhanger is all about! Also, I'll probably jump back to my planned articles for the next installment and cover one of the newest areas of the Open Tools API, the dynamic editor keybinding interfaces. In preparation, you should download the wizard and components I wrote for my presentation at the 1999 BorCon. Look them over and get a feel for what they do and how all the keybinding stuff fits together.

Another future article will cover some of the debugger APIs. These allow you gain access to a lot of the internals of IDE's integrated debugger. Things like process control, expression evaluation, breakpoints, and module lists. Virtually all the IDE's built-in debugger views are implemented on very similar interfaces as the ones exposed by the OTA.

That's all for this installment, but be sure to visit Borland's community site often for news, articles, patches and more. Also, be sure to use the threaded comments and post your thoughts and suggestions. I will endeavor to read them all and respond where appropriate. However, I usually will not answer direct e-mail.


Server Response from: ETNASC02