2

I have a function that closes all the forms in the application apart from the main form

procedure CloseOpenForms(const Component: TComponent);
var
  i: Integer;
begin
  for i := 0 to pred(Component.ComponentCount) do
  begin
    CloseOpenForms(Component.Components[i]);

    if Component.Components[i] is TForm then
    begin
      TForm(Component.Components[i]).OnCloseQuery := nil;

      TForm(Component.Components[i]).Close;
    end;
  end;
end;

Called from the main form:

CloseOpenForms(Self);

It works fine as long as there are no active OLE dialogs (e.g. TJvObjectPickerDialog).

How can I force these non modal OLE dialogs to close?

norgepaul
  • 6,013
  • 4
  • 43
  • 76
  • The `TJvObjectPickerDialog` for instance implements the [`IDsObjectPicker`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms676973%28v=vs.85%29.aspx) interface which doesn't have any method for cancelling the displayed dialog (simply because it was intended to be displayed as a modal dialog without a *cancel from somewhere else* option). My guess is, it's impossible to do this in a clean way (if there is a way). – TLama Apr 24 '12 at 08:49
  • How are you creating forms ? I bet you are not using `Application.CreateForm`. – TLama Apr 24 '12 at 13:49
  • Correct. Each form is created when required: AForm := TMyForm.Create(Self) – norgepaul Apr 24 '12 at 13:54
  • 1
    You would have to let the `Application` be the owner of the forms to get it to work. So for your case it would be `AForm := TMyForm.Create(Application)`. – TLama Apr 24 '12 at 13:58

1 Answers1

3

JVCL passes the application handle to 'hwndParent' parameter of IDSObjectPicker.InvokeDialog, hence the dialog is owned (not like 'owner' as in VCL, but more like popup parent) by the application window. Then you can enurate windows to find out the ones owned by the application window and post them a close command.

procedure CloseOpenForms(const AComponent: TComponent);

  function CloseOwnedWindows(wnd: HWND; lParam: LPARAM): BOOL; stdcall;
  begin
    Result := TRUE;

    if (GetWindow(wnd, GW_OWNER) = HWND(lParam)) and (not IsVCLControl(wnd)) then
    begin
      if not IsWindowEnabled(wnd) then      // has a modal dialog of its own
        EnumWindows(@CloseOwnedWindows, wnd);

      SendMessage(wnd, WM_CLOSE, 0, 0);
    end;
  end;

  procedure CloseOpenFormsRecursive(const RecComponent: TComponent);
  var
    i: Integer;
  begin
    for i := 0 to pred(RecComponent.ComponentCount) do
    begin
      CloseOpenFormsRecursive(RecComponent.Components[i]);

      if RecComponent.Components[i] is TForm then
      begin
        TForm(RecComponent.Components[i]).OnCloseQuery := nil;

        TForm(RecComponent.Components[i]).Close;
      end;
    end;
  end;

begin
  EnumWindows(@CloseOwnedWindows, Application.Handle);

  CloseOpenFormsRecursive(AComponent)
end;
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • We're getting closer :o) The above code works if the OwnedWindow hasn't opened any additional windows. If it has (e.g. clicking Advanced in the ObjectPicker window) none of the windows close. – norgepaul Apr 25 '12 at 08:13
  • Also, the original code called EnumWindows for every iteration of CloseOpenForms. I made some edits to fix it. – norgepaul Apr 25 '12 at 08:18
  • 1
    I think I got it working. I added code to recursively call CloseOwnedWindows to close all the children of children. Unfortunately, it only works correctly if the Application.ProcessMessages call is in there. Please remove it if you can think of a better method. – norgepaul Apr 25 '12 at 08:30
  • @norge - Sorry for the recursion, I didn't really look close what the code did. Your modification is about the same that I've come up. I think it's not easily possible to get rid of 'Application.ProcessMessages' here. Thanks for the edit! – Sertac Akyuz Apr 25 '12 at 08:56
  • @norge - Here's one version that avoids Application.ProcessMessages. Note that you won't be able to nest callback to 'EnumWindows' in the 64bit compiler. – Sertac Akyuz Apr 25 '12 at 13:38
  • Can you explain why nesting won't work with the 64bit compiler? Thanks. – norgepaul Apr 25 '12 at 16:06
  • 1
    @norge - Because the compiler feels it has to pass some hidden/implicit parameter as the first parameter. See this question: http://stackoverflow.com/questions/10162749/why-cant-i-nest-winapi-callbacks-as-local-functions-in-64-bit-delphi-compiler – Sertac Akyuz Apr 25 '12 at 16:56