Looking for information on the new controls in the MFC Feature Pack? Don't expect to find it in a book. Keep reading this article, on the other hand, and you'll find all the details you could want.
Contributed by Gabor Bernat Rating: / 4 October 20, 2009
Nowadays, there are a couple of fast evolving markets. IT is certainly one of them. Documenting all the modifications and extending them is hard work. There are countless hours of intensive research, writing, probing and rewriting of entire paragraphs for a technical book to appear. By the time you manage to write a high quality book, there is a high chance that technology you describe it is already outdated.
Therefore, I am not surprised that last time I searched for a book/article describing the new controls introduced by the MFC Feature Pack, I found nothing. This article will try to fill this lack. I will describe each control in depth, in code and images. To demonstrate it visually I will use the following sample you can download from the MSDN: New Controls.
Although I find the online documentation hard to decipher, what is truly interesting is that this application is well written. You can split up the controls into six main categories: Buttons, Color Controls, Masked Edit, List Controls, Shell Controls and other controls. The application will resemble this list.
We will integrate all the controls into a single dialog. To avoid a big mess, we will use property pages. There exists a class for this in the feature pack as well, named CMFCPropertyPage. This supports the display of pop-up menus on a property. This is just what we need. To use them, we need to complete a couple of steps.
First, start up the resource manager and add a new dialog. Populate it with various controls that will host the new ones. Obviously, you will not find the corresponding items in the toolbox to do so. However, use items that behave similarly. For instance, a color picker acts like a button: push it in a place and you generate an action.
Then use the add class option for the dialog. Within this, you can replace the button's definition -- for example, with a color picker, and so on. Replace the CDHtmlDialog to CMFCPropertyPage. Now we can override/extend two main functions.
The application calls the OnInitDialog when it creates the property page. The second call is to the DoDataExchange, which will help us to synchronize the data inside the property page and the ones in the memory. Use the first to initialize the objects and define their behavior. If you created the right type of object, you can leave the second function as it is. It will synchronize any specific change to the corresponding function.
You can do the same with all the property pages. Once that is done, we should add all of them inside a CMFCPropertySheet. Derive a class from this. Use the dynamic declaration scheme. Declare as members each of the property pages. Then make the constructor build the sheet as follows:
//Load the icon what we will set later for the program
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
In the initialization function we just set the icons for the application, and perhaps add an about menu item to the systems menu. The code snippet for this is self-explanatory:
// IDM_ABOUTBOX must be in the system command range.
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
With this, we added the menu item. However, selecting it at this point will not have any effect. For that, we need to catch the command sent by the system and process it. Overwrite the OnSysCommand function and set it for the incoming message.
The new name for a button's class is CMFCButton. This adds functionalities such as text alignment inside the button, combining image and text inside a button, selecting a cursor, or specifying a tool tip. Additionally you can specify modern- looking styles for the button, if you wish.
// OS supports it or not? -> initialization
if (!CMFCVisualManagerWindows::IsWinXPThemeAvailable())
{
m_bXPButtons = FALSE; // set option -> local variable
m_wndXPButtons.EnableWindow(FALSE);
}
else
{
m_wndBorder.EnableWindow(FALSE);
m_wndBorderLabel.EnableWindow(FALSE);
}
You can change this option with the help of the following static function: CMFCButton::EnableWindowsTheming. By default, this will try to use the Windows theme. The application calls this function automatically upon initialization. For demonstration purposes, we will put a checkbox inside our property page, and we will link its state change to the following function:
void CPage1::OnXpButtons()
{
UpdateData(); // Acquire the state of the check box
CMFCButton::EnableWindowsTheming(m_bXPButtons);// set
m_wndBorder.EnableWindow(!m_bXPButtons);
m_wndBorderLabel.EnableWindow(!m_bXPButtons);
RedrawWindow(); // Update the look
}
Using the CMFCButton class, we can also create some custom radio buttons. Add a button for each radio button. After you create the class from the file, you need to make sure that you call the DDX_Control function. This is going to subclass the control in the dialog box.
void CPage1::DoDataExchange(CDataExchange* pDX)
{
...
DDX_Control(pDX, IDC_RADIO1, m_btnRadio1);
...
}
Afterward, set the style of the button to semi-flat. Now we create some images to display as "checks," one for the modern alpha-blend support and one for the older systems. Set an image to the button. This will be on when it is not checked. Then set a checked image. Now we need to resize our controls to fit in.
You can also set the text now, or use the radio button's text from inside the resource view. Finally, set one of the checkboxes as checked. This is all there is to it. You can call the IsChecked() function to find out what is currently on, or just link some event handlers to your check boxes and use an internal variable of your own to follow this.
However, returning to the push button. As I just presented, you can add an image near the text image. Customize the position of these two so that they're related to each other, or disable some of it.
Add a new button to the resource property page. Modify it to the new extended one: CMFCButton. Make sure to embed the control inside the dialog. During the initialization, you can make the button transparent with the m_bTransparent member:
m_Button.m_bTransparent = TRUE;
If you do not use the systems themes, you can set three types of border: flat, semi-flat or a 3D look:
If you do not want to display the image, just set a null image pointer to the button:
m_Button.SetImage((HBITMAP) NULL);
Alternatively, if you want an image, you actually need to set two images: one for when the button is pushed (it is hot), and one for the other times. Just add/create the new bitmaps and use their resource ID.
if (afxGlobalData.bIsOSAlphaBlendingSupport)
{
m_Button.SetImage(IDB_BTN1_32, IDB_BTN1_HOT_32);
}
else
{
m_Button.SetImage(IDB_BTN1, IDB_BTN1_HOT);
}
Use the SetWindowText call to set new text, or just set none by passing an empty string:
m_Button.SetWindowText(_T(""));
You can change the position of the image if you modify the values of the two public members: m_bRightImage (left/right alignment as compared with the text) and m_bTopImage (up-down alignment as compared with the text). These are bool values. For example, in our case, we delineated three scenarios:
switch (m_nImageLocation)
{
case 0:
m_Button.m_bRightImage = FALSE;
m_Button.m_bTopImage = FALSE;
break;
case 1:
m_Button.m_bRightImage = TRUE;
m_Button.m_bTopImage = FALSE;
break;
case 2:
m_Button.m_bRightImage = FALSE;
m_Button.m_bTopImage = TRUE;
break;
}
The m_nAlignStyle will accept three enum values: center, left and right. Use this to align the text inside the button.
Whenever you make a change, it is a good idea to resize the control and invalidate it. This will ensure that the application will draw the most up-to-date state.
m_Button.SizeToContent();
m_Button.Invalidate();
You can set custom cursors. This is the shape of the mouse while it hovers over the corresponding button. You can use the system default, set a hand cursor or use one from a custom cursor file.
You can also customize a button to bring up a menu when pushed. For this you will first need a menu. Use the resource manager to create one. Create a button as well. Now we will modify this to the CMFCMenuButton type. Load the menu and set it for the button.
The menu can unfold down or to the right. You can change this behavior with the m_bRightArrow member. If you want the button to be pushed down while you make your choice from the menu, set the m_bStayPressed variable to true.
m_btnMenu.m_bRightArrow = TRUE;
m_btnMenu.m_bStayPressed = TRUE;
You may be able to push down the button itself and bring up the menu with the arrow part, or just bring up the menu regardless of where you push the button. If you want to do the former, you need to set the m_bDefaultClick variable to true, and then link a function to the button (this will be called whenever you make a choice) in the message map:
void CPage1::OnButtonMenu()
{
CString strItem;
switch (m_btnMenu.m_nMenuResult)
{
case ID_ITEM_1:
strItem = _T("Item 1");
break;
case ID_ITEM_2:
strItem = _T("Item 2");
break;
case ID_ITEM_3:
strItem = _T("Item 3");
break;
case ID_ITEM_4:
strItem = _T("Item 4");
break;
default:
if (!m_bMenuDefaultClick)
{
return;
}
strItem = _T("Default Menu Button Action");
break;
}
MessageBox(strItem);
}
Now with this knowledge, creating a check button as in the image below should be easy. It is sort of a single radio button:
The multiline text button is in fact just a simple button. Add all the text you want into it and call the SizeToContent function to turn it into a multiline button.
This is all for today. Make sure you come back next time if you are interested in context menus, color pickers and other new additions to MFC with the MFC Feature Pack. Add questions and observations here on the blog or join our DevHardware forums where you can ask our experts. I would like to ask you to rate my article as you think it deserves and until next time, Live With Passion!