4

I am implementing context sensitive help in my Delphi 2009 application. It works fine except in one case. I cannot identify that I am in the main menu, and which menu item has been opened.

What I want to do is if the user has opened the File menu and while its open presses F1, then I'll bring up my help on the File menu. If they open the Edit menu and press F1, then I'll bring up my help on the Edit menu, etc.

I am using ApplicationEventsHelp to process the user's pressing of F1 as follows:

function MainForm.ApplicationEvents1Help(Command: Word; Data: Integer;
  var CallHelp: Boolean): Boolean;
begin
  if Command = HELP_COMMAND then begin
    Application.HelpSystem.ShowTopicHelp(PChar(Data), Application.CurrentHelpFile);
    CallHelp := false;
  end;
  Result := true;
end;

As I mentioned, this works for everything except the main menu. I've tried using

FindVCLWindow(Mouse.CursorPos)

and other such methods that identify the active control to see if they would identify the menu, but they don't seem to.

Is there a way to tell which menu item (if any) is open when the F1 key is pressed?


Thank you everyone for your help and good ideas.

Just to document my final solution, I found that the system is not particularly good at figuring out which control it is in and sometimes gets it wrong and passes incorrect data to ApplicationEventsHelp which brings up an inappropriate help page.

After experimenting and using the solution for handling the menus in the accepted answer, I found it was best to identify which control I was in to bring up the correct help item. I ended up not even using the HelpKeyword property, but hardcoding it. The code is clear and it works. I also have my the help for my RVEdit window bringing up different help pages depending on the section of the document you are in (my CurCursorID tells me that).

For anyone who wants to do this like I did, here is how:

function TLogoAppForm.ApplicationEvents1Help(Command: Word; Data: Integer;
  var CallHelp: Boolean): Boolean;
var
  HelpKeyword: string;
  SType: string;

begin
  if Command = HELP_COMMAND then begin
    if PtInRect(RVEdit.ClientRect, RVEdit.ScreenToClient(Mouse.CursorPos)) then begin
      if CurCursorID = 'H' then HelpKeyword := 'RefTopReport'
      else if CurCursorID = 'T' then HelpKeyword := 'RefTableContents'
      else if CurCursorID = '~HNAME' then HelpKeyword := 'RefIndexNames'
      else if copy(CurCursorID, 1, 2) = 'N+' then HelpKeyword := 'RefIndexNames'
      else if CurCursorID = 'B' then HelpKeyword := 'RefBottomReport'
      else if CurCursorID <> '' then HelpKeyword := 'RefInformationArea'
      else HelpKeyword := 'RefEverythingReport';
      Application.HelpSystem.ShowTopicHelp(HelpKeyword, Application.CurrentHelpFile);
    end
    else if PtInRect(ElTree.ClientRect, ElTree.ScreenToClient(Mouse.CursorPos)) then
      Application.HelpSystem.ShowTopicHelp('RefTreeView', Application.CurrentHelpFile)
    else if PtInRect(TopToolbar.ClientRect, TopToolbar.ScreenToClient(Mouse.CursorPos)) then
      Application.HelpSystem.ShowTopicHelp('RefTopToolbar', Application.CurrentHelpFile)
    else if PtInRect(BottomToolbar.ClientRect, BottomToolbar.ScreenToClient(Mouse.CursorPos)) then
      Application.HelpSystem.ShowTopicHelp('RefBottomToolbar', Application.CurrentHelpFile)
    else
      Application.HelpSystem.ShowTopicHelp('RefMainWindow', Application.CurrentHelpFile);
    CallHelp := false;
  end
  else if Command = HELP_CONTEXTPOPUP then begin
    case Data of
      0: HelpKeyword := 'RefMenuBar';
      11: HelpKeyword := 'RefFileMenu';
      12: HelpKeyword := 'RefEditMenu';
      13: HelpKeyword := 'RefSearchMenu';
      14: HelpKeyword := 'RefNavigateMenu';
      15: HelpKeyword := 'RefViewMenu';
      16: HelpKeyword := 'RefOrganizeMenu';
      17: HelpKeyword := 'RefHelpMenu';
      else HelpKeyword := '';
    end;
    if HelpKeyword <> '' then begin
      Application.HelpSystem.ShowTopicHelp(HelpKeyword, Application.CurrentHelpFile);
      CallHelp := false;
    end;
  end;
  Result := true;
end;

I did have to put 11 through 17 into the HelpContext property of the MenuItems in my 7 main menus so that the correct help would come up depending on which menu you were in. The detection of the menu item is the help the answer to this question provided me.

The nice thing is that this code is easy to follow (using HelpKeywords instead of HelpContext numbers) and will probably still work even after conversion to Delphi XE and FireMonkey.

lkessler
  • 19,819
  • 36
  • 132
  • 203
  • 4
    Your entire approach is wrong. Don't use FindVCLControl. Set HelpContext for your controls and actions and that's it. – David Heffernan Oct 22 '11 at 08:27
  • @David - FindVCLControl was one idea I thought would work as a kludge. I wasn't happy with that approach and it didn't work anyway - which is why I asked this question. – lkessler Oct 22 '11 at 15:42

3 Answers3

3

Looking at Command = HELP_COMMAND and the cast of Data to PChar, it seems you work with a help system based on keywords rather then on context identifiers (HelpType = htKeyword).

(Here in Delphi 7) Menu items do not have the HelpType and HelpKeyword properties, so you are bound to use the HelpContext property:

function TForm1.ApplicationEvents1Help(Command: Word; Data: Integer;
  var CallHelp: Boolean): Boolean;
begin
  if Command = HELP_COMMAND then
  begin
    //Application.HelpSystem.ShowTopicHelp(PChar(Data), Application.CurrentHelpFile);
    //Doesn't this do the same?
    Application.HelpKeyword(PChar(Data));
    CallHelp := False;
  end
  else if Command = HELP_CONTEXT then
  begin
    // Convert the context identifier to your keyword, or:
    Application.HelpContext(Data);
    CallHelp := False;
  end;
  Result := True;
end;
NGLN
  • 43,011
  • 8
  • 105
  • 200
  • 1
    +1, in D2009, `TMenuItem` still don't have `HelpType` nor `HelpKeyword`, so `HelpContext` is the right approach. – TLama Oct 22 '11 at 09:02
  • 3
    I'm even not sure this event handler is needed anyway. CallHelp is True by default so the help topic should popup. This event handler is only meant for special intervention. – NGLN Oct 22 '11 at 09:03
  • @NGLN - Yes, I do special processing, but I simplified my example to the basics. – lkessler Oct 22 '11 at 15:10
  • @TLama - If you're saying that HelpType and HelpKeyword were added since D2009, then my problem will be solved when I upgrade to XE2. – lkessler Oct 22 '11 at 15:11
  • @NGLN - TY. You've given me the temporary workaround until I upgrade. However, I have to check for Command = HELP_CONTEXTPOPUP rather than HELP_CONTEXT in order to trap the HelpContext value in the menus. – lkessler Oct 22 '11 at 15:36
  • @lkessler HELP_CONTEXT is for the child menu items, HELP_CONTEXTPOPUP is for the parent menu items, unless you activate the menu bar by _ALT_ which does not _open_ the parent item immediately. So maybe it's best to check both. – NGLN Oct 23 '11 at 07:22
  • @Ikessler, I only said that D2009 don't have it. I don't know and can't verify (online help seems be down for now) if DXE2 have it. Sorry for misleading you. – TLama Oct 23 '11 at 09:52
2

By trapping windows message 'WM_MENUSELECT' it is possible to keep track of the selected menu item.

See menuitemhints for more information.

Example :

 type
    TForm1 = class(TForm)
    ...
    private
      fMyCurrentSelectedMenuItem : TMenuItem;
      procedure WMMenuSelect(var Msg: TWMMenuSelect) ; message WM_MENUSELECT;
    end

procedure TForm1.WMMenuSelect(var Msg: TWMMenuSelect) ;
 var
    menuItem : TMenuItem;
    hSubMenu : HMENU;
 begin
    inherited; // from TCustomForm (so that Application.Hint is assigned)

    menuItem := nil;
    if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then
    begin
      if Msg.MenuFlag and MF_POPUP = MF_POPUP then
      begin
        hSubMenu := GetSubMenu(Msg.Menu, Msg.IDItem) ;
        menuItem := Self.Menu.FindItem(hSubMenu, fkHandle) ;
      end
      else
      begin
        menuItem := Self.Menu.FindItem(Msg.IDItem, fkCommand) ;
      end;
    end;

    //miHint.DoActivateHint(menuItem) ;
    fMyCurrentSelectedMenuItem := menuItem;
 end; (*WMMenuSelect*)

So when the F1 button is pressed you can use the fMyCurrentSelectedMenuItem to activate the correct help.

LU RD
  • 34,438
  • 5
  • 88
  • 296
  • thanks for the answer. I was looking at solutions like this, but doing it NLGN's way is cleaner as it keeps the code all in one place. – lkessler Oct 22 '11 at 15:38
1

You can use GetMenuItemRect function:
1. Go through all items in your main menu and call GetMenuItemRect to get item position. Function will work only if item is displayed.
2. Use GetCursorPos and PtInRect to check if mouse is over menu item and call appropriate help topic.

Mike
  • 1,717
  • 2
  • 15
  • 19