65.9K
CodeProject 正在变化。 阅读更多。
Home

将旧版 MFC 应用程序移植到 MFC Feature Pack

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (41投票s)

2011年7月17日

CPOL

11分钟阅读

viewsIcon

119885

我在完善一个现有 200K LOC 应用程序的 GUI 时遇到的问题。

引言

我需要完善一个现有的 MFC 应用程序的 GUI,并决定使用 Visual Studio 2008 MFC Feature Pack。

起初,我想遵循 Marius Bancila 描述的步骤。然而,许多步骤并未按预期进行;反而出现大量编译错误和断言。由于我(以及其他人)发现文档不足,许多教程只关注演示应用程序,因此我决定让您参与我的奋斗过程。

对于谷歌搜索的用户,我包含了大多数断言消息。所有截图(出于法律原因)并非来自我正在处理的原始应用程序,而是来自 VS 2008 随附的标准 MFC WordPad 示例;因此,有时它们可能与文本不完全匹配。

移除 Win98 风格

在开始之前,实现现代 GUI 的第一步是 启用 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);

确保使用 ComCtl32.dll 的 6.0 版本

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

wordpad1_orig.png

WinXP 风格

wordpad2_xp.png

重构以预测编译错误

在新的 MFC 类中,一些接口已更改,因此如果您直接切换到新类,您将收到编译错误,并且必须盲目工作一段时间。因此,提前执行一些最小化的重构是有益的。

GetToolBarCtrl

新的 CMFCToolBar 中不提供 GetToolBarCtrl() 方法,因此必须修改一些用法。(为了更容易检测到这些问题,我引入了我自己的 MyToolBar,它派生自 CToolBar,但将 GetToolBarCtrl() 标记为已弃用。)

一些方法可以直接在 CToolBar 上调用(我猜想在早期版本的 MFC 中这是不可能的)。这些调用

CEdit* pEdit = (CEdit*) this->GetToolBarCtrl().GetDlgItem(ID_EDIT);
this->GetToolBarCtrl().GetItemRect();
this->GetToolBarCtrl().GetButtonCount();

可以替换为

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

HideButton

我找不到一种直接的方法来删除对 HideButton 的多个调用。(根据我们应用程序的配置,一些工具栏根本不可用,另一些则包含更少的项目。)

我决定为以后使用 SetNonPermittedCommands 做准备;因此,我必须一次性更改所有按钮的可见性,而不是通过单独的调用。

我的尝试是替换

m_wndToolBar->GetToolBarCtrl().HideButton(ID_FILE_PRINT,false); 
m_wndToolBar->GetToolBarCtrl().HideButton(ID_FILE_OPEN,true);

用类似这样的东西

MyToolBarBase::ItemVisibility_t visibility;
visibility[ID_FILE_PRINT] = true;
visibility[ID_FILE_OPEN] = false;
myToolBar->SetItemVisiblity(visibility);

转换本身

对于以下所有内容,我建议运行应用程序验证器以立即检测新断言。我从 Marius Bancila 描述的步骤开始。

在您的 stdafx.h 中添加新的头文件

#include <afxcontrolbars.h>

开始使用新类

CWinApp 更改为 CWinAppEx

InitInstance 中添加以下行

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

CMDIFrameWnd 更改为 CMDIFrameWndEx

这会在 CWinAppEx::GetRegSectionPath 中产生一个断言

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

InitInstance() 中添加以下内容

SetRegistryKey(_T("MyCompany"));

现在应用程序运行得更远一些,但在 CMDIClientAreaWnd::CreateTabGroup 中崩溃。

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

在此崩溃之前,已经出现了许多更多的断言和错误消息。特别是这个

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

(请勿点击 此链接,它对您没有帮助。)还有这个

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

相应的代码行是

ENSURE(str.LoadString(IDS_AFXBARRES_CLOSEBAR));

因此,又缺少了一些资源。解决方案在 MSDN 中给出

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

=> 将应用程序更改为使用 MFC 作为共享 DLL。在我的情况下,这并不是什么大问题,否则请按照上面的链接中的说明进行操作。

断言仍在继续

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!

出现在

CFrameWnd::DockControlBar(...)
pDockBar = (CDockBar*)GetControlBar(dwDockBarMap[i][0]);
ASSERT(pDockBar != NULL);

原因是 CControlBarsCToolBars 不能停靠在 CFrameWindow 中。(请参阅 social.msdn。)

=> 目前,注释掉所有形式的语句

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

=> 是的!应用程序已启动!

wordpad3_toolbarsmissing.png

好的,所有的对话框和菜单都丢失了,但目前我们先不用管它。

相反,让我们做些积极的事情,启用样式

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

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

重新启动应用程序,瞧!应用程序显示了一个酷炫的深色框架和修改过的系统菜单按钮。

wordpad4_obsidian.png

重新显示菜单栏

除了所有工具栏和控件栏之外,现在菜单也消失了。嗯,实际上,它并没有完全消失;按下 Alt-F,例如,它会再次可见,但位于窗口顶部某处。

wordpad4_obsidian_menu.png

要解决这个问题,请在 CMainFrame 中添加一个 CMFCMenuBar

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);

现在菜单是可见的(并显示新样式);系统菜单也出现在应有的位置。

wordpad5_menubar.png

将所有 CDialogBar 替换为 CPaneDialog

避免 GetControlBar

当我们替换 ToolBars 和 DialogBars 时,对 GetControlBar(IDD_MY_TOOLBAR) 的调用将开始返回 NULL 并可能导致应用程序崩溃。

因此,避免使用 GetControlBar,并可能替换

ShowControlBar(GetControlBar(IDD_MY_TOOLBAR, ...))

ShowControlBar(&m_myToolBar, ...)

重载的 ShowControlBar,它可以处理 CBasePane。在一种情况下,我也处理了 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;
}

(但后来发现该特定函数现在已过时,并由框架自动处理;见下文。)

将 CDialogBar 更改为 CPaneDialog

确保资源的 WS_VISIBLE 标志已设置(请参阅 此帖子)。

GetWindowRect 中的一个崩溃是由窗口层次结构的变化引起的。因此,我替换了

GetParent()-> GetParent()->GetWindowRect(&m_window_rect);

GetParent()->GetWindowRect(&m_window_rect);

直到发现整个块(超过 100 行)是以前程序员(试图)修复的错误,由于没有成功,它已在其他地方重做,现在是多余的。

确保对接时的最小尺寸

当对话框被拖动时,大小会变得太小。原因是 CalcDynamicLayout 方法现在不再被调用。相反,在对话框的构造函数中,调用

SetMinSize(CSize(190, 480));

重要提示:最小对话框尺寸的处理必须显式启用,例如,在 CWinApp::InitInstance 调用中

CPane::m_bHandleMinSize = true;

就这样!对话框栏现在可以正常工作了!

将 CStatusBar 更改为 CMFCStatusBar

这里,Mainfrm.h 中只需要更改一行;工作起来没有问题。

wordpad6_statusbar.png

将 CToolBar 更改为 CMFCToolBar

虽然 CMFCToolBar 已经 可以容纳许多不同的控件,但对于 CToolBar,此功能必须单独编程。因此,无法仅用 CMFCToolBar(可能需要大量单独编程)替换 CToolBar

在我的案例中,对于单个工具栏,我遇到了大量断言,因为所有组合框、编辑字段等现在都丢失了。因此,每个 GetDlgItem(IDD_MY_FANCY_CONTROL) 现在都会失败。

总之,如果您运气好,您的工具栏只显示一些按钮,那么就这样做

CToolBar 更改为 CMFCToolBar

一切编译,没有出现断言,但是——工具栏不可见。在菜单中点击它们也无济于事。对于状态栏,一切都如预期工作。

在我的案例中,原因有两方面

  • 存储工具栏状态的注册表路径已更改,并且尚未包含工具栏应显示的信息
  • 菜单尚未为(取消)显示工具栏而功能化

从菜单显示和隐藏工具栏

在原始应用程序中,为每个工具栏创建了一个菜单项。然后,此条目基本上链接到以下方法

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

此方法(以及其中提到的 OnToggleBar 方法)现在已过时。显示工具栏的相应菜单现在可以动态创建。您只需要一个(子)菜单,例如,ID 为 ID_VIEW_TOOLBAR,并在

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

OnCreate() 方法中。您可以使用 0 代替 ID_VIEW_CUSTOMIZE,如果您不允许自定义工具栏。strCustomize 是为启用的“自定义”功能显示的文本。

这里出现了一些问题,是我由于菜单本地化引起的。菜单最初只包含占位符文本,该占位符文本用作获取本地化文本的资源密钥。不幸的是,代码后台对于子菜单(“//递归尚未实现”)和动态文本似乎不起作用。作为一种变通方法,我从菜单中删除了工具栏条目;它们现在只能通过右键单击工具栏窗格来启用/禁用。

此外,工具栏和对话框现在需要有意义的(和本地化的)标题,因为菜单条目将显示相应的文本。

m_wndToolBar.SetWindowText("Fancy Toolbar");

移植现有的 CToolBars

如果您的应用程序的外观和感觉得到了更新,那么很可能许多工具栏也会被重新设计(例如,被可停靠的多行控件替换),因此以下内容可能根本不需要。

无论如何,让我们试试看,并将现有的、高级的 CToolBar 移植到 CMFCToolBar。如前所述,对于简单的工具栏,这可以立即完成。

wordpad7_maintoolbar.png

而且,新的工具提示现在也显示了

wordpad_toolbar_with_tooltip.png

不幸的是,对于自定义工具栏,情况并非如此容易。WordPad 示例中的格式工具栏现在完全搞砸了。

wordpad_broken_toolbar.png

将单个元素替换为 CMFC 类

注意:由于您的自定义工具栏实现不同,以下内容看起来会有所不同,但它应该会给您一个关于转换是否可行的提示。此外,我描述了我应用程序中进行的操作;我没有尝试移植 WordPad 工具栏。

MSDN 上还有一篇关于 在工具栏上放置控件的文章。

要在工具栏中插入一个编辑控件,现有的代码看起来像这样

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;

修改后的代码要短得多

case  ToolBar_Value::tbs_Edit : 
{
    CMFCToolBarEditBoxButton editButton(lCurrID, 0);
    m_pWinToolbar->ReplaceButton(lCurrID, editButton);
    break;
}
继承层次结构

有趣的是,以下调用仍然有效

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

原因是返回的值是 CMFCToolBarEditCtrl 类型,它继承自 CEdit。其他控件类型也存在类似的层次结构。

正确的元素数量

出现了一个问题,因为在我的案例中,自定义元素是添加到工具栏的,而在新语义中,现有按钮被替换,即必须添加占位符。我不得不更新包含 SetButtons 语句的代码。

=> 确保存在足够的占位符(具有正确的 ID)。

正确的按钮图像

对于某些按钮,图标是按程序确定的。有趣的是

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

现在不使用 myToolbar 的按钮 #3,而是第一个工具栏的图标 #3。

现在正确的方法是通过其所属的命令动态确定图像,例如

CMainFrame::OnUpdateFooUI(...)
{
    int imageIndex = GetCmdMgr()->GetCmdImage(ID_BTN_WITH_IMAGE_I_NEED, FALSE);
    ...
    myToolBar.SetButtonInfo(buttonId, ID_BTN_TO_CHANGE, state, imageIndex);
事件路由

在一个工具栏中,一旦从组合框中选择了一个项目,就会触发一个事件。事件处理程序位于工具栏类中

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

此事件现在不再被捕获。有人可能会说,上面的做法(我不同意)是一种不良实践,您应该将消息处理程序放在例如 CMainFrame 中。(我不太确定在 CMainFrame 中拥有 200 个消息处理程序的实际优势是什么,但请随意告诉我。)

一种变通方法是明确告知工具栏事件(请参阅 BCG 论坛)。

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;
}

请注意,如果 m_wndToolbar 可以处理命令,我在这里不会提前返回。原因是(出于任何原因)部分处理在工具栏类中进行(例如 OnCommand(..)),而其他部分在 CMainFrame 中进行(例如 OnUpdateCommandUI(..)。在统一之前,必须按上述方式进行路由。

关注点

MFC Feature Pack 示例

如前所述,MSDN 中的文档确实很缺乏。您知道,如果有什么东西比缺失的文档更糟糕,那就是错误的文档。我最喜欢的例子是 CMFCToolBarMenuButton

该页面显示了一个代码片段,我根本无法在硬盘上的示例中找到它。因此,我决定重新下载示例。原则上,我在正确的轨道上,但当您遵循 MSDN 中提供的链接(CMFCToolBarMenuButton -> WordPad-Sample -> Visual Studio 2008 Samples Page -> Visual C++ samples)时,您将到达一个过时的网站(日期为 2007 年 11 月),其中包含我已安装的相同示例。更好的是,Microsoft 知道这些过时的链接,请参阅 此帖子

这是 MFC Feature Pack Samples 的正确下载(相关示例在此:C:\Program Files\Microsoft Visual Studio 9.0\Samples\1033\AllVCLanguageSamples\C++\MFC\Visual C++ 2008 Feature Pack)。

其他断言

实际上,我分几次执行了转换的一部分。当我发现一些错误可以在切换到新库之前就可以预料到时,我撤消了所有操作,并在原始的、可编译的、可立即测试的应用程序中实现了修复。

在这些尝试中,出现了一些其他断言,我不想让您错过。

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

现在由...引起

this->LoadBarState("TOOLBARSTATES"); 

原因是转换前某些工具栏可见,并且这些工具栏本身尚未转换。尽管如此,配置仍然提到了那些工具栏。

=> 删除配置(.ini 文件或注册表项)以避免配置未转换的工具栏。

结论

  • 文档缺失:文档远不完整。MSDN 中的许多页面建议查找源代码(RTFC)。许多答案只能在 BCGSoft 论坛中找到。因此:如果您发现其他有益的信息,请随时在此分享
  • 自定义控件引起问题:许多问题由自定义控件引起,尤其是那些最初是为了弥补可用 MFC 类中的不足而编写的。在我的案例中,大多数问题是由我们的本地化、工具栏和工具提示类引起的。
  • 值得付出努力:尽管我还没有触及应用程序的布局(所有框、工具栏……仍然在它们原来的位置),但外观和感觉已经有了显著的改善。同时,引入的所有不稳定性都可以立即检测到(至少我希望我做到了)。

最后,感谢您阅读本文,祝您的转换项目好运!

历史

  • 2011 年 7 月 6 日:初始版本。应用程序编译。菜单、对接对话框正常工作。工具栏损坏。
  • 2011 年 7 月 18 日:应用程序运行,没有已知缺陷。
© . All rights reserved.