8

Using Delphi XE2, Win64.

So I have a large application with many forms and if I open the help file from the main form and open a modal window and then hit F1 to fire off context-sensitive help on the modal window the help file window shows with the correct info but the help file cannot be closed until I close the modal window. I cannot even get the help file to have focus again if I go back to the application until the modal window closes.

Calling this exact same help file from the old version of our application (built with Delphi 6) sitting within the same folder as the new version (built with Delphi XE2) the help file displays when the F1 key is hit from the modal window and is responsive and can be closed like I expect.

The help file is .chm type file.

To summarize.

Launch application Open help file by F1 Jump to application and open modal window in application Launch help from modal window by hitting F1 Help file window cannot be closed until I jump back to my application and close the modal window.

Does anybody have any idea at all why this would be?

I have searched the Internet and have not found any similar issue.

We are stumped.

Cheers TJ

----EDIT----

Here is some code for a sample two form app that also exhibits this behavior.

program Project1;

uses
  Vcl.Forms,
  HTMLHelpViewer,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2};

{$R *.res}

begin
  Application.Initialize;
  Application.HelpFile := 'C:\helpfile.chm';
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Here is Form1 code:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses Unit2;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2 := TForm2.Create(Application);
  try
    Form2.ShowModal;
  finally
    Form2.Free;
  end;
end;

end.

I set the helpcontext property on the two forms to two valid contexts within my help file.

Run the app - F1 to open help file Hit button so Form2 is created and shown F1 to call help file Cannot close help file until I close Form2.

Hope this helps. - TJ

TJ Asher
  • 737
  • 9
  • 27

1 Answers1

9

This is a serious design flaw in HtmlHelpViewer. And it's easy to reproduce the behaviour you describe. Well done for specifying the problem so clearly. The issue afflicts both 32 and 64 bit programs equally.

Personally I don't use HtmlHelpViewer because it just doesn't work. I implement a handler for TApplication.OnHelp. It looks like this:

class function THelpWindowManager.ApplicationHelp(Command: Word; 
  Data: THelpEventData; var CallHelp: Boolean): Boolean;
begin
  CallHelp := False;
  Result := True;
  //argh, WinHelp commands
  case Command of
  HELP_CONTEXT,HELP_CONTEXTPOPUP:
    HtmlHelp(GetDesktopWindow, Application.HelpFile, HH_HELP_CONTEXT, Data);
  end;
end;

Put that in in a class and assign it to Application.OnHelp on startup:

Application.OnHelp := THelpWindowManager.ApplicationHelp;

I've just tested that out on the trivial two form application and it works well. In real code you may wish to embellish this. For example, my actual code is more complex. It stores in user settings the position and window state of the help window when it is closed. And then when shown again, that position and window state are restored. So that the help window appears to remember where it last was on the screen.


Thanks to @Sertac for dredging out the details in the comments below. In summary here's where the HtmlHelpViewer code goes wrong:

  1. It makes sends the HH_INITIALIZE command at help system startup.
  2. As described in the documentation this configures HTML Help to run on the same thread as the calling application instead of a secondary thread.
  3. When you call ShowModal that calls DisableTaskWindows which disables windows in the calling thread.
  4. Because the help viewer window was created by your app's main thread (because of the HH_INITIALIZE command), it gets disabled.

And that's why you cannot interact with the a pre-existing help window whilst a Delphi modal form is active.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • How does it behave if you pass the handle of your main form instead of the desktop window? – Sertac Akyuz Feb 21 '13 at 22:05
  • 1
    @Sertac Well, I expect that to have window ownership problems when the modal window disables its owner. But no, that's fine too. `HtmlHelpViewer` blows chunks. – David Heffernan Feb 21 '13 at 22:07
  • That's a +1 then, since it won't get in the way if you need to pass a form's handle. BTW, I think it's ShowModal disabling thread windows by way of DisableTaskWindows. – Sertac Akyuz Feb 21 '13 at 22:20
  • Brilliant mate! Works like a charm. Oh, and this removes the need to use HTMLHelpViewer in the project. – TJ Asher Feb 21 '13 at 22:29
  • @Sertac in some modes of operation, the help control runs its windows out of a different thread – David Heffernan Feb 21 '13 at 22:31
  • @David - It's the `HH_INITIALIZE` command through `HtmlHelp` in `ValidateHelpViewer` of `THtmlHelpViewer`. Bypassing the call causes the help window parent to be launched in a different thread. – Sertac Akyuz Feb 21 '13 at 23:00
  • @SertacAkyuz Yep, that rings bells. So, HtmlHelpViewer calls `HH_INITIALIZE` and so runs in the calling thread. And then when `ShowModal` is called, the window gets disabled by `DisableTaskWindows`. Is that it? – David Heffernan Feb 21 '13 at 23:04
  • @David - Exactly. Easy to simulate the other way around by modifying `FInitialized` of `THtmlHelpViewer` to be 'true' through the debugger before it is tested. It later causes an AV but it is enough to see the help window is in a different thread. – Sertac Akyuz Feb 21 '13 at 23:31
  • Thanks @Sertac. I'm sure I knew these details once but I'm getting so forgetful in my dotage! I'm minded to QC this but it's probably a waste of time. HtmlHelpViewer is probably beyond redemption. – David Heffernan Feb 21 '13 at 23:39