Introduction 

I had to polish the GUI of an existing MFC application and decided to use the Visual Studio 2008 MFC Feature Pack.

At first, I wanted to follow the procedure described by Marius Bancila. Nevertheless, many steps did not work out directly; instead I got tons of compilation errors and assertions. Since I (and others) have found that documentation is lacking and many tutorials focus on demo applications only, I decided to let you participate in my struggle.

For the googlers, i included most of the assertion messages as well. All screenshots are (for legal reasons) not taken from the original application I am working with, but from the (standard MFC) wordpad sample, distributed with VS 2008; so sometimes they might not fit 100% to the text.

Remove Win98 Style   

Before we start, the first step to a modern GUI is to enable Visual Styles:

CMyApp::InitInstance()
{
...
INITCOMMONCONTROLSEX CommonControls;
CommonControls.dwSize = sizeof (INITCOMMONCONTROLSEX);
CommonControls.dwICC = ICC_STANDARD_CLASSES; // I could not see any effect of the specific value here
InitCommonControlsEx (&CommonControls);

Ensure that version 6 of ComCtl32.dll is used: 

#pragma comment(linker, "\"/manifestdependency:type='win32'\
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 

Win98 style:

wordpad1_orig.png

WinXP Style:

wordpad2_xp.png

Refactoring to anticipate Compilation Errors

In the new MFC-classes some interfaces have changed, so if you directly switch to the new classes you will get compilation errors and have to work blindly for a while. Therefore, it is advantageous to perform some minimal refactoring beforehand.

GetToolBarCtrl  

The method GetToolBarCtrl() is not available in the new CMFCToolBar, so some usages must be modified. (To more easily detect these issues I introduced my own MyToolBar which derives from CToolBar, but has GetToolBarCtrl() marked as deprecated)

Some methods can be called directly on CToolBar (I suppose this was not possible in earlier versions of the MFC). These calls
CEdit* pEdit = (CEdit*) this->GetToolBarCtrl().GetDlgItem(ID_EDIT);
this->GetToolBarCtrl().GetItemRect();
this->GetToolBarCtrl().GetButtonCount();

can be replaced by

CEdit* pEdit = (CEdit*) this->GetDlgItem(ID_EDIT); 
this->GetItemRect();
this->GetCount() ; 

HideButton 

I could not find a straight-forward way to remove several calls to HideButton. (Depending on a configuration in our app, some toolbars will be not available at all, others will contain fewer items) 

I decided to prepare for later use of SetNonPermittedCommands; therefore I have to change the visibility of all buttons at once, not via individual calls.

My attempt is to replace

    m_wndToolBar->GetToolBarCtrl().HideButton(ID_FILE_PRINT,false); 
    m_wndToolBar->GetToolBarCtrl().HideButton(ID_FILE_OPEN,true);   
with something like this
    MyToolBarBase::ItemVisibility_t visibility;
    visibility[ID_FILE_PRINT] = true;
    visibility[ID_FILE_OPEN] = false;
    myToolBar->SetItemVisiblity(visibility);

The Conversion itself 

For all of the following, I recommend to have application verifier running to immediately detect new assertions.

I started with the procedure described by Marius Bancila:  

Add a new header to your stdafx.h  

#include <afxcontrolbars.h>  

Start using the new Classes

Change CWinApp to CWinAppEx.  

Add the following lines to InitInstance 

InitContextMenuManager();
InitShellManager();
InitKeyboardManager();
InitTooltipManager();
CMFCToolTipInfo ttParams;
ttParams.m_bVislManagerTheme = TRUE;
theApp.GetTooltipManager()->
  SetTooltipParams(AFX_TOOLTIP_TYPE_ALL,
  RUNTIME_CLASS(CMFCToolTipCtrl), &ttParams);  

Change CMDIFrameWnd to CMDIFrameWndEx

This gives an assertion in CWinAppEx::GetRegSectionPath:

f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\afxregpath.cpp(33) : Assertion failed!

 Add the following to InitInstance() 

SetRegistryKey(_T("MyCompany")); 

 Now the app runs a bit further, but crashes in CMDIClientAreaWnd::CreateTabGroup  

"CMDIClientAreaWnd::OnCreate: can't create tabs window\n"

Before this crash, already many more asserts and error messages appeared. In particular this one:

Can't load bitmap: 42b8. GetLastError() = 716 

(Don't click this link, it won't help you). And this one 

 f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\afxtabctrl.cpp(1395) : Assertion failed!

The corresponding line of code is  

ENSURE(str.LoadString(IDS_AFXBARRES_CLOSEBAR));

so again some resource is missing. The solution is given in msdn:

 "it's a known problem for statically linked projects using the feature pack" 

=> Change application to use MFC as a shared dll. In my case this was not a big deal, otherwise, follow the instructions in the link above.  

The assertions continue:

f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\winfrm2.cpp(92) : Assertion failed!
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\winfrm2.cpp(99) : Assertion failed!
 

occurs in

CFrameWnd::DockControlBar(...)
                pDockBar = (CDockBar*)GetControlBar(dwDockBarMap[i][0]);
                ASSERT(pDockBar != NULL); 
The reason is that CControlBars and CToolBars cannot be docked in a CFrameWindowEx (see social.msdn).

=> For the moment, comment out all statements of the form

DockControlBar(&m_myToolBar,AFX_IDW_DOCKBAR_TOP);
DockControlBar(&m_myDialogBar,AFX_IDW_DOCKBAR_LEFT); 

=> Yes! The Application starts! 

wordpad3_toolbarsmissing.png

Ok, all the dialogs and the menu is missing, but for the moment lets not bother with that.

Instead, lets go for something positive and enable styles :  

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if (MyBaseClass::OnCreate(lpCreateStruct) == -1)
    return -1;

  CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerOffice2007));
  CMFCVisualManagerOffice2007::SetStyle(CMFCVisualManagerOffice2007::Office2007_ObsidianBlack); 

Restart the application and - voilà! - the application shows a cool dark frame and modified system menu buttons.   

wordpad4_obsidian.png 

Re-Show the Menu Bar  

Besides all the toolbars and controlbars now also the menu disappeared. Well, actually it did not disappear completely; after pressing e.g. Alt-F it becomes visible again, but somewhere on top of the window. 

wordpad4_obsidian_menu.png

To fix this, add a CMFCMenuBar to CMainFrame.

	if (!m_wndMenuBar.Create(this))
	{
		TRACE0("Failed to create menubar\n");
		return -1;      // fail to create
	}
	m_wndMenuBar.SetPaneStyle(m_wndMenuBar.GetPaneStyle() | CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS | CBRS_FLYBY);
...
	m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
	DockPane(&m_wndMenuBar);

Now the menu is visible (and shows the new style); also the system menu appears now where it should. 

wordpad5_menubar.png

Replace every CDialogBar with a CPaneDialog

Avoid GetControlBar 

When we replace ToolBars and DialogBars, calls to GetControlBar(IDD_MY_TOOLBAR) will start to return NULL and possibly crash the application.

Therefore avoid GetControlBar and maybe replace

ShowControlBar(GetControlBar(IDD_MY_TOOLBAR, ...)) 

by

ShowControlBar(&m_myToolBar, ...)  

with an overloaded ShowControlBar that can handle CBasePane as well.  In one case I could also handle the case of an ID:  

BOOL CMainFrame::OnToggleBar(UINT nID)
{
	MyControlBarBase* pBar = GetControlBar(nID);
	bool bfReturn = false;

	if(pBar != NULL)
	{
		ShowControlBar(pBar, !pBar->IsWindowVisible(),false);
		return true;
	}

	CBasePane * pPane = GetPane(nID);
	if (pPane != NULL) {
		ShowControlBar(pPane, !pPane->IsWindowVisible(), false);
		return true;
	}

	return false;
} 

(but later it turned out that this particular function is now obsolete and handled automatically by the framework; see below).

Change CDialogBar to CPaneDialog 

Ensure that the WS_VISIBLE flag for the resource is set (see this post).

One crash in GetWindowRect was caused by a change in window hierarchy. So I replaced
GetParent()-> GetParent()->GetWindowRect(&m_window_rect);  

with

GetParent()->GetWindowRect(&m_window_rect);  

until it turned out that the whole block (more than 100 lines) was an (attempted) bugfix by a former programmer that - since it didn't work out - had been redone elsewhere and was now superfluous.

Ensure minimal Size while Docking  

When dialogs are now dragged around, the size becomes much too small. The reason is, that the method CalcDynamicLayout is now not called anymore. Instead, in the constructor of the dialog call  

SetMinSize(CSize(190, 480));

Important: The handling of the minimal dialog size, must be explicitly enabled, e.g. in CWinApp::InitInstance call

CPane::m_bHandleMinSize = true;

That was all! Dialogbars are working now!

Change CStatusBar to CMFCStatusBar  

Here just a single line in Mainfrm.h requires a change; worked without problems:

wordpad6_statusbar.png 

Change CToolBar to CMFCToolBar  

While CMFCToolBar already can host many different controls, for CToolBar this functionality had to be programmed individually. Therefore it is not really possible to just replace a CToolBar (with possibly lots of individual programming) by a CMFCToolBar.

In my case, for the individual toolbars I got tons of asserts, because all the Combo boxes, Edit Fields, etc. are missing now. So every GetDlgItem(IDD_MY_FANCY_CONTROL) fails now.

Anyways, if you are lucky, and your toolbar just shows some buttons, here we go: 

Change CToolBar to CMFCToolBar.

Everything compiles, no asserts occur but - no toolbar is visible. Clicking them in the menu also doesn't help. For the status bar everything works as expected. 

In my case the reason was twofold:

  • the registry path where the toolbar states are stored has changed, and contains no information yet that the toolbars should be shown
  • the menu is not yet functional for (un-)displaying toolbars

Show and Hide Toolbars from the Menu

In the original application, for each toolbar a menu entry was created. This entry was then essentially linked to these methods  

CFrameWnd::OnBarCheck(UINT nID); 
CFrameWnd::OnUpdateControlBarMenu(CCmdUI* pCmdUI);

This approach (and with it the method OnToggleBar mentioned above) is now obsolete. The corresponding menu for showing toolbars can instead be dynamically created. All you need is a (sub)menu with e.g. ID ID_VIEW_TOOLBAR and 

// Enable toolbar and docking window menu replacement
EnablePaneMenu(TRUE, ID_VIEW_CUSTOMIZE, strCustomize, ID_VIEW_TOOLBAR); 

in the OnCreate()-method. Instead of ID_VIEW_CUSTOMIZE you can use 0, if you do not allow customization of toolbars. strCustomize is the text to be displayed for the "Customize"-functionality if enabled. 

Some problems appear here for me, caused by the localization of the menu. The menu originally contains only placeholder text, which is used as resource key to obtain the localized text. Unfortunately, the code behind does not work for sub-menus ("//recursion not implemented yet") and seemingly also not for the dynamic texts. As a workaround, I removed the toolbar entries from the menu; they can now only be enabled / disabled via right click on the toolbar-pane.

Furthermore, the toolbars and dialogs now need a meaningful (and localized) caption, since the menu entry will display the corresponding text. 

m_wndToolBar.SetWindowText("Fancy Toolbar");

Porting the existing CToolBars

If the look and feel of your application is refreshed, then probably many toolbars will be redesigned as well (and e.g. be replaced by dockable multi lined controls), so probably the following will not be necessary at all. 

Anyways, let's give it a try and port the existing, advanced CToolBars to CMFCToolBars.  As mentioned, for simple toolbars this works immediately.wordpad7_maintoolbar.png

Also the new tooltips show up now:

wordpad_toolbar_with_tooltip.png

Unfortunately, for custom toolbars, life is not so easy. The format toolbar in the wordpad example is totally screwed up now:

wordpad_broken_toolbar.png

Replace Individual Elements by CMFC-classes

Note: the following will look different for you, depending on your custom toolbar implementation, but it should give a hint, if conversion is possible for you. Furthermore, I describe what I did in my application; I did not try to port the wordpad-toolbars. 

There is also an msdn-article on putting controls on toolbars

To insert an Edit-Control into the toolbar, the existing code looked like that

case  ToolBar_Value::tbs_Edit : 
{
rect.DeflateRect(0,2);
pWin = new CEdit();
tbvLocal.setControlWindow(pWin);
if (!((CEdit*)pWin)->Create(WS_CHILD | WS_VISIBLE | WS_TABSTOP | 
                             ES_AUTOHSCROLL |     
                             WS_BORDER |  tbvLocal.getCtlAlignStyle() ,
	                     rect, getToolbar(), lCurrID))
      {
	 TRACE0("Failed to create Editfield\n");
	 return ;
      }
((CEdit*)pWin)->SetLimitText(50);
}
break;  

The modified code is quite a bit shorter

case  ToolBar_Value::tbs_Edit : 
{
    CMFCToolBarEditBoxButton editButton(lCurrID, 0);
    m_pWinToolbar->ReplaceButton(lCurrID, editButton);
    break;
} 
Inheritance Hierarchy

Interestingly, the following call will still work:

CEdit* pEdtPosition = (CEdit*)this->GetDlgItem(ID_EDT_FOO); 

The reason is, that the returned value is of type CMFCToolBarEditCtrl which inherits from CEdit. Similar hierarchies exist for other control types.

Correct Amount of Elements  

A problem occurs, because in my case the custom elements were added to the toolbar, while in the new semantics existing buttons are replaced, i.e., placeholders must be added. I had to update the code containing SetButtons-statements.

=> Ensure that enough placeholders (with correct IDs) are present.

Correct Button Images

For some buttons, the icons are determined programmatically. Interestingly 

    myToolBar.SetButtonInfo(buttonId, ID_BTN_TO_CHANGE, state, 3);

does now not use button #3 of myToolbar, but icon #3 of the first toolbar.

The correct way now is, to dynamically determine the image, via the command it belongs to, e.g. 

CMainFrame::OnUpdateFooUI(...)
{
    int imageIndex = GetCmdMgr()->GetCmdImage(ID_BTN_WITH_IMAGE_I_NEED, FALSE);
...
    myToolBar.SetButtonInfo(buttonId, ID_BTN_TO_CHANGE, state, imageIndex);  
Event Routing

In one toolbar, an event was fired as soon as an item was picked from a combo box. The event handler was placed in the toolbar class:

BEGIN_MESSAGE_MAP( MyToolBar, MyToolBarBase )
   ON_CBN_SELENDOK(ID_LB_FOO ,OnSelChangeCmbFoo)

This event is now not catched any more. One could argue, that the above is bad practice anyway (I wouldn't agree with that), and you should instead put the message handler to e.g. CMainFrame. (I am not really sure what the advantage of having 200 message handlers within CMainFrame actually is, but feel free to tell me).

One workaround is, to explicitly inform the toolbar about the event (see bcg-forum)

BOOL
CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
	BOOL Handled = FALSE;

	if (m_wndToolBar->CommandToIndex(nID) >= 0)
		Handled |= m_wndToolBar->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

	Handled |= MyMDIFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
	return Handled;
}

Note that I am not returning early here, if m_wndToolbar could handle the command. The reason is, that (for whatever reasons) part of the handling is done in the toolbar class (e.g. OnCommand(..)) whereas other parts are done in CMainFrame (e.g. OnUpdateCommandUI(..)). Until this is unified, routing must be done as above. 

Points of Interest

MFC Feature Pack Samples

As already mentioned, the documentation in the MSDN is really lacking. As you know, if there is one thing that is worse than a missing documentation, it is a wrong one. My favorite example is CMFCToolBarMenuButton: 

The page shows a code snippet that i just could not find in the samples on my harddisk. So I decided to download the samples again. In principle, I was on the right track, but when you follow the links given in the MSDN (CMFCToolBarMenuButton -> Wordpad-Sample -> Visual Studio 2008 Samples Page -> Visual C++ samples) you will arive at an outdated site (dating 11/2007), featuring the same samples I already had installed. Even better, Microsoft knows about these outdated links, see this post.

Here is the correct download for the MFC Feature Pack Samples (the relevant samples are e.g. here C:\Program Files\Microsoft Visual Studio 9.0\Samples\1033\AllVCLanguageSamples\C++\MFC\Visual C++ 2008 Feature Pack).

Other Assertions  

Actually, I performed parts of the conversion several times. When i found that some errors could already be anticipated before switching to the new library, i rolled back everything and implemented the fix in the original, compilable and immediately testable application.

During these attempts also some other assertions occured, that I don't want you to miss.

f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\winfrm.cpp(1712) : Assertion failed! 

now caused by 

this->LoadBarState("TOOLBARSTATES"); 

The reason is that some toolbars were visible before conversion, and are themselves not converted yet. Still, the configuration states something about those toolbars.

=> Remove the configuration (either the .ini-file or the registry entry) to avoid configuration of unconverted toolbars.   

Conclusion 

  • Documentation is lacking: The documentation is by far not complete. Many pages in the msdn suggest to look up the source code (RTFC). Many answers could only be found in the BCGSoft Forum. Therefore: if you find out other beneficial information, don't hesitate to share it here
  • Custom controls cause problems: Many problems occur with custom controls, in particular such, that were originally written to work around lacks of the available MFC classes. In my case, most problems where caused by our localization, toolbar and tooltip classes.
  • It is worth the trouble: Although, I did not yet touch the layout of the application (all boxes, toolbars, ... are still where they used to be) the look and feel has noticeably improved. At the same time, all instabilities introduced could be immediately detected (at least I hope I did so)
Finally, let me say thank you for reading this article, and good luck for your own conversion project!

History  

July 6th 2011: Inital version. Application compiles. Menu, docking dialogs working. Toolbars broken.

July 18th 2011: Application runs with no known deficits 

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"