Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Catching OnHelp in Delphi 7 - Event never fires 2

Status
Not open for further replies.

djjd47130

Programmer
Nov 1, 2010
480
US
I have been trying to detect when the user is trying to open the help for something. Switching a form to "KeyPreview:= True" and catching the "OnKeyDown" event works, but does not cut it. Plus, this project has hundreds of forms, and I'm not going to go do this on each and every form, and try to remember later to add it to any new forms, etc.

Therefore, I have been seeking a global application-level event handler for catching the help. In the "Application" object, there is in fact an event "OnHelp" which I tried to use, but for some reason this event is never triggered. The goal is to provide 2 help resources for the application: A) regular HTML help (.chm files) which is already there and working, and B) online help to open a corresponding webpage instead of the local help file. In order to do so, I have to catch any possible event of trying to open the help (not just F1), catch the HelpContext ID, cancel the local help file from opening, then open the web page in its place.

Here's my code related to what I'm trying to do...
Code:
function TForm1.AppHelp(Command: Word; Data: Longint; var CallHelp: Boolean): Boolean;
begin
  //Breakpoint here is never reached
  if WebHelp then begin //Check if user's option to use Web Help is enabled
    CallHelp:= False; //Cancel local help from opening
    DoHelp(Data); //Open web help instead
    //is "Data" supposed to be the HelpContext ID?
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnHelp:= AppHelp; //assigning function to event
end;

procedure TForm1.DoHelp(const HelpContext: Integer);
const
  WebRoot = '[URL unfurl="true"]http://mycompanywebsiteroot.com/Help.htm?id=';[/URL]
begin
  //Help.htm is a page that redirects to proper page based on 'id' parameter
  ShellExecute(WindowHandle, 'open', PChar(WebRoot+IntToStr(HelpContext)), nil, nil, SW_SHOWNORMAL); //launch web page in default browser
end;


JD Solutions
 
Only a guess, but check the value of Keypreview.

If it's not true, then the F1 key will not be seen by the form, but by the active control.
 
No, this is definitely not the problem. Like I said, I don't have any difficulties using the form's OnKeyDown event, and KeyPreview is in fact enabled. But this is NOT the method I want to use - I have hundreds of forms and I will not put this code on all hundred+ forms. The point is I need an application level event handler - which has nothing to do with catching a key press.

JD Solutions
 
Then you'll want to handle the WM_HELP message in your application. That way, you won't have to handle it on each of your forms.
Code:
  TForm1 = class(TObject)
  ...
  private
    { Private declarations }
    procedure WMHELP(var Msg: TWMHelp); message WM_HELP;
  public
   ...
  end;

procedure TForm1.WMHELP(var Msg: TWMHelp);
begin
   //handle help message here.
end;
 
Thank you, that half way answers my question. Now the problem is 2 things: A) How do I get the HelpContext ID which corresponds with the control which was focused, and B) How do I cancel the local help file from opening in this case?

And also, ok I can do this on a form, but do I have to do this on all forms? Can I just do it on the main form and the rest will work?

JD Solutions
 
No, as a message handler, just include it in the main form, it should work for all your forms.

You should be able to get the helpcontext from the active control:
Code:
procedure TForm1.WMHELP(var Msg: TWMHelp);
var
   ActiveCtrlID: Integer;
begin
   ActiveCtrlID:= Screen.ActiveControl.HelpContext;
   //handle help message here.
end;
 
As far as cancelling the local help, I'd have to see how it's triggered now.

Maybe a flag can be set within the message handler that is checked before the local help is opened. If the flag is set, don't open help, and turn off the flag, else open the local help.
 
After digging and digging into this, I found that this message also for some reason is missing some info. The help context ID (Msg.HelpInfo.dwContextId) always returns 0. I think Msg.Result may be able to cancel the action, don't know yet. But I absolutely need to figure out a way to get this context id. Remember, I have hundreds of forms in this project so I need something on the global application level, not on the form level.

JD Solutions
 
* clarifying what I mean - I'm sure I'm not the first person who's done this. There has to be some source already done exactly for this. Any tips would be great.

JD Solutions
 
I neglected to add the call to the inherited WMHelp message from within the form's message handler.

Code:
procedure TForm1.WMHELP(var Msg: TWMHelp);
begin
   inherited;
   ...
end;

See if that sets the help context ID.
 
A few item's I've found looking into this, hopefully one of them helps.

This from Quality Central

Code:
The context-sensitive help isn't shown as a hint when using the bordericon Help in a Form, but it's shown as normal helpcontext.This is due to the following function from the RTL Unit HelpIntfs:

function THelpManager.Hook(Handle: Longint; HelpFile: String; Command: Word; Data: Longint): Boolean;
begin
  if HelpFile <> '' then FHelpFile := HelpFile;
  case Command of
    HELP_CONTEXT:
    begin
     ShowContextHelp(Data, HelpFile);
    end;
    { note -- the following subtly turns HELP_CONTEXTPOPUP into HELP_CONTEXT.
      This is consistent with D5 behavior but may not be ideal. }
    HELP_CONTEXTPOPUP:
    begin
     ShowContextHelp(Data, HelpFile);
    end;
    HELP_QUIT:
    begin
     DoSoftShutDown;
    end;
    HELP_CONTENTS:
    begin
      DoTableOfContents;
    end;
  else
    CallSpecialWinHelp(Handle, HelpFile, Command, Data);
  end;
  Result := true;
end;

The case HELP_CONTEXTPOPUP is treated the same as HELP_CONTEXT, which causes the display of the normal helpcontext instead of a hint.
To solve this you have to clear the case HELP_CONTEXTPOPUP, then the procedure CallSpecialWinHelp will be called, which does show the context-sensitive help as a hint
If you don't want to temper with the RTL, it is of course also possible to hook the WM_HELP message yourself and write something like this:

procedure TForm1.WMHelp(var Message: TWMHelp);
var
  Control: TWinControl;
begin
  Control := FindControl(Message.HelpInfo^.hItemHandle);
  WinHelp(Screen.ActiveForm.Handle,pChar(Application.HelpFile),HELP_CONTEXTPOPUP, Control.HelpContext);
end;

This from experts-exchange

Code:
type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Edit1: TEdit;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure WMHELP(var Msg: TWMHelp); message WM_HELP;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.WMHELP(var Msg: TWMHelp);
begin
  Edit1.Text := IntToStr(Msg.HelpInfo.hItemHandle);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Label1.Caption := IntToStr(Memo1.Handle);
end;
 
I got very close with it... but it's still on a form-by-form basis. Using Screen.ActiveControl.HelpContext works, but isn't very sufficient, because if I have for example the [?] showing in the top-right next to [X] (Close) (which turns the pointer into a help pointer, click a control to get help on it) - that doesn't work. The control doesn't get the focus when clicked, so it's not getting the proper help context.

I'll take a look into those posts you put up there. Thank you.

JD Solutions
 
Getting the item handle looks to be promising, as in this second example of yours. I did in fact get the correct handle - but now what? How to get the HelpContext ID of the corresponding TWinControl of this handle? I guess what I mean is how to use this handle to get an instance of this control?


JD Solutions
 
Try

Code:
var
  Control: TWinControl;
begin
  with Msg.HelpInfo^ do
  begin
    Control := FindControl(hItemHandle);
    Edit1.Text := intToStr(Control.HelpContext);

Another link with some useful information - Not Delphi specific, but shows how to handle Help initiated from various events..

 
Thanks - I'm not used to handling handles :p

While I'm doing my testing for this in Delphi 7, the real project where this will come into play is in Delphi 2010. The Application.OnHelp event does work in D2010, but for some reason fires 3 times in a row every time. I certainly don't want to open the web page 3 times in a row.

Now we are also looking into upgrading entirely to Delphi XE2 (specifically Firemonkey). We have a demo of this, and upon testing, the Application.OnHelp event works perfectly. Only one weird thing here is "Integer" has now become "NativeInt" which is very strange. I've never used NativeInt, but somehow it is supported in earlier versions of Delphi (just not used anywhere). This is due to the new 64 bit compiling capabilities of XE2.

So I may have a different approach in the end for this. Thanks for all the help on the help.

JD Solutions
 
(finishing my last post) - My 2 computers only have Delphi 7 - due to licenses and what-not only the core developers have D2010. I use D7 personally all the time, I think it's the most stable version (and I trust Borland much more than Embarcadero). Today I worked with the devs and did the testing on D2010 and DXE2 and got those results. It looks as if this help functionality was somehow broken in D7 (while probably worked fine in earlier Delphi versions) - then in D2010 they tried to make it work but was somehow buggy - then finally in XE2 (or maybe even just XE) they got it working again. Of course using the message method as you recommend should presumably be supported in all versions - so long as I finish my code to be compatible. Once I get it 100% working, maybe I'll post an FAQ here on "Adding Online Help to Delphi Projects".

JD Solutions
 
Looking forward to your FAQ post... interesting note about how help (doesn't) work in 2010. I have D7, D2010 and I just purchased XE2, but have yet to install it, I'll have to test it when I get all three environments up and running.
 
I got very close with it... but it's still on a form-by-form basis.

I read this and I'm not sure precisely what the issues are, but regarding the above quote, why not just tie into the Application level event ([link=http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Forms_TApplication_OnHelp.html]TApplication.OnHelp[/url]) and be done with it from there? That's what I've been doing with this little HLP->CHM unit I've been trying to work out off and on...

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Glenn,

This is the entire problem of mine. In Delphi 7, Application.OnHelp is never triggered. I did make an event handler for this and assigned it, but no cigaro. It must be a bug in Delphi 7, because I do get success in later versions of Delphi.

JD Solutions
 
Okay...I've just been setting TApplication.HelpFile and it's been working for me just by including the unit. I did look and found this, which indicates a bug fix for > Delphi 6 and < Delphi 2009 for OnHelp:


Look for Example12.zip and download that to study the D6OnHelpFix.pas file. Hopefully that will point you in the right direction...



It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top