博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入浅出MFC“文档/视图”架构(5)――框架
阅读量:5872 次
发布时间:2019-06-19

本文共 8083 字,大约阅读时间需要 26 分钟。

深入浅出MFC“文档/视图”架构(5

――框架
作者:宋宝华  e-mail:21cnbao@21cn.com
从前文可知,在MFC
中,文档是真正的数据载体,视图是文档的显示界面,对应同一个文档,可能存在多个视图界面,我们需要另外一种东东来将这些界面管理起来,这个东东就是框架。
MFC
创造框架类的初衷在于:把界面管理工作独立出来!框架窗口为应用程序的用户界面提供结构框架,它是应用程序的主窗口,负责管理其包容的窗口。一个应用程序启动时会创建一个最顶层的框架窗口。
MFC
提供二种类型的框架窗口:单文档窗口SDI
和多文档窗口MDI
(你可以认为对话框是另一种框架窗口)。单文档窗口一次只能打开一个文档框架窗口,而多文档窗口应用程序中可以打开多个文档框架窗口,即子窗口(Child Window
)。这些子窗口中的文档可以为同种类型,也可以为不同类型。
Visual C++ AppWizard
的第一个对话框中,会让用户选择应用程序是基于单文档、多文档还是基于对话框的,如图5.1
5.1 
AppWizard
中选择框架窗口
MFC
提供了三个类CFrameWnd
CMDIFrameWnd
CMDIChildWnd
用于支持单文档窗口和多文档窗口,这些类的层次结构如图5.2
5.2 CFrameWnd
CMDIFrameWnd
CMDIChildWnd
类的层次
1
CFrameWnd
类用于SDI
应用程序的框架窗口,SDI
框架窗口既是应用程序的主框架窗口,也是当前文档对应的视图的边框;
CFrameWnd
类也作为CMDIFrameWnd
CMDIChildWnd
类的父类,而在基于SDI
的应用程序中,AppWizard
会自动为我们添加一个继承自CFrameWnd
类的CMainFrame
类。
CFrameWnd
类中重要的函数有Create
(用于创建窗口)、LoadFrame
(用于从资源文件中创建窗口)、PreCreateWindow
(用于注册窗口类)等。Create
函数第一个参数为窗口注册类名,第二个参数为窗口标题,其余几个参数指定了窗口的风格、大小、父窗口、菜单名等,其源代码如下:
BOOL CFrameWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT &rect, CWnd *pParentWnd, LPCTSTR lpszMenuName, DWORD  dwExStyle, CCreateContext *pContext)
{
  HMENU hMenu = NULL;
  if (lpszMenuName != NULL)
  {
    // load in a menu that will get destroyed when window gets destroyed
    HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
    if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
    {
      TRACE0("Warning: failed to load menu for CFrameWnd.\n");
      PostNcDestroy(); // perhaps delete the C++ object
      return FALSE;
    }
  }
 
  m_strTitle = lpszWindowName; // save title for later
 
  if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left,
    rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd
    ->GetSafeHwnd(), hMenu, (LPVOID)pContext))
  {
    TRACE0("Warning: failed to create CFrameWnd.\n");
    if (hMenu != NULL)
      DestroyMenu(hMenu);
    return FALSE;
  }
 
  return TRUE;
}
LoadFrame
函数用于从资源文件中创建窗口,我们通常只需要给其指定一个参数,LoadFrame
使用该参数从资源中获取主边框窗口的标题、图标、菜单、加速键等,其源代码为:
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd
  *pParentWnd, CCreateContext *pContext)
{
  // only do this once
  ASSERT_VALID_IDR(nIDResource);
  ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
 
  m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE)
 
  CString strFullString;
  if (strFullString.LoadString(nIDResource))
    AfxExtractSubString(m_strTitle, strFullString, 0);
  // first sub-string
 
  VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
 
  // attempt to create the window
  LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
  LPCTSTR lpszTitle = m_strTitle;
  if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault, pParentWnd,
    MAKEINTRESOURCE(nIDResource), 0L, pContext))
  {
    return FALSE; // will self destruct on failure normally
  }
 
  // save the default menu handle
  ASSERT(m_hWnd != NULL);
  m_hMenuDefault = ::GetMenu(m_hWnd);
 
  // load accelerator resource
  LoadAccelTable(MAKEINTRESOURCE(nIDResource));
 
  if (pContext == NULL)
  // send initial update
    SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);
 
  return TRUE;
}
SDI
程序中,如果需要修改窗口的默认风格,程序员需要修改CMainFrame
类的PreCreateWindow
函数,因为AppWizard
给我们生成的CMainFrame::PreCreateWindow
仅对其基类的PreCreateWindow
函数进行调用,CFrameWnd::PreCreateWindow
的源代码如下:
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT &cs)
{
  if (cs.lpszClass == NULL)
  {
    VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
    cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
  }
 
  if ((cs.style &FWS_ADDTOTITLE) && afxData.bWin4)
    cs.style |= FWS_PREFIXTITLE;
 
  if (afxData.bWin4)
    cs.dwExStyle |= WS_EX_CLIENTEDGE;
 
  return TRUE;
}
2
CMDIFrameWnd
类用于MDI
应用程序的主框架窗口,主框架窗口是所有MDI
文档子窗口的容器,并与子窗口共享菜单;
CMDIFrameWnd
类相较CFrameWnd
类增加的重要函数有:MDIActivate
(激活另一个MDI
子窗口)、MDIGetActive
(得到目前的活动子窗口)、MDIMaximize
(最大化一个子窗口)、MDINext
(激活目前活动子窗口的下一子窗口并将当前活动子窗口排入所有子窗口末尾)、MDIRestore
(还原MDI
子窗口)、MDISetMenu
(设置MDI
子窗口对应的菜单)、MDITile
(平铺子窗口)、MDICascade
(重叠子窗口)。
Visual C++
开发环境是典型的MDI
程序,其执行MDI Cascade
的效果如图5.3
5.3 MDI Cascade
的效果
       
而执行MDI Tile
的效果则如图5.4
5.4 MDI Tile
的效果
MDISetMenu
函数的重要意义体现在一个MDI
程序可以为不同类型的文档(与文档模板关联)显示不同的菜单,例如下面的这个函数Load
一个菜单,并将目前主窗口的菜单替换为该菜单:
void CMdiView::OnReplaceMenu()
{
   // Load a new menu resource named IDR_SHORT_MENU
   CMdiDoc* pdoc = GetDocument();
   pdoc->m_DefaultMenu =
      ::LoadMenu(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_SHORT_MENU));
   if (pdoc->m_DefaultMenu == NULL)
      return;
 
   // Get the parent window of this view window. The parent window is
   // a CMDIChildWnd-derived class. We can then obtain the MDI parent
   // frame window using the CMDIChildWnd*. Then, replace the current
   // menu bar with the new loaded menu resource.
   CMDIFrameWnd* frame = ((CMDIChildWnd *) GetParent())->GetMDIFrame();
   frame->MDISetMenu(CMenu::FromHandle(pdoc->m_DefaultMenu), NULL);
   frame->DrawMenuBar();
}
CMDIFrameWnd
类另一个不讲“不足以服众”的函数是OnCreateClient
,它是子框架窗口的创造者,其实现如下:
BOOL CMDIFrameWnd::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext*)
{
       CMenu* pMenu = NULL;
       if (m_hMenuDefault == NULL)
       {
              // default implementation for MFC V1 backward compatibility
              pMenu = GetMenu();
              ASSERT(pMenu != NULL);
              // This is attempting to guess which sub-menu is the Window menu.
              // The Windows user interface guidelines say that the right-most
              // menu on the menu bar should be Help and Window should be one
              // to the left of that.
              int iMenu = pMenu->GetMenuItemCount() - 2;
 
              // If this assertion fails, your menu bar does not follow the guidelines
              // so you will have to override this function and call CreateClient
              // appropriately or use the MFC V2 MDI functionality.
              ASSERT(iMenu >= 0);
              pMenu = pMenu->GetSubMenu(iMenu);
              ASSERT(pMenu != NULL);
       }
 
       return CreateClient(lpcs, pMenu);
}
CMDIFrameWnd::OnCreateClient
的源代码可以看出,其中真正起核心作用的是对函数CreateClient
的调用:
BOOL CMDIFrameWnd::CreateClient(LPCREATESTRUCT lpCreateStruct,
       CMenu* pWindowMenu)
{
       ASSERT(m_hWnd != NULL);
       ASSERT(m_hWndMDIClient == NULL);
       DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_BORDER |
              WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
              MDIS_ALLCHILDSTYLES;    // allow children to be created invisible
       DWORD dwExStyle = 0;
       // will be inset by the frame
 
       if (afxData.bWin4)
       {
              // special styles for 3d effect on Win4
              dwStyle &= ~WS_BORDER;
              dwExStyle = WS_EX_CLIENTEDGE;
       }
 
       CLIENTCREATESTRUCT ccs;
       ccs.hWindowMenu = pWindowMenu->GetSafeHmenu();
              // set hWindowMenu for MFC V1 backward compatibility
              // for MFC V2, window menu will be set in OnMDIActivate
       ccs.idFirstChild = AFX_IDM_FIRST_MDICHILD;
 
       if (lpCreateStruct->style & (WS_HSCROLL|WS_VSCROLL))
       {
              // parent MDIFrame's scroll styles move to the MDICLIENT
              dwStyle |= (lpCreateStruct->style & (WS_HSCROLL|WS_VSCROLL));
 
              // fast way to turn off the scrollbar bits (without a resize)
              ModifyStyle(WS_HSCROLL|WS_VSCROLL, 0, SWP_NOREDRAW|SWP_FRAMECHANGED);
       }
 
       // Create MDICLIENT control with special IDC
       if ((m_hWndMDIClient = ::CreateWindowEx(dwExStyle, _T("mdiclient"), NULL,
              dwStyle, 0, 0, 0, 0, m_hWnd, (HMENU)AFX_IDW_PANE_FIRST,
              AfxGetInstanceHandle(), (LPVOID)&ccs)) == NULL)
       {
              TRACE(_T("Warning: CMDIFrameWnd::OnCreateClient: failed to create MDICLIENT.")
                     _T(" GetLastError returns 0x%8.8X\n"), ::GetLastError());
              return FALSE;
       }
       // Move it to the top of z-order
       ::BringWindowToTop(m_hWndMDIClient);
 
       return TRUE;
}
3
CMDIChildWnd
类用于在MDI
主框架窗口中显示打开的文档。每个视图都有一个对应的子框架窗口,子框架窗口包含在主框架窗口中,并使用主框架窗口的菜单。
CMDIChildWnd
类的一个重要函数GetMDIFrame()
返回目前MDI
客户窗口的父窗口,其实现如下:
CMDIFrameWnd *CMDIChildWnd::GetMDIFrame()
{
  HWND hWndMDIClient = ::GetParent(m_hWnd);
  CMDIFrameWnd *pMDIFrame;
  pMDIFrame = (CMDIFrameWnd*)CWnd::FromHandle(::GetParent(hWndMDIClient));
  return pMDIFrame;
}
利用AppWizard
生成的名为“example
”的MDI
工程包含如图5.5
所示的类。
5.5 
一个MDI
工程包含的类
       
其中的CMainFrame
继承自CMDIFrameWnd
CChildFrame
类继承自CMDIChildWnd
类,CExampleView
视图类则负责在CMDIChildWnd
类对应的子框架窗口中显示文档的数据。
       
文中只是对CMDIFrameWnd
CreateClient
成员函数进行了介绍,实际上,CFrameWnd
CMDIChildWnd
均包含CreateClient
成员函数。我们经常通过重载CFrameWnd:: CreateClient
CMDIChildWnd:: CreateClient
函数的方法来实现“窗口分割”,例如:
BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs,
CCreateContext *pContext)
{
  if (!m_wndSplitter.Create(this,
  2, 2, // 
分割的行、列数
  CSize(10, 10),  // 
最小化尺寸
  pContext))
  {
    TRACE0("
创建分割失败
");
    return FALSE;
  }
  return TRUE;

}

 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120303,如需转载请自行联系原作者

你可能感兴趣的文章
【wikioi】1025 选菜
查看>>
emacs之配置yasnippet
查看>>
VisualStudio:让 XML 支持智能提示
查看>>
用于守护进程的出错处理函数
查看>>
AppCan可以视为Rexsee的存活版
查看>>
【转】SQL SERVER 2005 数据库状态为“可疑”的解决方法
查看>>
事件、委托、委托方法的总结(使用EventHandler<>)
查看>>
Revit API 创建带箭头的标注
查看>>
jetty启动报错Unsupported major.minor version 51.0
查看>>
Xamarin.Android开发实践(七)
查看>>
彩色图像上执行Mean Shift迭代搜索目标 ,维加权直方图 + 巴氏系数 + Mean Shift迭代...
查看>>
深入理解JavaScript系列
查看>>
strtol 函数用法
查看>>
eclipse内存溢出设置
查看>>
搭建jenkins环境(linux操作系统)
查看>>
VS 2015 GIT操作使用说明
查看>>
上海办理房产税变更
查看>>
innerHTML与jquery里的html()区别介绍
查看>>
每天一个linux命令(52):scp命令
查看>>
CMOS Sensor Interface(CSI)
查看>>