Click here to Skip to main content
Click here to Skip to main content

How to enable context sensitive help for individual controls on a FormView

By , 25 Oct 2002
 

Overview

Context sensitive help can be a powerful feature for any program that you develop. But FormView's are treated differently, with all the controls on it as a single help topic. That may not be what you want!

Implementing Context Help

To get context sensitive help to work for the controls on your FormView, you need to add the following to your CFormView derived class:

  • Map the WM_HELPHITTEST, and the WM_COMMANDHELP messages for your FormView
  • The WM_HELPHITTEST is sent by the system on clicking on an area of a window after enabling context sensitive help. The WM_COMMANDHELP message is sent when the user presses the F1 help key. We need to get our FormView to respond to these messages correctly and from there display the correct Help topic ID in the help file. The function prototypes for these messages and code looks like this:

    // in your FormViews header file
    afx_msg LRESULT OnHelpHitTest(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnCommandHelp(WPARAM wParam, LPARAM lParam);
     
    // in your FormViews MESSAGE_MAP
    ON_MESSAGE(WM_HELPHITTEST, OnHelpHitTest)
    ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
    
    // Your views class function
    
    LRESULT CYourFormView::OnHelpHitTest(WPARAM wParam, LPARAM lParam)
    {
        // message is sent to us as follows:
        // dwContext = ::SendMessage(hWnd, WM_HELPHITTEST,0,MAKELONG(point.x, point.y));
        // WPARAM = 0 ;
        // LPARAM = Point clicked
     
        CPoint p((DWORD)lParam) ;
     
        ClientToScreen(&p) ;
     
        CWnd *pWnd = WindowFromPoint(p) ;
        if (pWnd != NULL)
        {
            // convert to correct help ID, see makehelp.bat
            if (pWnd == this)
                return IDD + 0x20000 ;    
            else
                return pWnd->GetDlgCtrlID() + 0x60000 ; // see later about 0x60000
            }
        return 0 ;            // failed!
    }
    
    LRESULT    CContextHelpView::OnCommandHelp(WPARAM wParam, LPARAM lParam)
    {
        // message is sent to us as a test to see whether we make WinHelp appear
        // return non-zero if we process the message
        // wParam = 0, not used
        // lParam = 0, not used
        CWnd *pWnd = GetFocus();
        if (pWnd != NULL)
            {
            DWORD helpID;
            // convert to correct help ID
            if (pWnd == this)
                helpID = IDD + 0x20000 ;
            else
                helpID = pWnd->GetDlgCtrlID() + 0x60000 ;
            // we have a control with the focus, pop-up help for it
            AfxGetApp()->WinHelp(helpID);
            return TRUE;
            }
        return FALSE;        // let default handling process it
    }
    
  • Setup your makehelp.bat file to map IDC_* control ID's to help topics HIDC_* ones
  • By default your help file does not support by control help ID's. To get them to work you need to cause the MakeHelp.bat file map the ID's across to the correct Help ID range. I did this by adding the following lines to the MakeHelp.bat file, which should be present in your project's main directory

    @echo off
    REM -- First make map file from Microsoft Visual C++ generated resource.h
    echo // MAKEHELP.BAT generated Help Map file.  Used by CONTEXTHELP.HPJ.
    > "hlp\ContextHelp.hm"
    echo. >> "hlp\ContextHelp.hm"
    echo // Commands (ID_* and IDM_*) >> hlp\ContextHelp.hm"
    makehm ID_,HID_,0x10000 IDM_,HIDM_,0x10000 resource.h >> "hlp\ContextHelp.hm"
    echo. >> "hlp\ContextHelp.hm"
    echo // Prompts (IDP_*) >> "hlp\ContextHelp.hm"
    makehm IDP_,HIDP_,0x30000 resource.h >> "hlp\ContextHelp.hm"
    echo. >> "hlp\ContextHelp.hm"
    echo // Resources (IDR_*) >> "hlp\ContextHelp.hm"
    makehm IDR_,HIDR_,0x20000 resource.h >> "hlp\ContextHelp.hm"
    echo. >> "hlp\ContextHelp.hm"
    echo // Dialogs (IDD_*) >> "hlp\ContextHelp.hm"
    makehm IDD_,HIDD_,0x20000 resource.h >> "hlp\ContextHelp.hm"
    echo. >> "hlp\ContextHelp.hm"
    echo // Frame Controls (IDW_*) >> "hlp\ContextHelp.hm"
    makehm IDW_,HIDW_,0x50000 resource.h >> "hlp\ContextHelp.hm"
    echo. >> "hlp\ContextHelp.hm"
    echo // Commands (IDC_*) >> "hlp\ContextHelp.hm"
    makehm IDC_,HIDC_,0x60000 resource.h >> "hlp\ContextHelp.hm"
    REM -- Make help for Project CONTEXTHELP
    
    
    echo Building Win32 Help files
    start /wait hcw /C /E /M "hlp\ContextHelp.hpj"
    if errorlevel 1 goto :Error
    if not exist "hlp\ContextHelp.hlp" goto :Error
    if not exist "hlp\ContextHelp.cnt" goto :Error
    echo.
    if exist Debug\nul copy "hlp\ContextHelp.hlp" Debug
    if exist Debug\nul copy "hlp\ContextHelp.cnt" Debug
    if exist Release\nul copy "hlp\ContextHelp.hlp" Release
    if exist Release\nul copy "hlp\ContextHelp.cnt" Release
    echo.
    goto :done
    
    :Error
    echo hlp\ContextHelp.hpj(1) : error: Problem encountered creating help file
    
    :done
    echo.
    

    Adding these lines will make sure that your help files .hm file has the ID's needed to map the controls correctly. I chose the the value 0x60000 as the base range for dialog controls in the help file, if you use a different value, you need to modify the CYourFormView::OnHelpHitTest() function to return the control ID + the base value.

  • Add the help topics to your help file
  • Once you have the context sensitive help working, you need to map the topics in your help file to prove that everything is working correctly. The minimum you need to add per topic is:

    ${\footnote Button 4 topic}
    K{\footnote Button 4 topic}
    #{\footnote HIDC_BUTTON4}
    {\b Button 4 topic}\line
    This comes up when you click on button 4 for context help\par
    \line
    \page

Included at the top of this article is a working example

Updates

  • 25-10-2002 Now works with the F1 key as requested by Joel Charbonnet, example updated
  • Enjoy!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Roger Allen
Software Developer (Senior) Sirius Analytical Instruments
United Kingdom United Kingdom
Member
A research and development programmer working for a pharmaceutical instrument company for the past 17 years.
 
I am one of those lucky people who enjoys his work and spends more time than he should either doing work or reseaching new stuff. I can also be found on playing DDO on the Cannith server (Send a tell to "Maetrim" who is my current main)
 
I am also a keep fit fanatic, doing cross country running and am seriously into [url]http://www.ryushinkan.co.uk/[/url] Karate at this time of my life, training from 4-6 times a week and recently achieved my 1st Dan after 6 years.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralAnother ModificationmemberJohn Simmons / outlaw programmer16 Dec '05 - 10:11 
In the OnHelpHitTest() function, do this instead:
 
LRESULT CMyFormView::OnHelpHitTest(WPARAM wParam, LPARAM lParam)
{
	CPoint pt((DWORD)lParam) ;
	CWnd *pWnd = ChildWindowFromPoint(pt, CWP_SKIPINVISIBLE); // <<<-------------
	LRESULT nID = 0;
	if (pWnd != NULL)
	{
		if (pWnd == this)
		{
			nID = IDD;
		}
		else
		{
			nID = pWnd->GetDlgCtrlID();
		}
	}
	return nID ;            // failed!
}
 
Using ChildWindowFromPoint has two advantages:
 
1) It returns a CWnd for every control on the form, even if they're disabled, hidden, or transparent. You can also use the second parameter in the function to filter exactly what is found.
 
2) You don't need to use ScreenToClient() on the point before using it.
 

 
------- sig starts
 
"I've heard some drivers saying, 'We're going too fast here...'. If you're not here to race, go the hell home - don't come here and grumble about going too fast. Why don't you tie a kerosene rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt
 
"...the staggering layers of obscenity in your statement make it a work of art on so many levels." - Jason Jystad, 10/26/2001
 

GeneralRe: Another ModificationmemberMember 345539813 Nov '08 - 7:08 
Do you have a working samplke of the HtmlHelp in a CFormView that you could share?
I am catching the OnHelpHitTest in CFormView, and I can not for the life of me get the help to pop up.
I get an error message indicating that I have a problem with Aliases/Maps.
 
As far as I know (not a great deal I am afraid) the Alias/Maps can have any value for the #Defines.
I have simply taken the .hm file copied it, and added the #Defines.
This all workd terifically for the Dialog Boxes, but has been somewhat of a disappointment with the CFormView.
 
I have even tried catching the OnHelpHitTest and popping my own help message. Which kinda sorta works, but then the HelpResource ID is passed on the the Help API and I still get an error.
Actually, sometimes with this the help window are pops but no text is visible in the window.
 
Thanks for any advice/assistance.
GeneralRe: Another ModificationmvpJohn Simmons / outlaw programmer13 Nov '08 - 13:08 
I haven't done C++/MFC in over a year. I'm afraid I won't be of much help.
 

"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"...the staggering layers of obscenity in your statement make it a work of art on so many levels." - Jason Jystad, 10/26/2001

GeneralLittle ModificationmemberTanmay Sathe2 Jan '05 - 6:11 
I dont know why you are using so many functions to get the ID of window under the cursor, even if a simple function OnToolHitTest can do the same job.
 
OnHelpHitTest could be replaced by this simple solution :-
 
LRESULT CYourFormView::OnHelpHitTest(WPARAM, LPARAM lParam)
{
int nID = OnToolHitTest((DWORD)lParam, NULL);
if (nID != -1)
return nID + 0x60000;
else
return IDD + 0x20000;
}
 

Tanmay
GeneralRe: Little ModificationmemberMember 250608529 May '08 - 7:28 
I tried this and it worked fine for me.
 
On One View.
 
WHen I tried it on another view, it return -1 (failure) on all the
controls and I could never determine why. Both Views are derived from CFormView.
 
Robert Ramey
GeneralHtmlHelpmemberlith116 Aug '04 - 5:39 
As I use the HtmlHelp instead of the WinHelp in my application utilizing this code?
GeneralRe: HtmlHelpmemberJohn Simmons / outlaw programmer16 Dec '05 - 10:12 
It should be.
 
------- sig starts
 
"I've heard some drivers saying, 'We're going too fast here...'. If you're not here to race, go the hell home - don't come here and grumble about going too fast. Why don't you tie a kerosene rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt
 
"...the staggering layers of obscenity in your statement make it a work of art on so many levels." - Jason Jystad, 10/26/2001
 

GeneralHelp pop-up.memberAlice14 May '03 - 23:56 
The code works great!!! Thank you!!!
I have a little problem...
When user clicks with the arrow in a control, the code calls onHelpHitTest().
I just want that the help appears in a popup.
I have inserted the command
AfxGetApp()->(helpId, HELP_CONTEXTPOPUP)
in the onHelpHitTest() function and before the return...
But the programm shows the popup and then the content help...
 
Any sugestions to help meeeee.... Rose | [Rose]
 
Alice
GeneralRe: Help pop-up.memberMassimo Bovara22 Jul '03 - 5:06 
Hi,
i have a solution for you.
you try to modify the code below:
 
(in .h file)
afx_msg LRESULT OnHelpHitTest(WPARAM wParam, LPARAM lParam);
 
(in .cpp file)
ON_MESSAGE(WM_HELPHITTEST, OnHelpHitTest)
 
//function implementation
LRESULT CYourFormView::OnHelpHitTest(WPARAM wParam, LPARAM lParam)
{
CPoint p((DWORD)lParam) ;
ClientToScreen(&p) ;
CWnd *pWnd = WindowFromPoint(p) ;
 
if (pWnd != NULL) {
// convert to correct help ID, see makehelp.bat
if (pWnd == this)
return IDD + 0x20000 ;
else
return pWnd->GetDlgCtrlID() + 0x60000 ; // see later about 0x60000
}
return 0 ; // failed!
}

 
in:
 
(in .h file)
afx_msg void OnMyHelpHitTest(WPARAM wParam, LPARAM lParam);
 
(in .cpp file)
ON_MESSAGE(WM_HELPHITTEST, OnMyHelpHitTest)
 
//function implementation
void CEcdisBig::OnMyHelpHitTest(WPARAM wParam, LPARAM lParam)
{
CPoint p((DWORD)lParam) ;
ClientToScreen(&p) ;
CWnd *pWnd = WindowFromPoint(p) ;

if (pWnd != NULL) {
DWORD helpID;
if (pWnd == this)
helpID = IDD + 0x20000 ; // convert to correct help ID
else
helpID = pWnd->GetDlgCtrlID() + 0x60000 ;
AfxGetApp()->WinHelp(helpID,HELP_CONTEXTPOPUP);
}
}

 
now, recompile and run your application.
good luck.Smile | :)
GeneralRe: Help pop-up.memberMassimo Bovara24 Jul '03 - 3:35 
I have another solution for you.
 
you try code below:
 
void CContextHelpView::OnMyHelpHitTest(WPARAM wParam, LPARAM lParam)
{
// message is sent to us as follows:
// dwContext = ::SendMessage(hWnd, WM_HELPHITTEST, 0, MAKELONG(point.x, point.y));
// WPARAM = 0 ;
// LPARAM = Point clicked
 
DWORD helpID;
CWinApp *theApp = AfxGetApp();
CString helpFilePath = theApp->m_pszHelpFilePath;

helpFilePath += ">NewWin"; //NewWin is defined in ContextHelp.hpj, in the section "Window"
 
CPoint p((DWORD)lParam) ;
 
ClientToScreen(&p) ;

CWnd *pWnd = WindowFromPoint(p) ;
if (pWnd != NULL)
{
if (pWnd == this)
helpID = IDD + 0x20000 ; // convert to correct help ID
else
helpID = pWnd->GetDlgCtrlID() + 0x60000 ;
::WinHelp(pWnd->m_hWnd, helpFilePath, HELP_CONTEXT, helpID);
}
}

 
for more information download the pdf file at:
http://www.smountain.com/resource/CPPWinHelp.pdf[^]
 
good luck.
GeneralRe: Help pop-up.memberalfa_aquila29 Oct '04 - 4:43 
Hallo!,
I tried the code and it works great! Thank you!!! :DDD
I still have a problem though... that when I use the help popup, first comes the popup and then the CONTEXT help :/ how can I avoid this?
 
Thank you very much in advance!
Xia.
GeneralRe: Help pop-up.memberMassimo B.2 Nov '04 - 21:42 
D'Oh! | :doh:
Hi,
You can try in this way!
 
void CYourClass::OnMyHelp(WPARAM wParam, LPARAM lParam)
{
CString Dummy("Dummy");//it is needed to avoid the windows call to WinHelp
}
//Add WinHelp Function, if you need
void CYourClass::WinHelp(DWORD dwData, UINT nCmd)
{
CString helpFileName="HELPFILE.hlp";
CString helpFilePath = "C:\\HelpFileDir\\";
.............
.............
.............
 
helpFilePath += helpFileName;
::WinHelp( m_hWnd, helpFilePath, HELP_FINDER, 0L);
//CDialog::WinHelp(dwData, nCmd);
}
 
Good luck,Smile | :)
Massimo B.
GeneralShow help in a Popup WindowmemberTanmay Sathe2 Jan '05 - 6:32 
Hi,
You can try this. I am using it and works fine.
 
Step 1] Add BOOL bPopup in CYourApp class, initialize it to FALSE.
Step 2] Override WinHelp in CYourApp & replace it by this code
 

void CYourApp::WinHelp(DWORD dwData, UINT nCmd)
{
CWinApp::WinHelp(dwData, bPopup? HELP_CONTEXTPOPUP: nCmd);
bPopup = FALSE; // No popup until I say
}

 
Step 3] Replace CYourView::OnHelpHitTest by this one:

int CYourView::OnHelpHitTest(WPARAM, LPARAM lParam)
{
// Find the child window ID under mouse cursor
int nID = OnToolHitTest((DWORD)lParam, NULL);
if(nID != -1)
{
// Show help in popup window
((CCatApp *)AfxGetApp())->bPopup = TRUE;
return HID_BASE_COMMAND+nID;
}
else
return 0;
}

 
Roll eyes | :rolleyes:
 
Tanmay
GeneralRemoving F1 shortcutmemberManojP8 May '03 - 18:41 
Can you please tell me the way remove the shortcut key F1 so that the help of my application does not start when user presses F1 key.
 
Thanks,
 
Manoj Phirke
GeneralRe: Removing F1 shortcutmemberMassimo Bovara24 Jul '03 - 3:41 
you must remove from your code the command:
ON_COMMAND(ID_HELP, CMDIFrameWnd::OnHelp)
 
if you want other information download the file at the URL:
http://www.smountain.com/resource/CPPWinHelp.pdf[^]
 
good luck.
GeneralAn alternate hookupmemberHerbert Illfelder5 Mar '03 - 12:17 
First of all, Roger's method works and the article was very useful.Smile | :) I had only 2 biases and one minor problem I sought to address. First, if at all possible, I dislike modifying files that I do not have to (MAKEHELP.BAT) and, second, the proceedure generates lots of HID tags that I don't use. There is also a minor problem in that duplicate control ID's produce duplicate HID numbers. Turns out, there is an alternative that Microsoft provides that solves these.
 
1) Do NOT modify (or unmodify) MAKEHELP.BAT
 
2) Check the "context sensitive" checkbox on the control's properties. This will generate an HID entry in the resource.hm file. The number will be unique even if the control contains a repeated ID number. A problem remains in that you need to make sure you do not repeat the ID text as opposed to the ID number.
 
3) Use the implemention of OnHelpHitTest & OnCommandHelp except that the 0x60000 in both should be replaced with 0x80000000 + (IDD << 16) (IDD is defined in the header as being an enum equal to the dialog ID)
 
4) In the MAP section of the .hpj file, add
 
#include <..\resource.hm>
 
That's all folksSmile | :)

GeneralDoes not work for dialogsmemberSam Hobbs27 Feb '03 - 8:03 
This article is a partial answer for dialogs also but is not a complete answer for dialogs. Note that I am still using VC 5 so if MFC has been improved since then in a relevant manner then I would not know about it.
 
There is a CDialog::OnHelpHitTest but the WM_HELPHITTEST message is not sent. The WM_COMMANDHELP message is sent when (context-sensitive) help mode is active but GetFocus won't get the control that is clicked.
 
It is not possible to detect help mode using CWinApp::m_bHelpMode because it is false when the WM_COMMANDHELP message is sent. CDialog does not have m_bHelpMode member.
 
So to allow determination of help mode, a member that serves the purpose of the m_bHelpMode member (in other classes) can be created for the dialog and the member can be called m_bHelpMode. It can be set to TRUE prior to the processing of the SC_CONTEXTHELP system command and FALSE following it.
 
Then in OnCommandHelp for the dialog if help mode is active then the following can be used instead of GetFocus:
 
CPoint Point(GetMessagePos());
CWnd* pWnd = WindowFromPoint(Point);

 
Just be careful while debugging because a breakpoint prior to the WindowFromPoint will make you think it's not working when you single-step over it.

GeneralRe: Does not work for dialogsmemberHerbert Illfelder5 Mar '03 - 11:49 
Peter Moss has an article dealing with adding Context Sensitive help to Dialogs. The article is:
 
Implementing Context-sensitive Help in MFC Apps
By Peter Moss
http://www.codeproject.com/winhelp/mfchelp.asp
 
Don't know if it works with VC 5 but it does with VC 6.
GeneralRe: Does not work for dialogsmemberSam Hobbs5 Mar '03 - 12:57 
Thank you.
 
I don't know if I was clear but I have context-sensitive help working quite well for my dialog. I think with my comments it should be very easy for others to do also.
 
For my dialog, I am happy to have the default help topic be shown when the F1 key is pressed. I probably can very easily implement the Shift-F1 keys to activate context-sensitive help but I have not done that yet. Otherwise the solution I have works greate for my dialog. That is, when the "context-sensitive help" button (the one in the upper-right with the "?") is pressed and the context-sensitive help mode is active, then when a control is clicked on the context-sensitive help topic for that control is shown.

QuestionHow To Implement F1 Help - FormViewmemberJoel Charbonnet24 Oct '02 - 12:49 
The context-sensitive help works great! How do I get the F1 help to do the same? If the cursor is in a edit box and the user presses F1, I want it to retrieve the same help as if the user had used the context-sensitive help in the same edit box.
 
Any suggestions?
AnswerRe: How To Implement F1 Help - FormViewmemberRoger Allen25 Oct '02 - 0:27 
I'll have to think on this one. Sounds like I need to add a new section for the F1 key. I have an idea. I will try it out today, if it works, I'll post it.

 
Roger Allen
Sonork 100.10016
 
I have a terminal disease. Its called life!

AnswerSolutionmemberRoger Allen25 Oct '02 - 0:58 
I added the following code to the CFormView class:
 
// header file

	afx_msg LRESULT	OnCommandHelp(WPARAM wParam, LPARAM lParam);
 
// .cpp file message map

	ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
 

// .cpp file function

LRESULT	CContextHelpView::OnCommandHelp(WPARAM wParam, LPARAM lParam)
{
	// message is sent to us as a test to see whether we make WinHelp appear
	// return non-zero if we process the message
	// wParam = 0, not used
	// lParam = 0, not used
	CWnd *pWnd = GetFocus();
	if (pWnd != NULL)
	{
		DWORD helpID;
		if (pWnd == this)
			helpID = IDD + 0x20000 ;				// convert to correct help ID
		else
			helpID = pWnd->GetDlgCtrlID() + 0x60000 ;
		
		// we have a control with the focus, pop-up help for it
		AfxGetApp()->WinHelp(helpID);
		return TRUE;
	}
	return FALSE;		// let default handling process it
}
 
 
The above code worked well in my example.
 

 
Roger Allen
Sonork 100.10016
 
I have a terminal disease. Its called life!

GeneralError building .hlp filememberPJ Arends18 Jul '02 - 20:19 
There is an error in your contexthelp.hpj file. In the [map] section, you have hard coded the path to the afxhelp.hm file, and that path does not match the path I used on my machine. To make it more user friendly, please include a copy of the file with the demo and reference that copy.
 
Also, after changing the path so that it compiles, I ran the demo, and when I click on the Contextsensitve help button to get the question mark cursor, then on one of the buttons on the form, I get a "topic not found" error.
 


CPUA 0x5041
 
Sonork 100.11743 Chicken Little
 
My pet, My pet stick
Nicer than a twig
Cooler than a rock.
          (Microsoft ad)
 
Within you lies the power for good - Use it!
GeneralRe: Error building .hlp filememberRoger Allen18 Jul '02 - 23:15 
PJ Arends wrote:
I get a "topic not found" error
Is an upto date version of the help file in the release/debug directory of the app when you ran it?
 

I will also take a look at the [map] section. Its just that the .hpj file was automatically generated.
 

 
Roger Allen
Sonork 100.10016
 
If I had a quote, it would be a very good one.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 26 Oct 2002
Article Copyright 2002 by Roger Allen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid