Dynamic buttons function call: alternative of BEGIN_MESSAGE_MAP
Introduction
This article's main purpose is to show another aspect of BEGIN_MESSAGE_MAP()
. In MFC, whenever there is a need to make a button control dynamic, we often get hung up on the point of defining a method for that button such that when that button is clicked, our method will be called. This article is going to show you a sample of code where I have created a dynamic button. This button is mapped with a function as per the job selected by the radio buttons.
Background
As an MFC developer, you may know that if you have to do a task in which buttons or controls are going to be created dynamically, then in you need to presume constant(Resource_ID)
for controls. You also have to specify ON_COMMAND(RESOURCE_ID, methodName)
in BEGIN_MESSAGE_MAP()
. Thus, whenever that button is clicked, the corresponding function will invoke.
However, for a more dynamic environment we need to find an alternative way in which we can re-define that method, where the method is already defined for that button-click event.
If you know .NET, then you must be familiar with delegates -- i.e. pointers to functions -- that have a significant role in defining any particular method for any control. I have tried something similar in this article, but for button-click case only. This time, I have covered only the ON_COMMAND()
aspect of MFC with my message map. I have defined my own message map and assigned value to the controls, as per need. In the future I'll try to capture all of the events of MFC so that it will become flexible for all events.
Using the code
Here I have used a trick: just like BEGIN_MESSAGE_MAP()
, I am collecting information about ID and its corresponding function. I have made my own map to collect all IDs and functions. Here is the map type created, having ID and function pointers:
typedef void (CDynButtonDlg::*fn)(int i);
typedef std::map< UINT, fn > EventMessageMap;
Using this map type, we have created a new variable. Here is the actual instance:
EventMessageMap msgMap;
Whenever we need to create a button dynamically, we'll make a corresponding entry in this map:
// To Create Dynamic Button with #define DYNAMIC_BUTTON_ID 123
m_btnDynamic.Create("Dynamic", WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,
CRect(100,150,200,200),this, DYNAMIC_BUTTON_ID);
// Make Entry in Map for this ID
msgMap[DYNAMIC_BUTTON_ID] = &CDynButtonDlg::Job1;
Now our work of changing functions is too simple. Whenever we wish to change the functionality of a function, we just need to do this:
// Make Entry in Map for this ID
msgMap[DYNAMIC_BUTTON_ID] = &CDynButtonDlg::Job2;
The only remaining question to be answered is, "How will this function be called when the button is clicked?" The answer is:
// OnCmdMsg is called when any command is fired
BOOL CDynButtonDlg::OnCmdMsg(UINT nID, int nCode,
void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
// Here we'll iterate our Map & call it's corresponding function.
EventMessageMap::iterator itTrg = msgMap.find( nID );
if(itTrg != msgMap.end())
{
fn btnM = msgMap[nID];
this->*btnM)(5);
}
return CDialog::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
I am not claiming that this is the only way to do this. If you wish to fulfill your target, then you can even put conditions in one method that you already defined in BEGIN_MESSAGE_MAP()
. I Just found a new way to accomplish this task, so I am here to share it with all of you.
What's next
I wish to get this working for all events so that it become as flexible as delegates are in .NET. I'll wait for your comments and post responses to comments I get. For major issues, I'll update this article accordingly.
HAPPY CODING. :)
History
- 21 June, 2007 -- Original version posted