1

How to add support of HTML help files (.chm) on Delphi XE2? We need to use A-links (A-keywords) on HelpContext property of every control to lookup help pages. Delphi XE2 has native support of HTML help files by unit HTMLHelpViewer. But how to use it?

Dmitry
  • 14,306
  • 23
  • 105
  • 189

3 Answers3

2

It's not hard with F1 jump to a context.

Select Edit1 and press F1 . Help opens and Overview.htm is shown.

enter image description here

Prerequisite.

enter image description here

Edit1 Help settings:

enter image description here

sample.chm source settings.

sample.ali

IDH_Overview=Overview.htm
IDH_welcom=FirstTopic.htm
IDH_UsingtheMenus=Overview.htm

sample.h

#define IDH_Creating_Projects_and_Topics 1005
#define IDH_Overview 1003
#define IDH_UsingtheMenus 1009

Unit1.pas

unit Unit1;

interface

uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, HTMLHelpViewer, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    HHALINKLOOKUP: TButton;
    JumpAnchor: TButton;
    Edit1: TEdit;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure HHALINKLOOKUPClick(Sender: TObject);
    procedure JumpAnchorClick(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
var
hpPath : string;
link : HH_AKLINK;

procedure TForm1.FormCreate(Sender: TObject);
begin
  hpPath := ExtractFilePath(Application.ExeName) +
    'HelpFile\sample.chm';
  Application.HelpFile := hpPath;
end;

procedure TForm1.HHALINKLOOKUPClick(Sender: TObject);
var
link : HH_AKLINK;
szUrl,szKey,szMsgText,szMsgTitle,szWindow : AnsiString;
begin
   szKey      := Edit1.Text; // 'UsingtheMenus';
   szUrl      :='Overview.htm';
   szMsgText  :='Error: Can''t find "'+Edit1.Text+'"!';
   szMsgTitle :='Error: HH_ALINK_LOOKUP';
   szWindow   :='main';

   with link do begin
   cbStruct    := sizeof(HH_AKLINK) ;
   fReserved   := False;
   pszKeywords := PChar(szKey);
   pszUrl      := nil;
   pszMsgText  := PChar(szMsgText);
   pszMsgTitle := PChar(szMsgTitle);
   pszWindow   := PChar(szWindow);
   fIndexOnFail:= False;
   end;
   HtmlHelpW(0, hpPath+'>main', HH_DISPLAY_TOPIC, DWORD_PTR(nil));
   HtmlHelpW(0, hpPath, HH_ALINK_LOOKUP, DWORD_PTR(@link));
end;

procedure TForm1.JumpAnchorClick(Sender: TObject);
begin
  HtmlHelpW(0, hpPath+'::/Overview.htm#'+Edit1.Text+'>main', HH_DISPLAY_TOPIC, DWORD(nil));
end;
end.

Here is a ready to use sample.chm and the source Download

There is a trick how to easily, to jump, not only to the .htm file but jumps directly to an anchor.

Change sample.ali

IDH_Overview=Overview.htm
IDH_welcom=FirstTopic.htm
IDH_UsingtheMenus=Overview.htm#UsingtheMenus

Insert an anchor at the place, you want to jump to in Overview.htm

[...]
<A NAME="UsingtheMenus" </A>
<P><STRONG>Using the Menus and Toolbars</STRONG>
<P>The menus and toolbars provide a complete set of tools 
[...]

Now it is possible with F1, jump directly to the desired point in overview.htm.

enter image description here

moskito-x
  • 11,832
  • 5
  • 47
  • 60
1

I suspect that to use A-links you need to do the following:

  1. Assign an Application.OnHelp handler as described below.
  2. Assign Application.HelpFile during program startup.
  3. Call Application.HelpKeyword if you wish to invoke the help system with an A-link.
  4. Set the HelpKeyword property for any GUI controls that you wish to respond to context sensitive F1 key presses.

The OnHelp handler looks like this:

function TMainForm.ApplicationHelp(Command: Word; 
  Data: THelpEventData; var CallHelp: Boolean): Boolean;
var
  Link: THH_AKLink;
  ALink: string;
begin
  CallHelp := False;
  Result := True;
  //argh, WinHelp commands
  case Command of
  HELP_COMMAND:
    begin
      ZeroMemory(@Link, SizeOf(Link));
      Link.cbStruct := SizeOf(Link);
      ALink := PChar(Data); // we are going to re-purpose the keyword as an A-link
      Link.pszKeywords := PChar(AnsiString(ALink)); // seems we have to pass a PAnsiChar ..
      Link.fIndexOnFail := True;
      HtmlHelp(GetDesktopWindow, Application.HelpFile, HH_ALINK_LOOKUP, 
        DWORD_PTR(@Link));
    end;
  end;
end;

The HtmlHelpViewer unit contains methods named LookupALink which do the same. But I don't see how they could ever be called.

The above approach is a little bit hacky because it interprets keywords as A-Links. If you want context sensitive help, I can't see what else you can do.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • But `Application.HelpKeyword` shows help by keyword (a title of help page) - not by A-link (a special code of help page). – Dmitry Mar 07 '13 at 17:11
  • Yep. Didn't spot that nuance. I fixed the answer. I don't think HtmlHelpViewer gets the job done. You need to write your own `OnHelp` handler. I think. I always do that anyway because `HtmlHelpViewer` is so buggy. – David Heffernan Mar 07 '13 at 17:20
  • Thanks! I'll try it now. But does `Application.HelpJump` call help with A-link too? – Dmitry Mar 07 '13 at 17:29
  • No. That resolves to `HH_DISPLAY_TOPIC`. In your question you say you want context sensitive help with different destinations for each control. All you have to work with are the `HelpContext` and `HelpKeyword` properties. The former is no good. It's an integer. Thus you must use `HelpKeyword`. – David Heffernan Mar 07 '13 at 17:36
  • How to create `THelpEventData` by A-link string? – Dmitry Mar 07 '13 at 17:46
  • You don't. The system does it for you. Call `Application.HelpKeyword` and pass your A-link string. That will route to that event. Remember, what we have in the answer is an event handler. Just like any event handlers, you do not call them directly. – David Heffernan Mar 07 '13 at 17:48
  • Should I add the customed `OnHelp` handler for every form on the project, bpls, dlls or only for the main form? – Dmitry Mar 07 '13 at 17:50
  • Delphi writes `undeclared identifier` for `HtmlHelpViewer.LookupALink`. – Dmitry Mar 07 '13 at 17:52
  • Our project receives WM_MYHELP message from all forms from the application and bpls: `SendMessage(Handle, WM_MYHELP, 0, DWORD(PChar(Self.HelpKeyword)));`. We need to show help by A-Link from this message. – Dmitry Mar 07 '13 at 17:57
  • We need only to call help with A-link string. If I pass A-link string to `Link.pszKeywords` on the code above help opens with standard lookup (not by A-link) and only with the first char of the keyword. – Dmitry Mar 07 '13 at 18:05
  • @Altaveron Slow down! Read the answer. "Should I add the customed OnHelp handler for every form on the project, bpls, dlls or only for the main form?" No, just to `Application.OnHelp`, as I state in the answer. – David Heffernan Mar 07 '13 at 18:08
  • "Delphi writes undeclared identifier for HtmlHelpViewer.LookupALink" Yes. That would happen. But my answer doesn't call `LookupALink`. I merely point out that it cannot be called! Stay on course. – David Heffernan Mar 07 '13 at 18:09
  • Er, I've no idea what `WM_MYHELP` is. Please don't expect me to read your mind. That's not in the question. I think I answered the question that you asked. This comment trail is a disaster already. – David Heffernan Mar 07 '13 at 18:10
  • `HH_ALINK_LOOKUP` is known to work. `HtmlHelp()` is known to work. The code in the answer will connect your app to the help system and use A-links rather than keywords. – David Heffernan Mar 07 '13 at 18:11
  • OK, there's a problem with Unicode/Ansi. That's why only the first character is used. Bear with me. – David Heffernan Mar 07 '13 at 18:30
  • OK, the code in the answer will work. But I'd like to investigate more. I also answered at the question with the bounty. I'll put more detail there when I have it. – David Heffernan Mar 07 '13 at 18:39
  • The actual code is incorrect. There is no `ALink` on the `TMainForm.ApplicationHelp`. – Dmitry Mar 12 '13 at 12:18
  • This code opens help by keyword instead by A-link for me. I don't know why. But I fixed it by setting `CallHelp := False;` on `Application.OnHelp` and by calling help manually with the same code as between `begin` and `end` inside `case`. – Dmitry Mar 12 '13 at 12:30
  • Hmm. The code in the answer already sets `CallHelp` to `False`. – David Heffernan Mar 12 '13 at 12:41
  • I removed all code expept `CallHelp := False` on `OnHelp = ApplicationHelp` method. And call removed code manually. Else F1 (only F1 key) shows keyword help instead A-link help. This issue also exists if the active form is the main application form. And now I need to fix only this issue - all other works fine. – Dmitry Mar 12 '13 at 13:23
  • I fixed this issue by `aLink.fIndexOnFail := False;` instead `True`. But now help doesn't show for the main window. Why? A-link is correct. – Dmitry Mar 12 '13 at 13:47
  • Only `Application.MainFormHandle` as the first parameter for `HtmlHelp` call works incorrectly. – Dmitry Mar 12 '13 at 13:53
  • I don't know. I've not got your project. My simple test project works fine. Are you working with a very small and simple project? – David Heffernan Mar 12 '13 at 13:56
  • The project is very large. But I removed all code calls help before add new one. – Dmitry Mar 12 '13 at 15:53
  • Try starting from an empty project. File | New | VCL Forms App. – David Heffernan Mar 12 '13 at 15:58
  • David, do you know how just open HTML Help on the first page (without lookup)? – Dmitry Mar 12 '13 at 18:32
  • How do you want to identify that page? Have you found the documentation yet? – David Heffernan Mar 12 '13 at 18:42
  • HTML Help has an issue. It never opens the first page by A-link. I checked - link is correct. But HtmlHelp() doesn't open it if `aLink.fIndexOnFail := False` and searching by index if `aLink.fIndexOnFail := True`. So I just need to open the first page without transfering A-link. – Dmitry Mar 12 '13 at 18:44
  • It seems, all A-links with "_" char don't work! At last I found the original issue for all other problems! – Dmitry Mar 12 '13 at 18:47
  • Very strange issue. A-links with '_' char don't work only for the main form. All other A-links work on the main form. All links work on other forms. (Form handler is transfered to HtmlHelp() procedure.) – Dmitry Mar 12 '13 at 19:20
  • Perhaps the reason why you have to convert to AnsiString is that Alinks can only use a very limited character set. Did you try URL encoding the underscore? `'Start%5FPage'` Or is the problem not related to underscores and `Start_Page` is a Klink rather than an Alink? – David Heffernan Mar 12 '13 at 19:21
  • I found that there is no dependency on is it main form or not. The issue depends on `_` char. It exists if I use `_` on .pas source - and there is no issue if I enter `_` on .dfm-file and read it on code. – Dmitry Mar 12 '13 at 19:25
  • You are right. This is not A-link. And not keyword. Something else. May be K-link (but I don't know what it is). So initial problem is that another developer trying to use this other kind of link instead A-links on some places. And all problems became from that. – Dmitry Mar 12 '13 at 19:31
0

Not sure how Xe2 viewer works (I'm on 2007) but I just use Eric Granges port of the Microsoft HTML help API, which unsurprisingly, is called HTMLhelpAPI.pas.

You can call an Alink using the function

ChmShowTopic(const filename,atopic:string):HWND;

Andy k
  • 1,056
  • 1
  • 11
  • 22
  • No, that uses the `HH_DISPLAY_TOPIC` which is not for A-links. What's more, that port of the HTML Help API is not necessary in modern Delphi since the Delphi supplied header translation has all that's needed. That code dates from 1998!! – David Heffernan Mar 11 '13 at 13:27