2
votes

I am building a test application(testApp) for a legacy MFC based application (MFC-app). I am trying to simulate mouse clicks on the MFC-app using message-passing between them. I was able to do this successfully for bring up dialog boxes from the MFC-app menu. However when I am trying to simulate a mouse click on the View of the MFC -app it doesn't seem to work.

The main question I have is whether there are any known limitations in trying to use SendMessage,PostMessage functions to communicate to a derived class of CView ? Also note that I am reusing the ON_COMMAND() handlers for handling my messages since the goal is to exercise the same handler which gets called through Menu option clicks via my TestApp. More details about what I tried and the errors I am getting:

Attempt 1.

TestApp:

::SendMessage to MFC-app's CMainFrame asking it to bring up the CView with the desired input. ----> This works

MFCApp:

CMainFrame: Retrieves a ptr to the derived class of CView (CDesignView) and its HWND handle using the approach described here: https://support.microsoft.com/en-us/kb/108587 Code used is pasted below:

  CMDIChildWnd * pChild = MDIGetActive();

  if ( !pChild )
      return -1;

  CView *pView = pChild->GetActiveView();

  if (!pView) { 
    MessageBox(_T("Could not get a handle to the design"), _T("Test2 Error"),  MB_OK);
    return -1;
}

  // Fail if view is of wrong kind
  if ( !pView->IsKindOf( RUNTIME_CLASS(CDesignView) ) ) {
      MessageBox(_T("View obtained is not of type DesignView"), _T("Test2 Error"),  MB_OK);
     return -1;
  }
CDesignView* designView = (CDesignView*)pView ; 
HWND view_hWnd = designView->m_hWnd ; 
if (!view_hWnd) {
     MessageBox(_T("designView handle could not be obtained"), _T("Test2 Error"),  MB_OK);
     return -1;
}

-------------------> At this point the code has non-NULL values for view_hWnd and designView. However when I use these for SendMessage it fails:

designView->PostMessageW(ID_DESIGN_xxx,NULL, NULL) ;

--> This does NOT work i.e no change in app as if the mesg was never sent. The ID_DESIGN_xxx handler is never called. The handler is declared as below in the CDesignView Message Map:

ON_COMMAND(ID_DESIGN_xxx , OnXXX)

(Note: I am re-using the handler which the MFCApp had already used for the menu option corresponding to this function on the CDesignView since the goal is to test it out)

-------------------->When I replaced it with a direct call to the handler as below it works:

designView->OnStarOrder() ;

However that is not the behavior I want since it involves exposing too many View handlers as public and also defeats the purpose of a test-app closely simulating the actual use model.

------------------->To further debug I also tried calling the native WM_xxx messages like below.

designView->PostMessageW(WM_CLOSE,NULL, NULL) ;

This gave an exception failure in this check : IsKindOf( RUNTIME_CLASS(CView) assertion fail.

Attempt 2

I also tried to make the TestApp send the messages to the MFCApp CDesignView instead of its own MainFrame doing it as described above. So I passed the CDerivedView handle view_hWnd from above code to TestApp using a ON_COPY message. Then TestApp does a ::SendMessage(view_hWnd,WM_CLOSE,NULL, NULL). The same error was got. This approach was tried to rule out the possibility of the CDesignView not being an active window at the time of the SendMessage. In this case, I manually click on the CView of MFCApp before letting the TestApp send the message.

None of these seem to work. Any suggestions you can provide to proceed will be of great help. Thanks in advance!

1
You should PostMessage with WM_COMMAND and the correct parameters to invoke your ID_DESIGN_xxx or whatever. See MSDN for details.Roger Rowland
Thanks Roger. Using WM_COMMAND solved the problem. For any amateur MFC programmer like me reading this, don't be misled by the title of the question. The problem had nothing to do with CView or its derived class. It really was a case of how dto directly call an ON_COMMAND handler function in your SendMessage/Postmessage.veekay
No problem, you're not the first to make that particular mistake and you won't be the last! Glad you're sorted now .... or at least moved on to the next problem ;-)Roger Rowland

1 Answers

1
votes

Concerning your main question of "whether there are any known limitations in trying to use SendMessage,PostMessage functions to communicate to a derived class of CView" the answer is no. The functions SendMessage() and PostMessage() are standard Win32 API functions for providing a message to any window whose window handle is defined.

A bit of background on MFC

Most of the MFC classes that wrap a window are derived from at some point CWnd. The CWnd class is used to wrap around a Windows window and the Win32 API used with a window. So many of the Win32 API functions that take a window handle have an analogue CWnd class method.

If you look at the declaration for CView you can see it is derived from CWnd which has a version of these two functions as methods. However the methods of CWnd have a different interface than the Win32 API version as they eliminate the window handle as the first argument.

The CWnd class declaration looks like

LRESULT CWnd::SendMessage(UINT message, WPARAM wParam = 0, LPARAM lParam = 0);

The implementation of this method within the CWnd class is probably something along the lines of:

LRESULT CWnd::SendMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
    return ::SendMessage (m_hWnd, message, wParam, lParam);
}

where m_hWnd is defined in the CWnd class as HWND m_hWnd; and is the window handle that the CWnd class is wrapping.

What is a message map

In an MFC window class file such as for a class derived from CView there will be a set of source lines similar to:

BEGIN_MESSAGE_MAP(CPCSampleApp, CWinApp)
    //{{AFX_MSG_MAP(CPCSampleApp)
    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
    ON_COMMAND(ID_APP_EXIT, OnAppExit)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

which are a set of preprocessor macros defined in an include file that allow an MFC message map be to created.

The BEGIN_MESSAGE_MAP macro looks like:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
    PTM_WARNING_DISABLE \
    const AFX_MSGMAP* theClass::GetMessageMap() const \
        { return GetThisMessageMap(); } \
    const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
    { \
        typedef theClass ThisClass;                        \
        typedef baseClass TheBaseClass;                    \
        static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
        {

which is creating a set of functions along with an array to store the various message map entries into.

The END_MESSAGE_MAP macro provides the end of the array of message map entries and looks like:

#define END_MESSAGE_MAP() \
        {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
    }; \
        static const AFX_MSGMAP messageMap = \
        { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
        return &messageMap; \
    }                                 \
    PTM_WARNING_RESTORE

The actual array elements are of a struct AFX_MSGMAP_ENTRY which looks like this:

struct AFX_MSGMAP_ENTRY
{
    UINT nMessage;   // windows message
    UINT nCode;      // control code or WM_NOTIFY code
    UINT nID;        // control ID (or 0 for windows messages)
    UINT nLastID;    // used for entries specifying a range of control id's
    UINT_PTR nSig;       // signature type (action) or pointer to message #
    AFX_PMSG pfn;    // routine to call (or special value)
};

Under the hood of MFC is a series of lookup functions that take a Windows message and then iterates over the list of Windows messages declared in the message map array to see if there is a match.

If it finds a match of the Windows message id and the appropriate wParam value, then it calls the function through the function pointer provided with the proper arguments of the interface specification for the matching message map entry.

The ON_COMMAND macro, which contains the source for an array entry looks like:

#define ON_COMMAND(id, memberFxn) \
    { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \
        static_cast<AFX_PMSG> (memberFxn) },
        // ON_COMMAND(id, OnBar) is the same as
        //   ON_CONTROL(0, id, OnBar) or ON_BN_CLICKED(0, id, OnBar)

If you look at the definition of an ON_COMMAND the windows message identifier is hard coded to WM_COMMAND so in order to trigger the ON_COMMAND entry, a Windows message must specify the WM_COMMAND message identifier.

The MFC Run Time knows that it is to call the message handler with no arguments because the signature type is AfxSigCmd_v, a value in the enumeration AfxSig which is used to inform the MFC Run Time what the interface to the message handler looks like.

If you look at the interface specification for an ON_COMMAND handler, there are no arguments so when the MFC run time calls the designated function pointer, it does not provide any arguments.

So to use the ClassView class' method SendMessage() to send a Windows message to trigger a message map entry of ON_COMMAND(ID_DESIGN_xxx , OnXXX) of a ClassView object variable of viewObject you would need to use:

viewObject->SendMessage(WM_COMMAND, ID_DESIGN_xxx, 0);

or you could use the Win32 API with:

::SendMessage (viewObject->m_hWnd, WM_COMMAND, ID_DESIGN_xxx, 0);

Another example: ON_NOTIFY_EX

Another message map macro that is different is the ON_NOTIFY_EX macro. It looks like:

#define ON_NOTIFY_EX(wNotifyCode, id, memberFxn) \
    { WM_NOTIFY, (WORD)(int)wNotifyCode, (WORD)id, (WORD)id, AfxSigNotify_EX, \
        (AFX_PMSG) \
        (static_cast< BOOL (AFX_MSG_CALL CCmdTarget::*)(UINT, NMHDR*, LRESULT*) > \
        (memberFxn)) },

and would appear in the message map as:

ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnToolTipText)

The function that will be called when this message map entry is triggered has an interface that looks like:

BOOL CPCSampleView::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult )

To trigger this you would need to send a message such as:

TOOLTIPTEXT  myToolTipInfo = {0};

//  fill in the necessary data fields to identify the tool tip properly
myToolTipInfo.hdr.idFrom = ID_CONNECT_LAN_ON;  // set the id for which tool text to fetch
myToolTipInfo.hdr.code = TTN_NEEDTEXT;   // set the notify code
// ... other fields as appropriate

viewObject->SendMessage(WM_NOTIFY, idcControlId, &myToolTipInfo);

as the specification for a WM_NOTIFY Windows message is:

wParam

The identifier of the common control sending the message. This identifier is not guaranteed to be unique. An application should use the hwndFrom or idFrom member of the NMHDR structure (passed as the lParam parameter) to identify the control.

lParam

A pointer to an NMHDR structure that contains the notification code and additional information. For some notification messages, this parameter points to a larger structure that has the NMHDR structure as its first member.

There is also an ON_NOTIFY message map macro which has a different type of signature, AfxSigNotify_v, than the ON_NOTIFY_EX macro and the message handler has a different interface than what is used for the ON_NOTIFY_EX macro. However both use the WM_NOTIFY Windows message id. It looks like:

#define ON_NOTIFY(wNotifyCode, id, memberFxn) \
    { WM_NOTIFY, (WORD)(int)wNotifyCode, (WORD)id, (WORD)id, AfxSigNotify_v, \
        (AFX_PMSG) \
        (static_cast< void (AFX_MSG_CALL CCmdTarget::*)(NMHDR*, LRESULT*) > \
        (memberFxn)) },