使用 CodeProject - 应用程序的一天 - 第 2 部分,共 5 部分
使用 CodeProject 进行偶尔支持的正确编码方式。
第 2 部分,共 5 部分 - 简介
指向系列其他部分的链接
- 第 1 部分 - 构建基本应用程序并添加网格
- 第 3 部分 - 添加自定义状态栏和多线程
- 第 4 部分 - 将视图放入扩展 DLL
- 第 5 部分 - 修改系统菜单,添加 XML 配置文件支持,以及清理。
以下文本与第 1 部分相同。如果您尚未阅读该文章,那么本文对您来说将毫无用处,所以请务必先补上。我们在那儿等您。如果您已阅读第 1 部分文章,可以 跳过这些介绍部分。
本文系列是我“我们实际使用的代码”系列文章的又一篇。这里没有不必要的理论讨论,没有技巧阐述,也没有因为我独立思考出了所有东西而沾沾自喜。这只是我为了支撑我们的一个应用程序而做的一些事情。本文中的大部分内容都基于我从 CodeProject 获取的其他代码,以下内容描述了我正在积极开发的一个项目的基本情况,以及我如何整合从 CodeProject 获取的文章和帮助。
抱怨
我已在 CodeProject 注册六年多了(截至本文撰写时),并逐渐发现了一些关于文章的令人担忧的趋势。首先,文章作者倾向于发布一篇文章,随着时间的推移,作者基本上就放弃了这篇文章,而回复提问的人要么得不到作者的任何回复,要么得到类似“我不再编写此/彼语言了”的回应。让我们面对现实吧,您也不能责怪他们。我使用的许多文章都有三四年了,我理解程序员需要前进,这通常意味着完全放弃旧代码。
另一方面是那些下载给定文章关联的源代码和示例的人。很多时候,有人会在文章中发布一个问题,这个问題与文章本身毫无关系,但主题与文章的某个方面有关。例如,我曾发布了一篇关于动态构建菜单的文章。最近,有人在那篇文章中留言,询问如何为他们动态构建的菜单添加 winhelp。还有些人遇到了文章中(真实或想象的)问题,并期望别人为他们修复。这些人真的惹恼我了。毕竟,我们在这里都是程序员。
那么,本文的要点是什么?
本文的全部目的是说明在过去六年里,我从 CodeProject 获取的代码片段、类和技术在现实世界中的应用,包括为了适应我有时奇特的需求而进行的变通。很多时候,我会使用 VC++ 论坛来提问,以帮助我理解一篇文章,或者为我自己的使用而修改文章的代码。
假设
本文的原始版本最初是一个详细的教程,描述了如何使用 IDE 以及其他类似的东西。过了一段时间,我意识到这给文章的篇幅造成了巨大的开销。除此之外,我开始对整个事情感到厌烦,而且我清楚地看到我的写作质量因此开始下降。
唯一的解决办法就是重新开始,并假设您(用户)对 VS2005 IDE 具有实际的操作知识,尤其是在创建 VC++/MFC 应用程序方面。这样,我们可以谈论更重要的事情,而不是受困于您应该已经知道的内容。我还假设您对 MFC 有相当的实际操作知识。我并不是说您必须是专家,但我假设您可以在 MFC 项目中进行操作,而不会被 CMainFrame
的复杂性所困扰。
其他
在整篇文章中,您会找到“编码注意事项”。这些只是描述了我编码时的做法以及这样做的原因。它们绝非想象中的要求,但它们通常涉及代码的可读性和可维护性。我相信你们中的许多人都有自己的做事方式,但请尽量减少关于这些问题的评论。毕竟,这篇文章与风格无关。
编写完整的演示应用程序的总过程只需要大约一个小时(如果您提前知道所有步骤)。写这一系列文章花费了我好几天时间,所以不要被它的长度吓倒。
本文的 HTML 和图像包含在项目下载中,但不包含漂亮的 CodeProject 格式。如果您能在心理上处理好这一点,您可以直接参考此 HTML 文件继续您的编程。
最后,我知道有些人会因为是我写的文章就直接给我的内容打 1 分。我请求您成熟、专业地投票,将您的政治倾向限制在“肥皂箱”上。请记住,您投票的是文章,而不是作者。
我们已经完成了什么
在本系列文章的第一部分,我们完成了创建 MFC SDI 应用程序的步骤,并通过添加 MFC Grid Control 使视图更加有趣。在第 2 部分,我们将创建一个可以切换主窗格视图的平面分割窗口。
添加带可切换视图的分割窗口
在 MFC 应用程序中添加分割窗口实际上非常简单,所有工作都在 CMainFrame 类中完成。当然,有一篇 MSDN 文章描述了添加分割窗口的过程,但我讨厌为了弄清楚如何做某事而追逐链接,我打赌您也一样。所以,为了方便起见,我们将跳过基本的分割窗口,直接进入我们真正想要的——平面分割窗口。
在我的实际应用程序中,我只需要一个水平分割窗口,所以本讨论仅限于此要求。此外,我从 Marc Richarme 的 平面分割窗口 文章开始,然后从 Dan Clark 的 多视图分割窗口 文章中添加了大部分代码,以获得可切换视图的功能。这是使用 CodeProject 上的两篇独立文章创建一个专用类的完美示例。由于结合了这两篇文章的代码,您最好使用我在示例项目中提供的代码,除非您只是想自己亲身体验一遍。
在第 1 部分,我们将 MFC Grid Control 添加到了 CSDIMultSpliView
类(由应用程序向导创建)中。虽然这很好,但我们要变得更花哨一些,加入可切换视图。为此,我们需要创建我们将要切换的视图。
创建新视图类 - CPrimaryView
此视图将用于显示网格。
- 在“解决方案资源管理器”窗格中右键单击 SDIMultiApp1 项目项,然后在上下文菜单中选择 添加 | 类...
- 在随后的“添加类”对话框中,在“类别”树(对话框左侧)中选择 *MFC*,然后在“模板”列表中(对话框右侧)选择 *MFC 类*。单击“添加”。
- 在下一个对话框中,指定类名(本示例使用
CPrimaryView
),并选择基类。对于我们的示例,我们将使用CView
。单击“完成”。

- 将与网格控件相关的所有代码从
CSDIMultiSplitView
类移动到CPrimaryView
类。您应该使用 IDE 在此类中添加适当的覆盖。如果您需要详细说明,请参阅本文系列的第 1 部分。简而言之,您需要覆盖OnInitialUpdate
和OnCmdMsg
,并为OnSize
和OnEraseBkgnd
添加消息处理程序。只需将代码从CSDIMultiApp1View
中生成的函数复制到此类中。 - 可选步骤 - 从 CSDIMultiSplit 类中删除网格控件代码。我们不需要在那里放它,因为该类将简化为一个占位符视图,用于可切换视图。在本文提供的示例应用程序中,我只是将网格控件代码用
ifdef
括起来,这样在编译应用程序时就不会包含它。
创建新视图类 - CSecondaryView
这将是一个简单的 GDI 视图,其中包含报告形式的文本(实际上,它将是一个简单的线条集合,在 for
循环中创建和显示)。此视图也将支持打印(正如您很快会发现的,这并不容易)。

这是 OnDraw()
函数的一个新版本,它使视图更具趣味性。不要因为我们在屏幕上显示了大量线条而感到惊讶,因为我们将使用它来测试我们稍后将添加的打印功能。
void CSecondaryView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); CFont docFont; CFont* pOldFont; // construct and select our font BuildFont(pDC, &docFont, 11, false, false); pOldFont = pDC->SelectObject(&docFont); // Get a baseline lineheight value. We add 2 pixels to the calculated // lineheight so we have a little white space betwen lines of text. CString sText = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; int nLineHeight = pDC->GetTextExtent(sText).cy + 2; // set our x position int nMargin = 10; // limit how many lines we draw int nMaxLines = 70; // draw the lines for (int i = 1; i <= nMaxLines; i++) { sText.Format("This is line number %02d of %d", i, nMaxLines); } pDC->SelectObject(pOldFont); }
为了支持所需的字体,这里是您必须添加到类中的 BuildFont
函数。更敏锐的用户可能会认出这个函数就是我们在 CFlatSplitterWnd
类中使用的那个。
BOOL CSecondaryView::BuildFont(CDC* pDC, CFont* pFont, int nFontHeight, bool bBold, bool bItalic) { nFontHeight = -MulDiv(nFontHeight, pDC->GetDeviceCaps(LOGPIXELSY), 72); CString sFontName = _T("Arial"); int nWeight = (bBold) ? FW_BOLD : FW_NORMAL; BYTE nItalic = (bItalic) ? 1 : 0; return pFont->CreateFont(nFontHeight, 0, 0, 0, nWeight, nItalic, 0, 0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, sFontName); }
创建新视图类 - CInfoView
按照上述相同步骤,创建另一个名为 CInfoView
的新视图类,同样,将基类设置为 CSrollView
。

这里有一些代码可以使视图看起来更有趣。此代码与我们在 CSecondaryView
类中使用的代码之间的唯一区别在于我们在屏幕上显示的线条数量。由于我们不打算使此视图可打印,因此我们不需要那么多线条来测试视图。
void CInfoView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); CFont docFont; CFont* pOldFont; // construct and select our font BuildFont(pDC, &docFont, 11, false, false); pOldFont = pDC->SelectObject(&docFont); // Get a baseline lineheight value. We add 2 pixels to the calculated // lineheight so we have a little white space betwen lines of text. CString sText = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; int nLineHeight = pDC->GetTextExtent(sText).cy + 2; // set our x position int nMargin = 10; // limit how many lines we draw int nMaxLines = 20; // draw the lines for (int i = 1; i <= nMaxLines; i++) { sText.Format("This is line number %02d of %d", i, nMaxLines); } pDC->SelectObject(pOldFont); }
最后,也将 BuildFont()
函数的副本放入此类。
继续查看解决方案资源管理器窗格中的相应文件(或者类视图窗格,如果您更喜欢的话)。要完成此视图的基本功能,只需在 OnDraw
中添加一些代码即可填充视图。这将有两个目的——在我们在此视图和网格视图之间切换时提供即时且免费的视觉反馈,并为我们在文章稍后添加打印功能做好准备。现在,这是一个新的 OnDraw
函数。
创建新视图类 - CSecondaryView
按照上述相同步骤,创建另一个名为 CSecondaryView 的新视图类。
创建新视图类 - CInfoView
按照上述相同步骤,创建另一个名为 CInfoView 的新视图类。
这里有一些代码可以使视图看起来更有趣。
实现带可切换视图支持的分割窗口
我们实际应用程序的任务之一是提供一个拆分视图,允许用户在另一个窗口中查看特定类型的数据。唯一真正的要求是分割条始终可见,并且它水平分割主窗口。为了简洁和将所有内容保持在预期的范围内,我们将忽略其他类型的拆分以及水平和垂直拆分组合相关的问题。
除了上述要求外,我还要自己想办法实现它。在 CodeProject 上浏览了一番后,我从 Marc Richarme 的 FlatSplitterWnd 类中找到了一个不错的起点。本质上,这个类提供了一个始终可见的平面分割窗口,而我的工作是让它变得漂亮。我选择将分割条本身设置为黄色,并在条上写上“Info”字样,并加上两个向下的箭头。这样做的目的是方便查看。废话不多说,以下是我为实现此功能所遵循的步骤。
- 所以,首先我们将 CFlatSplitterWnd 文件添加到我们的项目中。再次,我倾向于将来自外部源的文件放在自己的文件夹中(是的,即使像这种情况一样修改它们),所以下载 FlatSplitterWnd 文章的源代码后,将 FlatSplitterWnd.CPP 和 FlatSplitterWnd.H 文件解压到您选择的文件夹(我将其放在 \SDIMultiSplit\FlatSplit 中)。由于我们在添加 MFC Grid Ctrl 时执行了类似步骤,因此我认为没有理由再次演示如何将文件添加到项目中。只需在“解决方案资源管理器”中右键单击 SDIMultiSplit 项目,在上下文菜单中单击 添加 | 现有...,然后浏览到您放置 FlatSplitterWnd 文件的文件夹,然后单击“添加”。
- 如果您像我一样将这些文件放在自己的文件夹中,您必须在项目设置的附加包含目录中添加该文件夹。请确保为所有配置都这样做。
- 现在我们已经将文件放在我们想要的位置,让我们看看我为实现分割条的视觉样式所做的更改。打开 FlatSplitterWnd.CPP,并查看该类的构造函数。
CFlatSplitterWnd::CFlatSplitterWnd() { m_cxSplitter = m_cySplitter = 3 + 1 + 1; m_cxBorderShare = m_cyBorderShare = 0; m_cxSplitterGap = m_cySplitterGap = 3 + 1 + 1; m_cxBorder = m_cyBorder = 1; }
由于我们需要为条形文本腾出空间,因此我们需要使条形更高,所以我将上面的代码更改为此。
CFlatSplitterWnd::CFlatSplitterWnd() { // we need our splitter bar to be really wide/tall to // support the text - jms m_cxSplitter = m_cySplitter = 15 + 1 + 1; m_cxBorderShare = m_cyBorderShare = 0; m_cxSplitterGap = m_cySplitterGap = 15 + 1 + 1; m_cxBorder = m_cyBorder = 1; // we need this for swappable views m_bFirstView = true; }
- 接下来,我们需要修改 OnDrawSplitter 函数。这是关于分割条颜色/样式的所有精彩之处。这是原始函数。
void CFlatSplitterWnd::OnDrawSplitter(CDC* pDC, ESplitType nType, const CRect& rectArg) { // Let CSplitterWnd handle everything but the border-drawing if((nType != splitBorder) || (pDC == NULL)) { CSplitterWnd::OnDrawSplitter(pDC, nType, rectArg); return; } ASSERT_VALID(pDC); pDC->Draw3dRect(rectArg, GetSysColor(COLOR_BTNSHADOW), GetSysColor(COLOR_BTNHIGHLIGHT)); }
我的修改的关键在于 ESplitType 参数。有四种可能的值,但我们只对其中一种感兴趣——splitBar。下面显示的 OnDrawSplitter 函数的修改版本包含足够的注释,无需我再做进一步的描述。
if((nType != splitBorder && nType != splitBar) || (pDC == NULL)) { CSplitterWnd::OnDrawSplitter(pDC, nType, rectArg); return; } ASSERT_VALID(pDC); switch (nType) { case splitBorder : { pDC->Draw3dRect(rectArg, GetSysColor(COLOR_BTNSHADOW), GetSysColor(COLOR_BTNHIGHLIGHT)); } break; case splitBar : { // we need to get the window rect so we can center our // text CRect wndRect; GetWindowRect(&wndRect); // get ready to draw our yellow rectangle CRect tempRect = rectArg; // draw the 3D rectangle (do I really need to do this?) pDC->Draw3dRect(rectArg, GetSysColor(COLOR_BTNSHADOW), GetSysColor(COLOR_BTNHIGHLIGHT)); CFont docFont; CFont* pOldFont; CBrush newBrush; CBrush* pOldBrush; // make the brush yellow and select it newBrush.CreateSolidBrush(RGB(255,255,0)); pOldBrush = pDC->SelectObject(&newBrush); // construct and select our font BuildFont(pDC, &docFont, 9, false, false); pOldFont = pDC->SelectObject(&docFont); // build our title text CString sTitle = "INFO"; CSize sz = pDC->GetTextExtent(sTitle); // make our rectangle 1 pixel smaller on all sides tempRect.DeflateRect(1,1,1,1); // calculate start position for text int xPos = (int)((wndRect.Width() - sz.cx) * 0.50); // make the text black, and the background (for drawing // text) transparent pDC->SetTextColor(RGB(0,0,0)); pDC->SetBkMode(TRANSPARENT); // draw our rectangle (uses default black pen) pDC->Rectangle(&tempRect); // free the brush resources pDC->SelectObject(pOldBrush); newBrush.DeleteObject(); // make sure our drawing rectangle is large enough to // accomodate the text AND the arrowheads, and draw the // text if we can bool bCanDrawText = (xPos - 30 > tempRect.left); if (bCanDrawText) { pDC->TextOut(xPos, tempRect.top, sTitle); } // free the font resource pDC->SelectObject(pOldFont); // if we have room to draw the text, then we have room // for the arrowheads as well if (bCanDrawText) { // create a brush for the down-arrow heads newBrush.CreateSolidBrush(RGB(0,0,0)); pOldBrush = pDC->SelectObject(&newBrush); // adjust the rectangle tempRect.DeflateRect(0,3,0,4); CPoint pts[4]; // draw the down-pointing arrowheads for (int i = 0; i <=1; i++) { int x = (i == 0) ? tempRect.Width() - sz.cx - 30 : tempRect.Width() + sz.cx + 15; int y = tempRect.top + 2; x = (int)(x * 0.50); pts[0] = CPoint(x, y); pts[1] = CPoint(x + 10, y); pts[2] = CPoint(x + 5, y + 5); pts[3] = CPoint(x, y); pDC->Polygon(pts, 4); } // free our resources pDC->SelectObject(pOldBrush); newBrush.DeleteObject(); } } break; }
编码注意事项 此版本的 CFlatSplitterWnd 仅支持绘制水平分割条。在本系列文章的后面,我们将尝试解决这个问题。 我还添加了一个函数来构建我们需要的字体。为了尽可能保持 OnDrawSplitter() 函数的整洁,我将其代码移到了自己的函数中,如下所示。
//---------------------------------------------------------------------- // Added this function to build a suitable font for the INFO text - jms //---------------------------------------------------------------------- BOOL CFlatSplitterWnd::BuildFont(CDC* pDC, CFont* pFont, int nFontHeight, bool bBold, bool bItalic) { // set the font properties CString sFontName = _T("Arial"); int nWeight = (bBold) ? FW_BOLD : FW_NORMAL; BYTE nItalic = (bItalic) ? 1 : 0; nFontHeight = -MulDiv(nFontHeight, pDC->GetDeviceCaps(LOGPIXELSY), 72); // create the font return pFont->CreateFont(nFontHeight, 0, 0, 0, nWeight, nItalic, 0, 0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, sFontName); }
- 现在,我们将为分割窗口添加可切换视图的支持。对于此功能,我修改了 CFlatSplitterWnd 类,以包含 CodeProject 上另一篇文章的必要代码 - Dan Clark 的 Unlimited number of switchable views within a Splitter window。
由于我们只是从另一篇文章复制/粘贴到我们现有的分割窗口类中,因此无需实际使用 Dan 文章中的任何文件。我们将只讨论我复制/粘贴的函数以及我对这些函数的更改。当然,您也可以选择将此类派生自 CFlatSplitterWnd(反之亦然),但我个人不喜欢太多文件,更不用说耗费 VS2005 的处理能力了(您拥有的文件/类越多,IDE 执行某些函数(如更新 intellisense)所需的时间就越长)。
AddSwitchableView 函数是从 CMainFrame 的 OnCreateClient() 函数调用的,它为每个您想使其可切换的视图调用一次。当我第一次查看这段代码时,我认为可以进行一些细微的改进,所以这些函数与 Dan 的文章不完全匹配。原始代码的更改已在注释中注明。
/////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// //// Replaceable Views //// //// Source - copied from CodeProject article by Dan Clark //// https://codeproject.org.cn/splitter/DanCMultiViewSplitter.asp ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------ // Adds a view - jms version implements the following changes from the // original code. // // 1) I think a global variable that indicates the first view works // better than having the calling function determine the condition. // I default this variable to true in the constructor. // // 2) Eliminating the isFirstView parameter also allowed me to eliminate // the original altID from the parameter list. // // The two changes described above resulted in a shorter parameter list // and cleaner code. //--------------------------------------------------------------------- bool CFlatSplitterWnd::AddSwitchableView(CRuntimeClass* pView, CCreateContext* pContext, CRect& size, UINT viewID) { CWnd* pWin = (CWnd*) pView->CreateObject(); DWORD style = WS_CHILD; if (m_bFirstView) { style |= WS_VISIBLE; m_bFirstView = false; } pWin->Create(NULL, NULL, style, size , this, viewID, pContext); views[pWin] = viewID; return true; }
SwitchView() 函数实际执行视图切换操作。我更改了大部分注释,并对旧视图的窗口 ID 设置做了一个小的更改。
//---------------------------------------------------------------------- // Hides one view and shows another. // //Note from jms - notice that any splitter pane can hold swappable views. //---------------------------------------------------------------------- bool CFlatSplitterWnd::SwitchView(UINT id, int paneRow, int paneCol) { CView* pOldView = (CView*) GetPane(paneRow, paneCol); // get current // view // if we don't have an old view set, get out. // if you assert here, you haven't properly initilaized the splitter // window. ASSERT(pOldView != NULL); if (pOldView == NULL) { MessageBeep(0); return false; } // get the new view specified by the id parameter CView* pNewView = (CView*)GetDlgItem(id); // if the new view is null, get out // if you assert here, you haven't properly initilaized the splitter // window. ASSERT(pNewView != NULL); if (pNewView == NULL ) { return false; } CFrameWnd* mainWnd = (CFrameWnd*)AfxGetMainWnd(); // if you assert here, something REALLY bad happend. ASSERT(mainWnd != NULL); if (mainWnd == NULL) // serious prob { return false; } // set the active view to the specified view if (mainWnd->GetActiveView() == pOldView) { mainWnd->SetActiveView(pNewView); } // show/hide the windows - do it in this order to avoid flashing pNewView->ShowWindow(SW_SHOW); pOldView->ShowWindow(SW_HIDE); // set the window id from our view map pNewView->SetDlgCtrlID(IdFromRowCol(paneRow, paneCol)); // we need to reset the old views ID so we can look it up again later // jms change - since we actually got to this point in the code, it's // safe to assume that the old view is really somewhere in the view map. CWnd* pCwnd =(CWnd*)pOldView; if (views.find(pCwnd) != views.end()) { UINT oldId = views[pCwnd]; pOldView->SetDlgCtrlID(oldId); } // clean up RecalcLayout(); pOldView->Invalidate(); pNewView->Invalidate(); return true; }
-
GetViewPtr() 函数未从原始版本更改。它只是检索映射到指定 ID 的视图的指针。
//--------------------------------------------------------------------- // Gets the window corresponding to the specified ID //--------------------------------------------------------------------- CWnd* CFlatSplitterWnd::GetViewPtr(UINT id, int paneRow, int paneCol) { map<CWnd*, UINT>::iterator It, Iend = views.end(); for (It = views.begin(); It != Iend; It++) { if ((*It).second == id) { return (*It).first; } } return NULL; }
GetIsActiveView() 函数是我自己添加的,用于帮助确定给定视图的活动状态,并从 CMainFrame 调用。
//----------------------------------------------------------------------- // This function is called by CMainFrame OnUpdate functions for the view // selection menu items. //----------------------------------------------------------------------- BOOL CFlatSplitterWnd::GetIsActiveView(UINT nID, int nPaneRow, int nPaneCol) { CWnd* pOldWnd = GetPane(nPaneRow, nPaneCol); CWnd* pNewWnd = GetViewPtr(nID, nPaneRow, nPaneCol); return (pOldWnd == pNewWnd); }
以下行已添加到 CFlatSplitterWnd.H
class CFlatSplitterWnd : public CSplitterWnd { public: // from https://codeproject.org.cn/splitter/DanCMultiViewSplitter.asp map<CWnd*, UINT> views; bool m_bFirstView; CWnd* GetViewPtr (UINT id, int paneRow, int paneCol); bool SwitchView (UINT id, int paneRow, int paneCol); bool AddSwitchableView(CRuntimeClass* pView, CCreateContext* pContext, CRect& size, UINT viewID); BOOL GetIsActiveView (UINT nID, int nPaneRow, int nPaneCol);
- 最后,我们需要为我们的视图创建 ID。还记得我们在第 1 部分创建的Constants.h 文件吗?打开它,并在网格控件的定义正下方添加以下代码。
#define IDC_PANE0_SECONDARY_VIEW 49001 #define IDC_PANE0_PRIMARY_VIEW 49002 #define IDC_PANE1_PRIMARY_VIEW 49003
挂载 CMainFrame
- 添加 OnCreateClient 的重写。类向导会将此函数放在 CMainFrame 的底部。
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { // TODO: Add your specialized code here and/or call the base class return CFrameWnd::OnCreateClient(lpcs, pContext); }
用以下版本替换该函数。此代码块中的注释应充分说明正在发生的事情。
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { CRect cr; GetWindowRect(&cr); // we need to subtract a reasonable value from the returned screen // height so that when we actually create the view, it won't cover // up part of the screen with it's soon-to-be-resized rectangle. int nHeight = ::GetSystemMetrics(SM_CYSCREEN) - 100; // our splitter window will be static if (!m_mainSplitter.CreateStatic(this, 2, 1)) { AfxMessageBox("Error setting up splitter window", MB_ICONERROR); return FALSE; } // add our replacable views - the view IDs are defined in constants.h m_mainSplitter.AddSwitchableView( RUNTIME_CLASS(CPrimaryView ), pContext, CRect(0, 1, cr.Width(), nHeight), IDC_PANE0_PRIMARY_VIEW); m_mainSplitter.AddSwitchableView( RUNTIME_CLASS(CSecondaryView), pContext, CRect(0, 1, cr.Width(), nHeight), IDC_PANE0_SECONDARY_VIEW); // create views in the splitter // we need a blank view to start off in the upper pane as the "old // view because part of the initialization of the application is // changing to the proper view. We'll use the view created by the // application wizard. m_mainSplitter.CreateView(0, 0, RUNTIME_CLASS(CSDIMultiApp1View), CSize(cr.Width(), nHeight), pContext); // and for the lower pane, we'll use our CInfoView class. m_mainSplitter.CreateView(1, 0, RUNTIME_CLASS(CInfoView), CSize(cr.Width(), 0), pContext); return TRUE; }
- 接下来,我们需要添加一些辅助函数,以便将来让我们的生活更轻松。
CPrimaryView* CMainFrame::GetPrimaryView() { return (dynamic_cast<CPrimaryView*>(m_mainSplitter.GetPane(0,0))); } CSecondaryView* CMainFrame::GetSecondaryView() { return (dynamic_cast<CSecondaryView*>(m_mainSplitter.GetPane(0,0))); } CInfoView* CMainFrame::GetInfoView() { return (dynamic_cast<CInfoView*>(m_mainSplitter.GetPane(1,0))); }
如您所见,这些函数仅返回指向函数名称所示的活动视图的指针。由于我们使用的是 dynamic_cast,因此如果返回的视图不是指定的类型(介于 < > 符号之间),则指针将为 NULL。
现在我们已经编写了所有视图类并实现了分割窗口,我们需要为用户提供一种切换视图的方式。
菜单使其生效
用户可以通过菜单项在主视图和辅助视图之间切换。在 MFC 应用程序中添加菜单非常简单。
- 在 IDE 的资源视图中,打开资源,展开树直到看到资源类别列表,展开菜单项,然后双击 IDR_MAINFRAME。IDE 中将打开一个新窗口,显示程序的当前菜单。
- 找到视图项,单击它,然后在“状态栏”项正下方名为“在此处键入”的行上右键单击。
- 右键单击按钮并从随后的菜单中选择插入分隔符项。菜单中将添加一条分隔线,分隔线下方会出现一个新的“在此处键入”行。

- 单击新的在此处键入项,然后键入“主视图”。请注意,正下方添加了一个新的“在此处键入”项。
- 单击新的在此处键入项,然后键入“辅助视图”。
- 返回并右键单击主视图项,然后从菜单中选择添加事件处理程序...。

- 此时应显示“事件处理程序向导”对话框(见下文)。此对话框允许您一次设置一个事件处理程序。请注意,我们可以设置两个可能的事件。我们都需要它们用于示例,但由于我们一次只能处理一个,因此我们将选择 ON_COMMAND。请务必确保您已选择要添加此处理程序的正确类——它必须是 CMainFrame。您需要重复上一步才能回到这里设置 ON_COMMAND_UI 的处理程序。同样,请确保将处理程序添加到正确的类。
- 对辅助视图菜单项重复最后两步。如果您正确执行了前面的步骤,您应该会在 CMainFrame 类的底部看到以下函数。
void CMainFrame::OnViewPrimaryview() { } void CMainFrame::OnUpdateViewPrimaryview(CCmdUI *pCmdUI) { } void CMainFrame::OnViewSecondaryview() { } void CMainFrame::OnUpdateViewSecondaryview(CCmdUI *pCmdUI) { }
- 向新的消息处理函数添加代码,使其切换视图并根据当前活动视图启用/禁用菜单项。
void CMainFrame::OnViewPrimaryview() { m_mainSplitter.SwitchView(IDC_PANE0_PRIMARY_VIEW, 0, 0); } void CMainFrame::OnUpdateViewPrimaryview(CCmdUI *pCmdUI) { pCmdUI->Enable(!m_mainSplitter.GetIsActiveView( IDC_PANE0_PRIMARY_VIEW,0,0)); } void CMainFrame::OnViewSecondaryview() { m_mainSplitter.SwitchView(IDC_PANE0_SECONDARY_VIEW, 0, 0); } void CMainFrame::OnUpdateViewSecondaryview(CCmdUI *pCmdUI) { pCmdUI->Enable(!m_mainSplitter.GetIsActiveView( IDC_PANE0_SECONDARY_VIEW,0,0)); }
编译应用程序
重新生成解决方案,然后运行应用程序。第一张图片显示了应用程序的初始显示方式。第二张截图显示了单击/拖动分割条时应用程序的外观。第三张图片说明了菜单选择辅助视图,第四张图片显示了辅助视图。

下一步?
在第 3 部分,我们将再次使用 CodeProject 上的文章来添加自定义状态栏类和一些多线程组件。
第 2 部分结束
由于本文篇幅较长,我决定将其分成几部分。如果网站编辑按照我的要求操作,所有后续部分都应该在网站的同一个代码部分。每一部分都有自己的源代码,因此在阅读后续部分时,请务必下载该部分相应的源代码(除非您要手动完成文章中概述的所有内容)。
为了保持一致性(和理智),请对所有部分进行投票,并以相同的方式投票。这有助于将文章保留在同一部分。感谢您的理解。