Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Bob Swart (aka Dr.Bob) - Medical Officer Delphi in a Nutshell
 Under The Hood #12 - Hidden IDE Secrets
See Also: Delphi Papers and Dr.Bob's Examines Columns

Let's take a look behind the scenes of Delphi - under the hood, if you want. The manuals describe the use of the Components array property on Forms, which is a list of all components owned by the component. You can use the Components property to access any of these owned components, such as the controls owned by the Form. The Components property is especially useful if we need to refer the owned components by number rather than name.

As an example, let's change the Caption of each button on a Form to 'Dr.Bob' as follows:

  procedure TForm1.Button1Click(Sender: TObject);
  var
    i: Integer;
  begin
    for i:=0 to Pred(ComponentCount) do
      if Components[I] is TButton then
        TButton(Components[I]).Caption := 'Dr.Bob';
  end;

Application.Components
Now, this is all documented. But what about the same Component array in the Application variable? The following program shows the ClassName and the ComponentName of all components that are childs of the Application variable:

  {$APPTYPE CONSOLE}
  uses
    Forms;
  var
    i: Integer;
  begin
    for i:=0 to Pred(Application.ComponentCount) do
      writeln(Application.Components[i].Name,': ',
              Application.Components[i].ClassName);
  end.
The small program above only contains the THintWindow as childwindow of the Application (I wonder why a HintWindow is created for a CONSOLE application?).

If we execute this code as ButtonClick event from a Form, we get the Form1 (of type TForm1) as additional component.
Note by the way that we can indeed execute "writeln" statements from a regular visual Delphi application, as long as we've selected the "Console application" option. You can have CONSOLE output and a visual Forms application combined in one! (really handy for easy debugging).

Debug Component
Anyway, now that we've seen what our applications can contain, let's put this information in a small "debug" component and show what the Delphi IDE Application contains for interesting sub-components.

  unit comphood;
  interface
  uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

  type
    TDrBob = class(TComponent)
      constructor Create(AOwner: TComponent); override;
    end;

  procedure Register;

  implementation

  constructor TDrBob.Create(AOwner: TComponent);
  var
    i: Integer;
    f: System.Text;
  begin
    inherited Create(AOwner);
    System.Assign(f,'C:\DELPHI.OUT');
    Rewrite(f);
    for i:=0 to Pred(Application.ComponentCount) do
      writeln(f,Application.Components[i].Name+': '+
                Application.Components[i].ClassName);
    System.Close(f)
  end;

  procedure Register;
  begin
    RegisterComponents('Dr.Bob', [TDrBob]);
  end;

  end.
We can install this component like any other component. But once we drop it on a form, the file C:\DELPHI.OUT is generated when the TDrBob component is constructed. The output file C:\DELPHI.OUT, generated by this little component containt the following text:
  : THintWindow
  : TDdeMgr
  : TQRPrinter
  DbEngineErrorDlg: TDbEngineErrorDlg
  MenuBuilder: TMenuBuilder
  : TPopupMenu
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem
  : TMenuItem

Popup, Popup!
Let's see if we can get the Captions of the TMenuItems and also active the TPopupMenu by modifying the TDrBob.Create as follows:

  constructor TDrBob.Create(AOwner: TComponent);
  var
    i: Integer;
    f: System.Text;
  begin
    inherited Create(AOwner);
    System.Assign(f,'C:\DELPHI.OUT');
    Rewrite(f);
    for i:=0 to Pred(Application.ComponentCount) do
    begin
      if (Application.Components[i] IS TPopupMenu) then
        Appliation.Components[i] AS TPopupMenu).Popup(210,210);
      write(f,Application.Components[i].Name+': '+
              Application.Components[i].ClassName);
      if (Application.Components[i] IS TMenuItem) then
        write(f,' {',(Application.Components[i] AS TMenuItem).Caption,'}');
      writeln(f);
    end;
    System.Close(f);
  end;
The output from this little experiment is stunning: we get to see the Form's pop-up menu, and a list of all menu items from this pop-up menu:
  : THintWindow
  : TDdeMgr
  : TQRPrinter
  DbEngineErrorDlg: TDbEngineErrorDlg
  MenuBuilder: TMenuBuilder
  : TPopupMenu
  : TMenuItem {Align To &Grid}
  : TMenuItem {Bring To &Front}
  : TMenuItem {Send To &Back}
  : TMenuItem {Revert to &inherited}
  : TMenuItem {-}
  : TMenuItem {&Align...}
  : TMenuItem {&Size...}
  : TMenuItem {Scal&e...}
  : TMenuItem {Tab &Order...}
  : TMenuItem {&Creation Order...}
  : TMenuItem {Add To &Repository...}
  : TMenuItem {&View as Text}

Customize Popup
Hmm, I wonder if we can Add items to the TPopupMenu component as well? For that, we need to add a event method OnDrBob to the TDrBob component, and connect this method to the OnClick event of a New MenuItem that we add to the first item of the TPopupMenu. Let's see if the following code actually works...


  unit comphood;
  interface
  uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus;

  type
    TDrBob = class(TComponent)
      constructor Create(AOwner: TComponent); override;
    private
      procedure OnDrBob(Sender: TObject);
    end;

  procedure Register;

  implementation

  constructor TDrBob.Create(AOwner: TComponent);
  var
    i: Integer;
    f: System.Text;
    FirstItem, NewItem: TMenuItem;
  begin
    inherited Create(AOwner);
    System.Assign(f,'C:\DELPHI.OUT');
    Rewrite(f);
    for i:=0 to Pred(Application.ComponentCount) do
    begin
      if (Application.Components[i] IS TPopupMenu) then
      begin
        FirstItem := (Application.Components[i] AS TPopupMenu).Items[0];
        NewItem := TMenuItem.Create(FirstItem);
        NewItem.Caption := 'Dr.Bob';
        NewItem.OnClick := OnDrBob;
        FirstItem.Insert(0, NewItem);
        (Application.Components[i] AS TPopupMenu).Popup(210,210);
      end;
      write(f,Application.Components[i].Name+': '+
              Application.Components[i].ClassName);
      if (Application.Components[i] IS TMenuItem) then
        write(f,' {',(Application.Components[i] AS TMenuItem).Caption,'}');
      writeln(f);
    end;
    System.Close(f)
  end;

  procedure TDrBob.OnDrBob(Sender: TObject);
  begin
    ShowMessage('Hello, world!');
  end;

  procedure Register;
  begin
    RegisterComponents('Dr.Bob', [TDrBob]);
  end;

  end.

Yes, it does! We can now actually modify the pop-up menu for Forms!

Previously, Component Editors for Forms were impossible. With a little imagination and this undocumented technique, I showed that they're very much possible!
Note that the Popup Menu is only modified for the Form where the TDrBob component is dropped upon. If we create a new Form, then this one still has the original TPopupMenu (until we drop another instance of TDrBob on it, of course).


This webpage © 1996-2017 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.