Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
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.
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).