Using the contexthelp-button from the title-bar


This topic is 32-bit only and was made in cooperation with Paul Koufalis.

This example uses the source in procedure winstyle.p, available on page WinStyle.p.
It also uses the MsgBlaster control, free available for download.

It seems common practice in Windows 95 to create a Contexthelp button in the titlebar of a dialog, rather than a 'normal' helpbutton. When the user chooses the Contexthelp button it will stay down and the mouse pointer changes into an arrow with question mark. When the user now chooses a control, Windows will send a WM_HELP message to the top-level window, releases the Contexthelp button and reloads the usual mouse-pointer.
It's up to the application to handle the WM_HELP message.

This page explains what's going on. It seems a lot of work, at least when you have to repeat all steps for every new dialog. The good news is that it must be very easy to wrap everything up into one single ActiveX control.

Added later: We actually have created this ActiveX control: cthelp.ocx and it is attached so you can download it.
The examples on this page show how to do this for a Dialog. The sources work equally well for a window but that's a bit unusual. If you do want to use this on a window please be warned that the Contexthelp-button can not be placed until the Minimize/Maximize buttons are removed from the title bar.
So on a normal window you will prefer to call context-help from a toolbar instead the title bar. In that case, see contexthelp from any button.
Let's start with adding the Help button. Create a dialog and add this source fragment to the main block:

  DEFINE VARIABLE hStyle AS HANDLE NO-UNDO.
  RUN WinStyle.p PERSISTENT SET hStyle.
  RUN AddHelpButton IN hStyle (FRAME {&frame-name}:HWND).
  DELETE PROCEDURE hStyle.
  FRAME {&frame-name}:LOAD-MOUSE-POINTER("Arrow":U).
  RUN AddContextIDs.

Load-mouse-pointer("Arrow") is necessary for a Dialog, not for a window. I don't understand why.
The button works now and sends WM_HELP messages to the dialog. Progress normally doesn't notify you when external messages occur, so we seem to need the msgblaster control.
Drop a msgblaster on the dialog and set it up as follows:

PROCEDURE MsgBlaster.Msgblst32.MESSAGE .
  DEFINE INPUT        PARAMETER p-MsgVal    AS INTEGER NO-UNDO.
  DEFINE INPUT        PARAMETER p-wParam    AS INTEGER NO-UNDO.
  DEFINE INPUT        PARAMETER p-lParam    AS INTEGER NO-UNDO.
  DEFINE INPUT-OUTPUT PARAMETER p-lplRetVal AS INTEGER NO-UNDO.
 
  IF p-MsgVal = 83 /* = WM_HELP */ THEN
     RUN HelpContextPopup(p-lParam).
 
END PROCEDURE.
 
 
PROCEDURE initialize-controls :
  DEFINE VARIABLE hparent AS INTEGER NO-UNDO.
  DEFINE VARIABLE hc AS COM-HANDLE NO-UNDO.
 
  &IF "{&window-name}" <> "" &THEN
     /* if this is a window: */
     hparent = GetParent({&window-name}:HWND).
  &ELSE
     /* if this is a dialog: */
     hParent = FRAME {&frame-name}:HWND.
  &ENDIF
 
  hc = chMsgBlaster:Msgblst32.
  hc:MsgList(0) = 83.   /* = WM_HELP */
  hc:MsgPassage(0) = 1. /* or -1 or 0, didn't notice any difference */
  hc:hWndTarget = hparent.
  RELEASE OBJECT hc.
 
END PROCEDURE.

So now we will be notified if a WM_HELP message occurs; the message event will run the procedure HelpContextPopup. HelpContextPopup is a very general procedure e.g. not specific for one dialog, so you can safely put it in a persistent library.

PROCEDURE HelpContextPopup :
/*------------------------------------------------------------------
  Purpose:     show context help in a popup window
  Parameters:  p-lParam contains a pointer to a HELPINFO structure
  Notes:       
-------------------------------------------------------------------- */
  DEFINE INPUT PARAMETER p-lParam AS INTEGER.
 
  DEFINE VARIABLE helpinfo AS MEMPTR.
  DEFINE VARIABLE ContextType AS INTEGER.
  DEFINE VARIABLE HWND AS INTEGER.
  DEFINE VARIABLE ContextID AS INTEGER.
  DEFINE VARIABLE ReturnValue AS INTEGER.
 
  SET-SIZE(helpinfo) = 28.
  SET-POINTER-VALUE(helpinfo) = p-lParam.
  ContextID = GET-LONG(helpinfo, 17).
 
  /* ContextID=0 will result in the standard text
     "No help topic is associated with this item" 
     You might want to test that and return or
     replace 0 by a different (translated) ContextID
  */
  /* Was WM_HELP called for HELPINFO_WINDOW or for HELPINFO_MENUITEM ? 
     We don't want to support help for MENUITEM right now */
 
  ContextType = GET-LONG(helpinfo, 5).
  IF ContextType<>1 /* 1=HELPINFO_WINDOW */ THEN DO:
     SET-SIZE(helpinfo)=0.
     RETURN.
  END.
 
  HWND = GET-LONG(helpinfo,13).
  RUN WinHelp{&A} IN hpApi (HWND, 
                            "myhelp.hlp", 
                            8,    /* 8 = HELP_CONTEXTPOPUP */
                            ContextID,
                            OUTPUT ReturnValue).
 
  SET-SIZE(helpinfo) = 0.
 
END PROCEDURE.

So WinHelp will be called and shows a certain ContextID from "myhelp.hlp" in a little yellow popup-window, aligned to the control where your mouse was on. Great. But what ContextID, you might wonder?

ContextID's

When you write a helpfile each topic will be assigned a unique integer identifier: the ContextID. The ContextHelp button feature requires that you write a lot of topics: at worst one for each widget, at best one topic for each group if widgets (like one for each (smart)frame).
The ContextID's supplied by your help authoring tool must be mapped to the widgets or widgetgroups in the program. From the main block we already called procedure AddContextIDs. Here's the implementation:

PROCEDURE AddContextIDs :
/*-------------------------------------------------------------------
  Purpose:     map widgets to ContextID's
  Parameters:  
--------------------------------------------------------------------- */
  DEFINE VARIABLE retval AS INTEGER NO-UNDO.
  RUN SetWindowContextHelpId IN hpApi(FRAME {&frame-name}:HWND, 101, OUTPUT retval).
  RUN SetWindowContextHelpId IN hpApi(FRAME FRAME-A:HWND, 102, OUTPUT retval).
  RUN SetWindowContextHelpId IN hpApi({&window-name}:HWND, 103, OUTPUT retval).
 
  DO WITH FRAME {&frame-name}:
     RUN SetWindowContextHelpId IN hpAPi(button-1:HWND,  143, OUTPUT retval).
     RUN SetWindowContextHelpId IN hpApi(button-2:HWND,  142, OUTPUT retval).
     RUN SetWindowContextHelpId IN hpApi(fill-in-1:HWND, 144, OUTPUT retval).
     RUN SetWindowContextHelpId IN hpApi(fill-in-2:HWND, 145, OUTPUT retval).
  END.
 
END PROCEDURE.

Of course these are all widgets your program probably don't have, it is just an example. The important part is that if you assign a ContextID to a window, all frames in that window will inherit that ContextID. If you assign a ContextID to a frame, all widgets in that frame will inherit that ContextID. And so on.
That makes it possible and convenient to assign ContextID's inside the sources of SmartObjects.

Attachments

cthelp.zip : cthelp.ocx