Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Porting a legacy MFC application to MFC Feature Pack

0.00/5 (No votes)
17 Jul 2011 5  
Problems I encountered when polishing the GUI of an existing 200K LOC application.

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);
    // I could not see any effect of the specific value here
    CommonControls.dwICC = ICC_STANDARD_CLASSES;
    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 will 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 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 straightforward way to remove several calls to HideButton. (Depending on the 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 having 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 have 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 CFrameWindow. (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 are missing, but for the moment, let's not bother with that.

Instead, let's 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 control bars, now the menu has also 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 now the system menu appears 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, ...))

with:

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 the 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) bug-fix 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 the CWinApp::InitInstance call:

CPane::m_bHandleMinSize = true;

That was all! Dialog bars 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.

Anyway, 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., the 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 the 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 contained only placeholder text, which is used as the 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 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 this:

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 not now 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 caught 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 in, 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 the 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 MSDN is really lacking. As you know, if there is one thing that is worse than 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 hard disk. So I decided to download the samples again. In principle, I was on the right track, but when you follow the links given in MSDN (CMFCToolBarMenuButton -> WordPad-Sample -> Visual Studio 2008 Samples Page -> Visual C++ samples), you will arrive 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 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, some other assertions occurred 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 were 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 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 what was lacking in the available MFC classes. In my case, most problems were 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 6, 2011: Initial version. Application compiles. Menu, docking dialogs working. Toolbars broken.
  • July 18, 2011: Application runs with no known deficits.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here