Context sensitive help in dialob boxes

Hi all.

I have a problem implementing context sensitive help in dialog boxes.

I've entered a project, built with MFC which is already live and running.

The project already contain many dialog boxes, property sheets and form views, and I need to implement context sensitive help for all things that already work.

I did not want to enter each CDialog derived class and the like, and handle the WM_HELP message, so I've implemented an Hook which captures WM_HELP messages recieved from controls, and calls a Help File I've built.

It all works fine until I reach a point, where some controls have the same Child window ID, but need to show different Pop-Up messages in their tooltip window.

I know!, you should use different IDs for each control in your application, but I remind you all, that I do not have that previlege.

Many programmers worked on this project before me, and each one implemented his own Dialog boxes, and some names of controls are used over 6-8! different dialog boxes.

I thougt of taking into consideration the Template ID of the dialog itself when creating HM files and implementing the Hook, but I can't find out a common central way, to find a Dialog resource template identifier after the dialog is already created.

Did someone dealt with this thing before?

I'm pretty stressed on this manner, and I'll appreciate a good answer.
koskiaAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

cyberfrankCommented:
Hi!

I'm in same shoes too and I'll try to help You.
1.) Did You try to open some dialog box and check Help Id? Because in this case VC will automatically generate IDs.
2.) I don't understandly quit good. Why would You hook WM_HELP message? The windows mechanism will do for You, you have to only create tooltep text/context sensitive help pages in Your hlp file or chm.
In chm (HTML help) You only have to hook OnHelp() // I'll send you later the content of this function, it's one line //
and You have to hook WinHelp() function of the mainframe, and nothing else?
So, what did You tried till now?

CF
0
koskiaAuthor Commented:
Don't really get down to what you say.
I'll describe what I did so far.

Again, I'm trying to implement Context sensitive help for all my windows at once, the tooltip way.

I want to add it to an already running application, so I've build a little HOOK function which does exactly that.

SetWindowsHookEx( ... CALLWANDPROC, MyFunc ...);

LRESULT CALLBACK MyFunc(UINT nCode, WPARAM wParam, LPARAM lParam)
{
0
koskiaAuthor Commented:
Don't really get down to what you say.
I'll describe what I did so far.

Again, I'm trying to implement Context sensitive help for all my windows at once, the tooltip way.

I want to add it to an already running application, so I've build a little HOOK function which does exactly that.

SetWindowsHookEx( ... CALLWANDPROC, MyFunc ...);

{

LRESULT CALLBACK MyFunc(UINT nCode,   WPARAM wParam, LPARAM lParam)
{      
  if (nCode != MSGF_DIALOGBOX ||
  ((CWPSTRUCT*)lParam)->message !=  
    WM_HELP)
    return CallNextHookEx(s_help.m_hHook, nCode, wParam, lParam);

      CWPSTRUCT* pStruct = (CWPSTRUCT*)lParam;
      HELPINFO* pInfo = (HELPINFO*)pStruct->lParam;

      ::WinHelp(pStruct->hwnd/*pStruct->hwnd*/, s_help.m_filename, HELP_CONTEXTPOPUP,
            pInfo->iCtrlId);
      return 0;
}

that's it, it works if every control have an entirely different id.
0
Cloud Class® Course: Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

migelCommented:
Hi!
In this case you must create map table to map pair dialog ID/name +control ID to the appropriate help topic id. I think you haven`t choice.
0
koskiaAuthor Commented:
migel, How do I get the Dialog ID after it had already been created?
0
abdijCommented:
I just hope that your predeccors in the project have not used the same Window Name for the dialog.

In that case you can use the CWnd::GetWindowText(buffer,buffersize) function to get the WindowText of the dialog.
Then strcmpi that with the known list of DialogTexts and based on it call the Help file.

Is that ok for you
0
koskiaAuthor Commented:
Look, the answer is not complete. and it's an easy way out which doesn't take into consideration other windows other than dialogs (FormViews and Property sheets for instance).

Besides, when building the help file I have no knowledge of the dialog's text, and even if I did, window's text can be changed, and manipulated.

This one is harder, My question again is - Is there's a way of getting a dialog template resource at runtime ??
Is there a place where I can catch this?
I've seen for instance that a dialog keeps the resource string in a member called m_lpszResourceID or something like that ???
but ... first the string is protected, and second, it doesn't help on form views and the like.

anyone else???
0
koskiaAuthor Commented:
Adjusted points to 800
0
migelCommented:
Hi!
I do mistake when say about dialog ID. Windows do not store this info while dialog being created.
You can do this.
define your own message (let it be WM_GETDLGID) and send it to the dialog when tou receive WM_HELP msg.
your dialogs can store template ID in the constructor (i think that common base class -say CStoredIDDialog - will help you). dialogs received message mentoned above just return this ID to the caller. for form views technique can be same
0
koskiaAuthor Commented:
Thanks migel, but this is messing around to much with the application.

I have an application with 10s of dialog boxes and form views (almost an hundred), I can't change each and every one of their base classes, this is too much to ask.

I better change all the duplicated control IDs, but this can be hard and complicated too, becuase MFC uses HARDCODED defines within DoDataExchange and the like.

This is a real problem.
BTW migel, the CDialog class do saves the dialog ID given in the constructor in m_lpszTempateName member function, but doesn't help much, it's for internal use anyway.

Is there another UNIQUE identifier for a window or a dialog, which can be known before and after compilation ???
0
luisrCommented:
What I would do is derive a class from CDialog (for example CDialogWithHelp) and handle the WM_HELP message in that class. You could look for the help ID made up of the dialog ID plus the control ID. This way you could be sure that the help topic ID is unique across all the application.
0
koskiaAuthor Commented:
luisr, thanks, but ...
this is exactly what I've said, I'm trying to get the dialog ID but I can't.
I've also already tried to create a class which inherits from CDialog, but I don't have a clue how to make something generic, which catches the resource identifier of the dialog.

I don't have a problem to save all my dialog IDs in one big array, the problem is getting the IDs to build the array.
0
migelCommented:
Since you can`t get it by usual way you must change base class :-( (sorry but you really can`t give dialog ID by standard way) May be Dialog caption can help you (but for formView it don`t help)
0
koskiaAuthor Commented:
I'm still sure there is a way.

I've seen a DLL which implemented that, it was written by a company which I can't get hold of.

But because I can't see just how they've implemented it, I also can't use their DLL.

I've actually seen how a few controls with the same control ID, which sits on different dialogs and form views get different help context topics.

without changing the dialog classes at all!!!
0
cyberfrankCommented:
Hi!

Like I told You,
1.) in VC++, in property bars of dialogs (or dialog member controls, i.e. for the button on the dialog ) if You checj "context help", the VC++ will generate a resource.hm file which contains unique ID-s which will be used like a help ID, when You pusj "Help" button, or use context-sensitive curson on the dialog or one of its controls.

2.) You only have to implement in Your *App class a
void *App::WinHelp(DWORD dwData, UINT nCmd)

function which will be called in each case (regular F1 or context-sensitive help ...) and You have to decide from nCmd parameter is it context sensitive or regular help. If You wich, I can look for it.

And that's  it!!!

You only have to check in all of Your dialogs, on the property sheet, the context sensitive check box.

Wht want You do something something complicated when You can do simplier?

CF
0
koskiaAuthor Commented:
I'll try it and get back to you cyberfank, thanks.

and if you can parellely look for it too I'll appreciate it
0
cyberfrankCommented:
Ok. I'll look for it in detail and write a complete answer ;-)

CF
0
cyberfrankCommented:
Hi!

The situation is following:

When You press 'F1' or somehow invoke the help mechanism, the CWinApp::WinHelp() will be invoked in final stage, but:
If You hit F1 in some dialog box, the default dialog's help message handler will be invoked, which is following:

LRESULT CDialog::OnCommandHelp(WPARAM, LPARAM lParam)
{
      if (lParam == 0 && m_nIDHelp != 0)
            lParam = HID_BASE_RESOURCE + m_nIDHelp;
      if (lParam != 0)
      {
            CWinApp* pApp = AfxGetApp();
            if (pApp != NULL)
                  pApp->WinHelp(lParam);
            return TRUE;
      }
      return FALSE;
}

What You are looking for is in the m_nIDHelp which contains the id of the dialog (IDD_xxxxx). So, the simpliest way - because You have told, that for different dialog boxis you have same idd - that you override the CDialog's

OnCommandHelp(WPARAM, LPARAM lParam)
message handler and return's with true.

If You press 'F1' in MainWindow the MainFrame's OnHelpCommand(...) will be executed.

One solution is, the You implement in each dialog the above mentioned function and call the help for it, or You change unically help id-s (in OnHelpCommand() the lparam value) and implement in CWinApp the WinHelp() function too in which case the first DWORD dwData will be You lParam.
I hope it helped.

CF
0
luisrCommented:
If your dialog class is named CWhateverDialog, you can get the dialog template ID by using:

  CWhateverDialog::IDD

That is how the dialog template ID is passed to the base class in the constructor of the dialog class, and it is defined in the header file with something like:

  enum { IDD = IDD_WHATEVER };
0
koskiaAuthor Commented:
cyberfank, as far as you are trying to help me properly, you don't exactly understand what I want, and your mechanism couldn't work in my situtation.

I really do not want to change, even a bit, the dialog classes which are already defined in the project.

I won't go and override 100+ of CDialog's driven classes, If I can do that, I can also change each of the control IDs so there won't be any duplications.

MFC does not automatically builds an HM file when you indicate you want context sensitive help, it uses an application called MAKEHM.BAT which parses the resource.h file and build the HM file according to definitions in there.

but if 2 controls on two different dialogs, are defined in one line (have the same number), the MAKEHM.BAT won't create 2 records for them in the HM file
0
koskiaAuthor Commented:
For luisr,
I've liked your comment, only becuase the fact that it haven't crossed my mind till I saw it.

but ... The IDD parameter is not generic, and is not part of the base class CDialog or CWnd.

if it was, I could make a generic call to CWnd::IDD, but becuase in each dialog class it sits in a different offset to the start of the class, I have to know each and every dialog class name, and that's again, something I don't want to do.

read some of my former comments.
0
cyberfrankCommented:
Ok, then You can do following:
In each case - if you don't override the mentioned OnHelpCommand() the CWinApp::WinHelp(DWORD dwData, ..) function will be invoked. So, You only have to implement this function in Your *App class and You have to made a switch() in which will You decide regarding to the dwData value which help will be invoked.
Is this acceptable for You?

CF
0
cyberfrankCommented:
The dwData value is following -- like i mentioned above:

lParam = HID_BASE_RESOURCE + m_nIDHelp,

so You have to made a switch-case selection on
(dwData - HID_BASE_RESOURCE ) and the result will be You dialog Id (the corresponding CDialog::IDD)

CF
0
cyberfrankCommented:
Hi,

About hm file. You were right. Only in case when You check the dialog control's 'Help id' check box on his property page, the will VC automatically produce the hm file which is something like following:
// Microsoft Developer Studio generated Help ID include file.
// Used by Linker_CPP.rc
//
#define HIDC_OPERATION_PARAMETERS_NAME  0x8bb90403    // IDD_ASG_FILTER_OPTIONS [English (U.S.)]

....

CF
0
luisrCommented:
Another thing, that IDD parameter is used to load the dialog box template, but it is first stored in the m_lpszTemplateName with the MAKEINTRESOURCE macro. You could decode the dialog template ID from there.
0
koskiaAuthor Commented:
cyberfrank,
For some reason the Framework does not call my CMyApp::WinHelp() when I press the F1 within a dialog box, or even from a menu item.

for luisr,
I wrote exactly what you wrote several comments ago, but it doesn't help much.
m_lpszTemplateName is for internal use of MFC, it's protected, so I have to inherit a class from CDialog.
But I have a problem that I need to implement it for some form views too.
If the parameter was sitting within CWnd, I wouldn't have a problem.

0
cyberfrankCommented:
Hi!

In Your CMianFrame class add following codes in the message map:

BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
  //{{AFX_MSG_MAP(CMainFrame)
    ....
  //}}AFX_MSG_MAP
  // Global help commands
  ON_COMMAND(ID_HELP_FINDER, CMDIFrameWnd::OnHelpFinder)
  ON_COMMAND(ID_HELP, CMDIFrameWnd::OnHelp)
  ON_COMMAND(ID_CONTEXT_HELP, CMDIFrameWnd::OnContextHelp)
  ON_COMMAND(ID_DEFAULT_HELP, CMDIFrameWnd::OnHelpFinder)
END_MESSAGE_MAP()


And You will get what You want ;-)
CF
0
NickRepinCommented:
***********************
koskia,

The EASIEST way to associate the help id with the dialog controls (or even with run-time-created controls) is to use so-called context help id.

This id is passed to you with WM_HELP message: HELPINFO->dwContextId.

To use with dialogs, you have to add these ids into dialog template (rc file).

To use with run-time-created controls, use SetWindowContextHelpId().

NOTE. I'm answering in Windows programming topic area only. So if you want to give me the points, please move this Q to Windows programming area.
I'll clarify all details regarding this question there.
***************************************
0
koskiaAuthor Commented:
OK All,
seems like I've accomplished the task, and if someone should get the points, its ... luisr.

I've hade quite a hard way figuring out in my head myself.

You can get a dialog id in runtime, with the LOWORD(m_lpszTemplateName).
The problem was that the m_lpszTemplateName is first protected, and second that it's part of the class CDialog and not CWnd, thus making it harder to monitor Help requests from controls within Form Views and Dialog bars, which does not have connection to the CDialog class.

but ...
the CFormView and the CDialgBar class also keeps the m_lpszTemplateName paramter, which can be accessed at runtime using the same method.

becuase each m_lpszTemplateName of each MFC class sits in a different offset within the class, You have to exactly know to which class you're referring to and downcast the appropriate way.

The entire solution is in my next comment.
0
koskiaAuthor Commented:
// Header file

class ContextSensitiveHelp
{
private:
      ContextSensitiveHelp();

public:
      virtual ~ContextSensitiveHelp();

      static LRESULT CALLBACK CSHHookFunc(int nCode, WPARAM wParam, LPARAM lParam);
      static BOOL Initialize(TCHAR* filename);
      static ContextSensitiveHelp s_help;

      HHOOK m_hHook;
      TCHAR m_filename[256];

};

// Helper classes
class CDialogID : public CDialog {
public:
      UINT GetID() { return LOWORD(m_lpszTemplateName); }
};

class CFormViewID : public CFormView {
public:
      CFormViewID() : CFormView(1) {}
      UINT GetID() { return LOWORD(m_lpszTemplateName); }
};

class CDialogBarID : public CDialogBar {
public:
      UINT GetID() { return LOWORD(m_lpszTemplateName); }
};

//////////////////////////////////////

// Cpp file
ContextSensitiveHelp ContextSensitiveHelp::s_help;

ContextSensitiveHelp::ContextSensitiveHelp() : m_hHook(0)
{
      _tcscpy(m_filename, _T(""));
}

ContextSensitiveHelp::~ContextSensitiveHelp()
{
      if (m_hHook)
            UnhookWindowsHookEx(m_hHook);
}

LRESULT CALLBACK ContextSensitiveHelp::CSHHookFunc(int nCode, WPARAM wParam, LPARAM lParam)
{
      if (nCode != MSGF_DIALOGBOX ||
            ((CWPSTRUCT*)lParam)->message != WM_HELP)
            return CallNextHookEx(s_help.m_hHook, nCode, wParam, lParam);

      UINT dlgID = 0;
      CWPSTRUCT* pStruct = (CWPSTRUCT*)lParam;
      HELPINFO* pInfo = (HELPINFO*)pStruct->lParam;

      HWND hDialog = ::GetParent(pStruct->hwnd);
      if (hDialog == NULL)
            return CallNextHookEx(s_help.m_hHook, nCode, wParam, lParam);

      CWnd* pWnd = CWnd::FromHandle(hDialog);

      if (pWnd->IsKindOf(RUNTIME_CLASS(CDialog)))
            dlgID = ((CDialogID*)pWnd)->GetID();
      else if (pWnd->IsKindOf(RUNTIME_CLASS(CFormView)))
            dlgID = ((CFormViewID*)pWnd)->GetID();
      else if (pWnd->IsKindOf(RUNTIME_CLASS(CDialogBar)))
            dlgID = ((CDialogBarID*)pWnd)->GetID();
      else
            return CallNextHookEx(s_help.m_hHook, nCode, wParam, lParam);

      UINT nID = dlgID * 0x1000 + pInfo->iCtrlId;

      ::WinHelp(pStruct->hwnd/*pStruct->hwnd*/, s_help.m_filename, HELP_CONTEXTPOPUP,
            nID);
      return 0;
}

BOOL ContextSensitiveHelp::Initialize(TCHAR* filename)
{
      if (s_help.m_hHook)
            return FALSE;

      s_help.m_hHook = SetWindowsHookEx(WH_CALLWNDPROC, ContextSensitiveHelp::CSHHookFunc,
            AfxGetApp()->m_hInstance, GetCurrentThreadId());

      if (s_help.m_hHook)
      {
            _tcscpy(s_help.m_filename, filename);
            return TRUE;
      }
      else
            return FALSE;
}

//////////////////////////////////////

I build the HM file using the same algorithm, fatherID * 0x1000 + child ID

and it works!!!

luisr, although I knew what you've already wrote, You rang my bell, and I've tried to explore it again, so post a dummy answer and you'll receive the easiest 1000 points you've ever recieved.

and thanks
0
koskiaAuthor Commented:
last comment ...

Sorry for the indentation, I've just copied / Pasted :)
0
luisrCommented:
Thank you very much!!

And, merry x-mas and happy new year!!!
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
koskiaAuthor Commented:
Thanks for the merry x-mas (although I'm jewish).
Merry christmas for you too, and have a wonderfull here.
keep up the good work.

I hope I won't be too bad, giving you only a C, because this is actually the real help value for my question.

You haven't wrote anything about my solution to the problem.
Do you see any problems???

And thanks again
Asaf
0
luisrCommented:
It looks good to me :-)
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
System Programming

From novice to tech pro — start learning today.