Overview
Message Map Mechanism is the backbone of Event-Driven programming. In the MFC framework, DECLARE_MESSAGE_MAP()
, BEGIN_MESSAGE_MAP()
, and END_MESSAGE_MAP()
are the three macros which create the road map of messages. Please refer to MSDN articles to get a better perception of what is happening inside these three macros. With this understanding, it would be pretty simple to write a custom message handler for a control that is into some other control.
I have chosen a dialog based application using MFC to demonstrate this. Inserting a MSHFlexGrid control, I have added buttons and checkboxes into the cells of the flex grid. The button clicked (BN_CLICKED
) event has been trapped in my sample application but one could easily add other events also, following these steps.
In a dialog based MFC application, if you insert a Microsoft Hierarchical Flex Grid control, the framework will automatically add wrapper classes to invoke public methods of the control class. Now, if we wish to insert buttons in each row of column zero of the flex grid, which will appear at run-time, we have to insert the following code inside the OnInitDialog()
function of the dialog class. Before doing so, add two pointers of CButton
as private data members of the main dialog class, i.e., the CDynMsgMapDlg
class in our example (CButton *m_pCheckBox
, *m_pButtons
).
OnInitDialog()
BOOL CDynMsgMapDlg::OnInitDialog()
{
CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
..............................
..............................
int nRow = m_hflexgrid.GetRows();
m_pButtons = new CButton[m_hflexgrid.GetRows()];
m_pCheckBox = new CButton[m_hflexgrid.GetRows()];
for(int iCnt = 1; iCnt<nRow; iCnt++)
{
int nY = m_hflexgrid.GetRowPos(iCnt);
int nH = m_hflexgrid.GetRowHeight(iCnt);
int nXBt = m_hflexgrid.GetColPos(0);
int nWBt = m_hflexgrid.GetColWidth(0,0);
int nXCb = m_hflexgrid.GetColPos(2);
int nWCb = m_hflexgrid.GetColWidth(2,0);
CRect rectButton(nXBt/15, nY/15, (nXBt+nWBt)/15, (nY+nH)/15);
CRect rectCheckBox(nXCb/15, nY/15, (nXCb+nWCb)/15, (nY+nH)/15);
CString cstrTextBt;
cstrTextBt.Format("Button %d ",iCnt);
m_pButtons[iCnt].Create(
cstrTextBt, WS_CHILD|BS_PUSHBUTTON|WS_VISIBLE,
rectButton, GetDlgItem(IDC_MSHFLEXGRID_TEST),
iCnt+IDC_BUTTON_FIRST
);
CString cstrTextCb;
cstrTextCb.Format("CheckBox %d ",iCnt);
m_pCheckBox[iCnt].Create(
cstrTextCb, WS_CHILD|BS_AUTOCHECKBOX|WS_VISIBLE,
rectCheckBox, GetDlgItem(IDC_MSHFLEXGRID_TEST),
iCnt+IDC_CHECKBOX_FIRST
);
}
return TRUE;
}
With this, you will be able to see a button and a checkbox array populated in the zero'th and the second column of the grid.
Now, we have to add a click event handler for the buttons we have already created. For this, you have to add a macro ON_CONTROL_RANGE()
within the ClassWizard generated BEGIN_MESSAGE_MAP()
and END_MESSAGE_MAP()
. Here is our code snippet:
BEGIN_MESSAGE_MAP(CDynMsgMapDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_DESTROY()
ON_CONTROL_RANGE(BN_CLICKED,IDC_BUTTON_FIRST,
IDC_BUTTON_LAST, OnButtonClicked)
END_MESSAGE_MAP()
Now, add the OnButtonClicked()
prototype within the class declaration.
afx_msg void OnButtonClicked(UINT nIDbutton);
The implementation of OnButtonClicked()
may be like:
void CDynMsgMapDlg::OnButtonClicked(UINT nIDbutton)
{
MessageBox("BN_CLICKED Event trapped within 'MessageMap' Dialog");
}
After doing the steps up to this, if we try to run our application, we will see that a message box is not coming with the click event of the dynamically created buttons. The reason behind it is the dynamic creation of the buttons. As buttons and checkboxes have taken the MSHFlexGrid as their parent, the BN_CLICKED
message has to be trapped within the wrapper class of MSHFlexGrid. So, add DECLARE_MESSAGE_MAP()
, BEGIN_MESSAGE_MAP()
, and END_MESSAGE_MAP()
to trap the BN_CLICKED
event as we did earlier. Now, the last thing is we have to send a WM_COMMAND
message to the parent window of the MSHFlexGrid, i.e., the main dialog window with the same ID.
BEGIN_MESSAGE_MAP(CMSHFlexGrid, CWnd)
ON_CONTROL_RANGE(BN_CLICKED,IDC_BUTTON_FIRST,
IDC_BUTTON_LAST, OnButtonClicked)
END_MESSAGE_MAP()
void CMSHFlexGrid::OnButtonClicked(UINT nID)
{
GetParent()->SendMessage(
WM_COMMAND,
MAKELONG(nID,BN_CLICKED),
(LPARAM)(GetDlgItem(nID)->m_hWnd)
);
}
Finally, add the OnScroll()
event of the MSHFlexGrid using the ClassWizard, and insert the following code into it:
void CDynMsgMapDlg::OnScrollMshflexgridTest()
{
int nRow = m_hflexgrid.GetRows();
int nTopRow = m_hflexgrid.GetTopRow();
for(int iCnt = m_hflexgrid.GetFixedRows(); iCnt<nRow; iCnt++)
{
if(iCnt<nTopRow)
{
m_pButtons[iCnt].ShowWindow(SW_HIDE);
m_pCheckBox[iCnt].ShowWindow(SW_HIDE);
}
else
{
int nY = m_hflexgrid.GetRowPos(iCnt);
int nH = m_hflexgrid.GetRowHeight(iCnt);
int nXBt = m_hflexgrid.GetColPos(0);
int nWBt = m_hflexgrid.GetColWidth(0,0);
int nXCb = m_hflexgrid.GetColPos(2);
int nWCb = m_hflexgrid.GetColWidth(2,0);
CRect rectButton(nXBt/15, nY/15, (nXBt+nWBt)/15, (nY+nH)/15);
CRect rectCheckBox(nXCb/15, nY/15, (nXCb+nWCb)/15, (nY+nH)/15);
m_pButtons[iCnt].MoveWindow(rectButton);
m_pButtons[iCnt].ShowWindow(SW_SHOW);
m_pCheckBox[iCnt].MoveWindow(rectCheckBox);
m_pCheckBox[iCnt].ShowWindow(SW_SHOW);
}
}
}
Now, run the application, click a button inside the grid, and you will see what you want.
We need to be careful about this:
- The button and checkbox IDs should be unique.