3

I'd like to detect when a new form has been created.

Now I use the Screen.ActiveFormChange event and check for new forms in Screen.CustomForms but ActiveFormChange is fired after the OnShow event of the form.

I'd like to detect the form even before OnShow was fired. Is there any way to do this without modifying the Vcl.Forms unit?

I'd like to detect all forms (also Delphi modal messages etc.) therefore inheriting all forms from a custom class is not possible (correct me if I am wrong).

Alternatively, is it possible to detect that a new component was added to some TComponent.FComponents list?

oxo
  • 946
  • 9
  • 21
  • normally you have control over the creation of forms, why the "need" for detection? – whosrdaddy Jul 20 '12 at 08:35
  • not really. if you call ShowMessage, the dialog window is created somewhere inside the Dialogs unit. – oxo Jul 20 '12 at 08:38
  • your code is calling showmessage, right? so you HAVE control... you can create a wrapper function for that. (DetectShowMessage or something like that) – whosrdaddy Jul 20 '12 at 08:49
  • @whosrdaddy That results in lots and lots of wrapper code – David Heffernan Jul 20 '12 at 08:51
  • @whosrdaddy David is right. ShowMessage was only an example. The method has to be used in different delphi applications that start all sorts of dialogs so a wrapper is not possible. What I want to detect is when a new form is added to Screen.CustomForms and hook with http://stackoverflow.com/questions/8743876/how-do-i-catch-certain-events-of-a-form-from-outside-the-form to it. – oxo Jul 20 '12 at 08:56
  • I wonder that there is no Screen.OnFormCreated or anything similar. It would be maybe 5 extra lines of code in the Forms unit... – oxo Jul 20 '12 at 09:05
  • @oxo I don't think there's anything built in. You'd need to do a bit of VCL hacking. – David Heffernan Jul 20 '12 at 09:12
  • @DavidHeffernan yes I expected it. I have been searching through the Forms unit for a message that would be sent to the Application object or something like that, but haven't found anything yet... Do you think is it possible to achieve my goal without modifying the VCL code? – oxo Jul 20 '12 at 09:18
  • @oxo You can certainly do it without modifying VCL code, but you may need to apply some runtime code hooks to do so! – David Heffernan Jul 20 '12 at 09:30
  • @DavidHeffernan The problem is that I need to hook to private procedure inside an object (TScreen.AddForm). I have already used http://stackoverflow.com/questions/6905287/how-to-change-the-implementation-detour-of-an-externally-declared-function - but only for replacing a "normal" procedure, not a procedure of object. Is there a similar hook I could apply on TScreen.AddForm? – oxo Jul 20 '12 at 09:38
  • 1
    I think you'll need to use a class helper to crack that private method – David Heffernan Jul 20 '12 at 09:45
  • Thanks for the info - I've found your answer on SO, I'll try it. – oxo Jul 20 '12 at 09:48
  • Yes, I recall writing something on this subject here once, but I couldn't face searching for it. Well done for finding it! Hope it helps. – David Heffernan Jul 20 '12 at 11:13
  • Yes I managed to do that with your help! Thanks a lot. You are welcome to write your comments as an answer so that I can accept it and you get some points :) The key was to use http://stackoverflow.com/questions/10156430/how-i-can-patch-a-private-method-of-a-delphi-class to get the TScreen.AddForm pointer and patch it with http://stackoverflow.com/questions/6905287/how-to-change-the-implementation-detour-of-an-externally-declared-function - now I can override TScreen.AddForm! Thanks for the tips! – oxo Jul 20 '12 at 11:22
  • I'm not really that fussed about writing an answer and getting points here. You could always upvote the answers that helped you if you want. – David Heffernan Jul 20 '12 at 11:23
  • 2
    Mike Lischke's `TThemeManager` used a series of techniques for detecting new forms in the program. (It had to know about new forms so it could instrument the paint routines of its components and apply XP themes.) It could be worth inspecting that code to see how it's done. – Rob Kennedy Jul 20 '12 at 13:41

3 Answers3

4

You can use the SetWindowsHookEx function to install a WH_CBT Hook, then you must implement a CBTProc callback function and finally intercept one of the possible code values for this hook. in this case you can try with HCBT_ACTIVATE or HCBT_CREATEWND.

Check this sample for the HCBT_ACTIVATE Code.

var
 hhk: HHOOK;

function CBT_FUNC(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
const
  ClassNameBufferSize = 1024;
var
 hWindow: HWND;
 RetVal : Integer;
 ClassNameBuffer: Array[0..ClassNameBufferSize-1] of Char;
begin
   Result := CallNextHookEx(hhk, nCode, wParam, lParam);
   if nCode<0 then exit;
   case nCode of
     HCBT_ACTIVATE:
     begin
       hWindow := HWND(wParam);
       if (hWindow>0) then
       begin
          RetVal := GetClassName(wParam, ClassNameBuffer, SizeOf(ClassNameBuffer));
          if RetVal>0 then
          begin
            //do something  
            OutputDebugString(ClassNameBuffer);                     
          end;
       end;
     end;
   end;

end;

Procedure InitHook();
var
  dwThreadID : DWORD;
begin
  dwThreadID := GetCurrentThreadId;
  hhk := SetWindowsHookEx(WH_CBT, @CBT_FUNC, hInstance, dwThreadID);
  if hhk=0 then RaiseLastOSError;
end;

Procedure KillHook();
begin
  if (hhk <> 0) then
    UnhookWindowsHookEx(hhk);
end;

initialization
  InitHook();

finalization
  KillHook();

end.

Note : if you uses the HCBT_CREATEWND code instead you will intercept any window created by the system not just "forms".

RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • Thanks, it's a VERY nice approach. Although it has some drawbacks: using HCBT_ACTIVATE binds the "//do something" code after OnShow event. And when using HCBT_CREATEWND, delphi's FindControl(hWindow) does not find anything. – oxo Jul 20 '12 at 18:32
  • 1
    That is because `HCBT_CREATEWND` is triggered at the time the `HWND` is created, before the VCL has a chance to associate it with a `TWinControl` object. – Remy Lebeau Jul 20 '12 at 19:47
  • That's absolutely true, but still a drawback... Nonetheless, it's a much better solution than using OnIdle, to my mind. For VCL forms it has about the same effect as Screen.ActiveFormChange. – oxo Jul 20 '12 at 20:21
2

Track Screen.CustomFormCount in Application.OnIdle:

  private
    FPrevFormCount: Integer;
  end;

procedure TForm1.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
begin
  if Screen.CustomFormCount > FPrevFormCount then
    Caption := Caption + ' +1';
  if Screen.CustomFormCount <> FPrevFormCount then
    FPrevFormCount := Screen.CustomFormCount;
end;

procedure TForm1.TestButton1Click(Sender: TObject);
begin
  TForm2.Create(Self).Show;
end;

procedure TForm1.TestButton2Click(Sender: TObject);
begin
  ShowMessage('Also trackable?');  // Yes!
end;

procedure TForm1.TestButton3Click(Sender: TObject);
begin
  OpenDialog1.Execute; // Doesn't update Screen.CustomFormCount
end;

Native dialogs managed and shown by Windows (TOpenDialog, TFontDialog, etc...) are created apart from the VCL and to track them also, you need a hacking unit. Try this one then.

Community
  • 1
  • 1
NGLN
  • 43,011
  • 8
  • 105
  • 200
  • OnIdle does NOT work the way I described it in the question - OnIdle is fired after OnShow (tested with main form). I think that hooking VCL as David suggested in the comments is the only proper way to get this working. But thanks anyway for noting native dialogs. – oxo Jul 20 '12 at 12:26
1

Thanks to David I found a solution: The clue is to replace Screen.AddForm method with your own. The way how to do it is described in these SO answers:

Thanks again!

Community
  • 1
  • 1
oxo
  • 946
  • 9
  • 21
  • Are you tried installing a WH_CBT hook with the [SetWindowsHookEx](http://msdn.microsoft.com/en-us/library/windows/desktop/ms644990%28v=vs.85%29.aspx) function? – RRUZ Jul 20 '12 at 16:40
  • Ok, I will post an answer about this topic. – RRUZ Jul 20 '12 at 16:46