Today I wanted to do something I did before: Add a drop file handler to an edit control in a dialog of the Delphi IDE. The dialog in question is the the Project Options dialog.
I thought it would be pretty simple to do that:
- Get the dialog instance from Screen.Forms list.
- Get the edit control using the form’s FindComponent method
- Attach a drop file handler to it.
Pretty standard for somebody who has written IDE plugins for Delphi before and is somewhat acquainted with its internals. Little did I know how difficult it would become.
First, the Project Options dialog is created dynamically (many other dialogs are created at program start). So, how do I access it?
It turns out that the global Screen object has got a useful event for this: Screen.OnActiveFormChange which is called every time the active form in the IDE changes. So my plugin could just assign an event handler to it and wait for the Project Options form to become the active one. This works, but there are two pitfalls:
- The IDE internally uses this event.
- How do I know which form is the one I am looking for?
To solve the first I have to hook it and call the original event handler.
To solve the second, I had to enhance my Delphi IDE Explorer to dynamically update its display. It also hooks Screen.OnActiveFormChange for this. This gave me the name and class of the Project Options dialog:
DelphiProjectOptionsDialog: TDelphiProjectOptionsDialog
So, now I had the dialog instance and needed the edit control for the debugging host application. Again, using the IDE explorer, I found its name and class name:
SourcePathInput: THistoryPropComboBox
So, it’s just a matter of calling FindComponent, check the class type to make sure it is the correct control and be done with it, right?
Not so, the Dialog also contains some frames that own their controls rather than the form. So I had to traverse the control hierarchy in order to find the control. It looks like this:
DelphiProjectOptionsDialog: TDelphiProjectOptionsDialog [3] Panel2: TPanel [1] PropertySheetControl1: TPropertySheetControl [0] : TDebuggerLocalPage [2] Panel1: TPanel [0] HostAppGroupBox: TGroupBox [0] HostAppInput: THistoryPropComboBox [2] CDWGroupBox: TGroupBox [0] CWDInput: THistoryPropComboBox [3] SourcePathGroupBox: TGroupBox [0] SourcePathInput: THistoryPropComboBox [2] : TDebuggerSymbolTablePage [1] gbSymbolTables: TGroupBox [1] ddDebugSymbolSearchPath: THistoryPropComboBox [4] cbLoadAllSymbols: TCheckBox [3] Panel1: TPanel [0] ListViewPanel: TPanel [0] SymbolTablesListView: THintListView
The numbers in front of the controls are its index in the parent’s Controls array.
The last challenge was the control’s class: THistoryPropComboBox. Whatever it is, it is not a TCombobBox but at least it derives from TCustomComboBox, so I could typecast it to that. But how do I access the Text property to fill it with the name of the file dropped on it? It is a protected field in TCustomComboBox. I took a gamble and assumed that it just publishes this field like TComboBox does and hard type casted it to TComboBox. It worked.
So, after several hours of hacking I can now drop an executable from the Windows Explorer on the Host application input field and its filename will automagically appear there.
As you can seen from the table above, there are some other interesting input fields for drop file support. They will come next, but for today, one is enough.