Trapping messages in MFC - Whats the difference? - c++

I was just wondering what (if any) the difference was between the following two message traps in MFC for the function, OnSize(..).
1 - Via Message map:
BEGIN_MESSAGE_MAP(CClassWnd, CBaseClassWnd)
...
ON_WM_SIZE()
..
END_MESSAGE_MAP()
2 - Via afx_message:
afx_msg type OnSize(...);
They seem to be used interchangeably, which one should be used or does it depend on other factors?

Both parts are necessary to add a message handler to a class. The message map should be declared inside your class, together with declarations for any message handler functions (e.g, OnSize).
class CClassWnd : public CBaseClassWnd {
...
afx_msg void OnSize(UINT nType, int cx, int cy);
DECLARE_MESSAGE_MAP
};
afx_msg is just an empty placeholder macro - it doesn't actually do anything, but is always included by convention.
The message map is then defined in the class's .cpp file:
BEGIN_MESSAGE_MAP(CClassWnd, CBaseClassWnd)
ON_WM_SIZE()
END_MESSAGE_MAP()
These macros generate a lookup table for the class which allows messages received by the window to be dispatched to the corresponding handler functions. The ON_WM_SIZE macro allows the wParam and lParam message parameters in the WM_SIZE message to be decoded into more meaningful values for the message handler function (nType, cx, and cy in this case). MFC provides macros for most window messages (WM_LBUTTONDOWN, WM_DESTROY, etc).
You can find more information on how message maps work in MFC here on MSDN.

afx_msg is just an empty macro, it's basically just there to denote that the method is an MFC message handler for readability purposes. Even with afx_msg there you still need to have an entry in the message map.

Some of the Windows message are already handled by MFC, so in these cases you can get away with adding just the method to your derived class.
For example the CWnd class (as do many other MFC classes) already maps a few Windows messages into it's message map (i.e. ON_WM_DRAWITEM, ON_WM_MEASUREITEM, ON_WM_ENTERIDLE etc, etc).
But any other message not already mapped by MFC will need to have both a class method and an entry in the message map for it to work.

Related

Error with BEGIN_MESSAGE_MAP "a nonstatic member reference must be relative to a specific object" MFC

I am creating a breakout game using MFC, and now I am programming a level creator. For the user to choose which block is desired to create, I'm using a SplitButton. I have created the menu resource to pair with my SplitButton, but I want each one of the options in the menu to do something. Reading the documentation about how to assign a afx_msg void OnOptionSelected() to an event using BEGIN_MESSAGE_MAP I found this:
//Microsoft Documentation Code
BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
ON_COMMAND(ID_MYCMD, &CMyDoc::OnMyCommand)
END_MESSAGE_MAP()
And I did the same with my code, creating an afx_msg for each of my menu options in my menu resource
//My code in CCreateWindow.h (Inside DECLARE_MESSAGE_MAP())
afx_msg void OnToughBlockChosen();
afx_msg void OnSturdyBlockChosen();
afx_msg void OnWeakBlockChosen();
afx_msg void OnSpecialBlockChosen();
afx_msg void OnIndestructibleBlockChosen();
And thus their assignation
//My code in CCreateWindow.cpp (Inside BEGIN_MESSAGE_MAP)
ON_COMMAND(ID_CHOOSEBLOCKTYPE_TOUGHBLOCK, &CCreateWindow::OnToughBlockChosen())
ON_COMMAND(ID_CHOOSEBLOCKTYPE_STURDYBLOCK, &CCreateWindow::OnSturdyBlockChosen())
ON_COMMAND(ID_CHOOSEBLOCKTYPE_WEAKBLOCK, &CCreateWindow::OnWeakBlockChosen())
ON_COMMAND(ID_CHOOSEBLOCKTYPE_SPECIALBLOCK, &CCreateWindow::OnSpecialBlockChosen())
ON_COMMAND(ID_CHOOSEBLOCKTYPE_INDESTRUCTIBLEBLOCK, &CCreateWindow::OnIndestructibleBlockChosen())
But when I did this, the error E024 a nonstatic member reference must be relative to a specific object appears. So far I have an idea of how it can be solved (ON_COMMAND(ID_CHOOSEBLOCKTYPE_TOUGHBLOCK, &m_CreateWindow.OnToughBlockChosen())) but just I dunno if that's the way to fix it and if it is I dunno where to declare the object since I tried to do it in the .h and .cpp and none worked. I just don't know what to do now since I did the same as in the Microsoft Documentation and it explicitly stated:
The ON_COMMAND macro is used to handle command messages generated by menus, buttons, and accelerator keys.

What are the parameters for ON_LBN_SELCHANGE?

I couldn't find anywhere in MSDN an example of handling ON_LBN_SELCHANGE. So, how does the afx_msg function look like, and what parameters does ON_LBN_SELCHANGE need in the message map?
The notification handler for LBN_SELCHANGE has no parameters.
All listbox notification handlers have a common syntax (link)
Each message-map entry takes the following form:
ON_Notification( id, memberFxn )
where id specifies the child window ID of the list-box control sending the notification
and memberFxn is the name of the parent
member function you have written to handle the notification.
The parent's function prototype is as follows:
afx_msg void memberFxn( );

Using handler OnBegindrag with CTreeCtrl

I have class MyCTreeCtrl and I want to add message handler like:
void MyCTreeCtrl::OnBegindrag(NMHDR *pNMHDR, LRESULT *pResult)
{
}
What should I write between:
BEGIN_MESSAGE_MAP(MyCTreeCtrl, CTreeCtrl)
END_MESSAGE_MAP()
for creating BEGINDRAG handler.
Can't you advice me some literature about message handling in MFC? Thanks.
You should not deal with message map trying to create handlers yourself. For most messages, wizard is going to add the code for you.
For tree control in the dialog for example, you can select tree control in the resource editor and choose Add Event Handler (There are also other ways of inserting message handler using class view and properties). It is unfortunate that MS named it an event handler while in reality it is notification message handler for control specific notification code; in your case it is TVN_BEGINDRAG.
Wizard inserts appropriate entries into a message map:
ON_NOTIFY(TVN_BEGINDRAG, IDC_TREE_DRAG, &CYourDlg::OnTvnBegindragTreeDrag)
Adds declaration in .h file:
afx_msg void OnTvnBegindragTreeDrag(NMHDR *pNMHDR, LRESULT *pResult);
and implementation (definition) on .cpp file:
void CYourDlg::OnTvnBegindragTreeDrag(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
// TODO: Add your control notification handler code here
*pResult = 0;
}
In the nutshell:
Message map is the way MFC was design for the flexibility of inserting message handlers. As for any Win32 application, message handler is called from windows procedure; in MFC it is MFC window procedure that all controls are subclassed with.
The message map is the static array of AFX_MSGMAP_ENTRY structures:
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)
};
MFC window procedure gets this map, search for an entry for specific signature (nSig) and if signature of the entry matches, calls appropriate function (pfn).
Each message entry in the map uses specific macro that expands to this structure.
In your case it is ON_NOTIFY, since message is MW_NOTIFY. You will also notice the notification code TVN_BEGINDRAG.
In case you want to create message entry for a message that is not in the wizard database, or for custom message, you have couple of choices, ON_MESSAGE you can use in following manner:
Macro goes into a message map and declaration and definition that go into header and cpp files.
ON_MESSAGE(WM_CUSTOM_MESSAGE, OnCustomMessage)
LRESULT CTreeCtrlDragSampleDlg::OnCustomMessage(WPARAM wParam, LPARAM lParam)
{
return 0;
}
afx_msg LRESULT OnCustomMessage(WPARAM wParam, LPARAM lParam);
Other choices: ON_COMMAND, ON_CONTROL that map WM_COMMAND messages from window or windows common control.
More info:
http://msdn.microsoft.com/en-us/library/6d1asasd(v=vs.100).aspx for VS 2010

How to know when CWnd's Create function was called?

I've wrote a class wrapping a grid controls.
I want to init the custom grid class when it is created by calling Create function.
Is there a way that i can catch the event?
Yes, if CWnd:Create or Cwd:CreateEx is used, it is possible to catch the Win32 event with:
afx_msg int OnCreate(
LPCREATESTRUCT lpCreateStruct
);
See CWnd::OnCreate
With the corresponding mapping:
BEGIN_MESSAGE_MAP(MyGrid, CWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()
Attention: If your control is directly added on a dialog template by the designer (ie, using DDX), the function CWnd:.OnCreate() is not called.
In all cases, the following function is called at creation, after the Hwnd (handle of window) is initialized:
virtual void PreSubclassWindow( );
See PreSubclassWindow
Best regards,
Alain

Migrated MFC app from VC6 to VS2010, now OnInitDialog() not called for CPropertyPage subclass

I have been tasked with migrating our product's UI to VS2010. It is an MFC app, originally written in VC6. I have performed the following steps:
Converted the VC6 .dsp using VS2010
fixed up compile errors due to stricter VS2010 compiler
Removed all project references to VC6 mfc libs and directories
My problem is that for a dialog object (actually it's a CPropertyPage object), OnInitDialog() is not being called before other methods are. This causes an exception as OnInitDialog() needs to setup member variables.
The dialog class (CPAGEViewDefRecordFields) is subclassed from our own CValidatedPropertyPage, which in turn is derived from the MFC CPropertyPage class. The virtual method OnInitDialog() is present in all subclasses.
In the VS2010 version, when DoModal() is called on the containing property sheet, the OnInitDialog() method of the CPAGEViewDefRecordFields class is not being called. In the VC6 version, it is being called and all works ok.
In VC6, I can see that the message WM_INITDIALOG is sent, and handled in AfxDlgProc(), which in turn then calls OnInitDialog() of the dialog object.
In the VS2010 version, the first message that is processed is WM_NOTIFY, not WM_INITDIALOG.
Unfortunately I have no prior experience in MFC. What I am assuming that something has changed in the behaviour of MFC between the VC6 version and the VS2010 version. However I've not been able to find anything on the net which is similar to this.
Is there another migration step I have missed? Should I have to do something to the resources in the project when doing the migration?
I have checked that the resource is tied to the correct cpp file, as I can double click on the property page, and the IDE takes me to the correct file for the CPAGEViewDefRecordFields class.
If any of you have any ideas, I'd be very grateful.
Thanks!
Chris.
class CPAGEViewDefRecordFields : public CValidatedPropertyPage
{
public:
// Construction
CPAGEViewDefRecordFields(CWnd* pParent,
CXpViewProp* pViewProp,
CFont* pFont = NULL,
UINT nIDCaption = 0,
BOOL bSumOpRequired = TRUE,
BOOL bMinMaxRequired = TRUE,
BOOL bAllRecords = TRUE,
BOOL bShowInitSel = TRUE,
XLong lLimits = 0,
BOOL bSortSelTree = TRUE,
CXpThreshBaseLogProp* pThreshLogProp = NULL);
~CPAGEViewDefRecordFields();
// Dialog Data
//{{AFX_DATA(CPAGEViewDefRecordFields)
enum { IDD = IDD_VIEW_DEF_RECORD_FIELDS };
//}}AFX_DATA
// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CPAGEViewDefRecordFields)
virtual BOOL OnInitDialog();
//}}AFX_VIRTUAL
virtual BOOL OnSetActive();
virtual BOOL OnKillActive();
virtual void OnOK();
protected:
...
// Generated message map functions
//{{AFX_MSG(CPAGEViewDefRecordFields)
afx_msg void OnPbRemove();
afx_msg void OnPbAdd();
afx_msg void OnDblclkAvailableFields(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnDblclkSelectedFields(NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
...
UPDATE:
After some debugging, I can see what I think is the problem. However, not being an MFC programmer, I don't understand it.
I can see that OnInitDialog() is being called for the property sheet, and that a WM_INITDIALOG is then sent from the property sheet to the property pages. however, at some point in the windows internals, a WM_NOTIFY message is being sent, so this is the first message that is received, not the expected WM_INITDIALOG
I've highlighted the points on the stack trace, attached - can anyone explain why this is occuring? Is this normal behaviour - should I be catering for this in the future?
I've actually found a workaround, and that's to have an initialised flag, so that no code is executed until OnInitDialog() has been called. This is not the best solution, and I fear is more of a hack, so i would still appreciated any understanding of these messages. (I'm not an MFC programmer by trade you see!)
thanks!
OnInitDialog is called after all the dialog controls are created and just before the dialog box is displayed.
Thought I'd better answer this.
The answer came from a SO user's comment:
Your workaround with an initialized flag is the same as I would do. It looks like a tree view sends a notification when the tree view is created but your dialog isn't ready yet. You might not know when other controls do the same thing, so you need an initialized flag
The "workaround" is the only way to guarantee that the dialog is ready.