Creating a Web Search Wizard using the OpenTools API.

By: Corbin Dunn

Abstract: How to create a wizard the remembers its position and state in the desktop speedsettings.

Creating a Web Search Wizard for Delphi
By Corbin Dunn
Delphi Developer Support

screen shot of Search ExpertAllen Bauer has written three articles for the OpenTools API with the most recent giving us some ideas on how to create a dockable IDE form. One thing that he didn't go into was how to get an expert or wizard to remember its position and state. This article goes through how I created a Web Search Expert for Delphi 5 that remembers its current position and state in the Desktop Speedsetting.

First of all, download the complete project so we are on the same playing field. I strongly recommend reading Allen Bauer's article to see how I started the project, but this isn't necessary. The key in his article is how to derive from an internal IDE form by adding DsnIDE50.dcp to the requires list of the package. After you have downloaded the source, open up the package SearchWiz.dpk.

Now, take a look at the unit SearchWizMnu. This is your normal registration unit that adds a menu item named "Web search..." to the Delphi IDE's Help menu. One of the new things to notice is in the Register procedure I call RegisterFieldAddress passing two parameters: a string that is the SearchWizForm's ini file section (which is 'SearchWizard' in this case), and the actual address of the SearchWizForm variable:

procedure Register;
begin
  if Assigned(RegisterFieldAddress) then
    RegisterFieldAddress(CIniSection, @SearchWizForm);
  ...
end;
Whenever a desktop is loaded, the IDE checks to see if the SearchWizForm is to be created and shown. To see how this is done, open up a .dst file. These files contain your desktop speedsettings and are located in your Delphi5\bin directory. If you don't have any, then save your desktop as something. As an example, I have a speed setting named Corbin's, so I opened up Corbin's.dst. Looking into it, I see the ini file section for my search expert (you won't see this until you have installed the Search Wizard):
[SearchWizard]
Create=1
Visible=1
State=0
Left=260
Top=116
Width=213
Height=481
MaxLeft=-1
MaxTop=-1
ClientWidth=205
ClientHeight=457
TBDockHeight=481
LRDockWidth=140
Dockable=1
Last Query=Corbin's treehouse
Last Search Engine=Borland Community
Use Same Window=1
WebBrowser Showing=1
Whenever I start Delphi it sees Create=1 in the ini section of my SearchWizard and automatically creates an instance of the Wizard in the SearchWizForm variable (if it is nil - this way it doesn't create multiple instances of the same form). But how does Delphi know what class type to create? Well, that is what the next line is for:
  RegisterDesktopFormClass(TSearchWizForm, CIniSection, CIniSection);
  RegisterPackageWizard(TSearchWizard.Create);
The call to RegisterDesktopFormClass set's TSearchWizForm as the class type to create when it reads the CIniSection and determines it should create it. RegisterPackageWizard is simply used to register my Wizard with the IDE. Finally, notice how in the finalization section of the unit I call UnRegisterFieldAddress and free my SearchWizForm variable.
finalization
  if Assigned(UnRegisterFieldAddress) then
    UnRegisterFieldAddress(@SearchWizForm);
    
  if SearchWizForm <> nil then
  begin
    SearchWizForm.Free;
    SearchWizForm := nil;
  end;
If we don't do this, then you may get access violations and make the IDE unstable. All these functions are in DeskUtil.pas, which is inside of the IDE. If you want to see the stub for this file, download Allen's example.

Next, we want to open up the main form unit, SearchWizFrm.pas, but it uses some internal IDE forms that I got from Allen's example, and if we open up SearchWizFrm.pas it will complain about not finding the base classes. The trick here is to open up DeskForm.pas, then DockForm.pas, and finally SearchWizFrm.pas. To get my Search Wizard to remember its position and data, I overrode LoadWindowState and SaveWindowState (both of which are in TDesktopForm). In SaveWindowState we are passed a TMemIniFile from which I write my settings to. By first calling inherited SaveWindowState, TDesktopForm saves the current position and visible properties.

procedure TSearchWizForm.SaveWindowState(Desktop: TMemIniFile;
  isProject: Boolean);
var
  I: Integer;
begin
  inherited SaveWindowState(Desktop, IsProject);
  if SaveStateNecessary and (Desktop <> nil) then
  begin
    Desktop.WriteString(CIniSection, CIniLastSearchQuery, cmbxQuery.Text);
    with cmbxEngines do
      Desktop.WriteString(CIniSection, CIniLastSearchEngine, Items[ItemIndex]);
    Desktop.EraseSection(CIniSavedQueries);
    if IsProject then
      for I := 0 to cmbxQuery.Items.Count - 1 do
        Desktop.WriteString(CIniSavedQueries, IntToStr(I), cmbxQuery.Items[I]);
    Desktop.WriteBool(CIniSection, CIniUseSameWindow, FUseSameWindow);
    Desktop.WriteBool(CIniSection, CIniWebBrowserShowing, FWebBrowserShowing);
  end;
end;
Notice the variable IsProject that tells if this is a project's desktop settings (.dsk file) that is being saved. Finally, LoadWindowState does the reverse, loading in all my custom information (such as saved search engines and queries):
procedure TSearchWizForm.LoadWindowState(Desktop: TMemIniFile);
var
  TempList: TStringList;
  I: Integer;
begin
  inherited LoadWindowState(Desktop);

  if Desktop <> nil then
  begin
    cmbxQuery.Text := Desktop.ReadString(CIniSection, CIniLastSearchQuery, '');
    if FSearchEngineList.IndexOfName(Desktop.ReadString(CIniSection, 
                               CIniLastSearchEngine, '')) >= 0 then
      cmbxEngines.ItemIndex :=
        FSearchEngineList.IndexOfName(Desktop.ReadString(CIniSection, 
          CIniLastSearchEngine, ''));
  ...
end;
After all this code was in place, one of the biggest problems that I had was getting my Search Wizard to show up when it was left open from a previous desktop. I could see my IniSection in the a project's .dsk file, but it wasn't getting shown (.dsk files are your project's current desktop) This is because I have a custom SpeedSetting that I use, and the speed setting was being set before my Search Wizard ever existed. So, after hitting "View->Desktops->Save Desktop" and saving my desktop, the Search Wizard would open when I started Delphi (or switch to that desktop).

The next thing that you will want to do is install this Wizard into the IDE. It should be as simple as clicking "Install Package" in the Package Editor window. You will notice that the usual Project->Compile (F9) option will not work because a lot of the internal code is running in the IDE. This is normal, and also makes debugging really difficult. The easiest way to do it is to place ShowMessage calls inside of function calls to see where your program is at. Or, you could log things to a file.

Once again, feel free to download the source code for this project. Open up SearchWiz.dpk and click "Install Package". Then, select "Help->Web Search..." and save your desktop if you want it to remember the wizard being open!

If you find any problems with the source code, please let me know! I also wouldn't mind getting any updates you make to enhance the Wizard...


Server Response from: ETNASC04