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

简介:


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

――框架
作者:宋宝华  e-mail:21cnbao@21cn.com
从前文可知,在MFC中,文档是真正的数据载体,视图是文档的显示界面,对应同一个文档,可能存在多个视图界面,我们需要另外一种东东来将这些界面管理起来,这个东东就是框架。
MFC创造框架类的初衷在于:把界面管理工作独立出来!框架窗口为应用程序的用户界面提供结构框架,它是应用程序的主窗口,负责管理其包容的窗口。一个应用程序启动时会创建一个最顶层的框架窗口。
MFC提供二种类型的框架窗口:单文档窗口SDI和多文档窗口MDI(你可以认为对话框是另一种框架窗口)。单文档窗口一次只能打开一个文档框架窗口,而多文档窗口应用程序中可以打开多个文档框架窗口,即子窗口(Child Window)。这些子窗口中的文档可以为同种类型,也可以为不同类型。
Visual C++ AppWizard的第一个对话框中,会让用户选择应用程序是基于单文档、多文档还是基于对话框的,如图5.1
5.1 AppWizard中选择框架窗口
MFC提供了三个类CFrameWndCMDIFrameWndCMDIChildWnd用于支持单文档窗口和多文档窗口,这些类的层次结构如图5.2
5.2 CFrameWndCMDIFrameWndCMDIChildWnd类的层次
1CFrameWnd类用于SDI应用程序的框架窗口,SDI框架窗口既是应用程序的主框架窗口,也是当前文档对应的视图的边框;
CFrameWnd类也作为CMDIFrameWndCMDIChildWnd类的父类,而在基于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;
}
2CMDIFrameWnd类用于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;
}
3CMDIChildWnd类用于在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继承自CMDIFrameWndCChildFrame类继承自CMDIChildWnd类,CExampleView视图类则负责在CMDIChildWnd类对应的子框架窗口中显示文档的数据。
       文中只是对CMDIFrameWndCreateClient成员函数进行了介绍,实际上,CFrameWndCMDIChildWnd均包含CreateClient成员函数。我们经常通过重载CFrameWnd:: CreateClientCMDIChildWnd:: 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,如需转载请自行联系原作者




相关文章
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
14天前
|
存储 分布式计算 关系型数据库
架构/技术框架调研
本文介绍了微服务间事务处理、调用、大数据处理、分库分表、大文本存储及数据缓存的最优解决方案。重点讨论了Seata、Dubbo、Hadoop生态系统、MyCat、ShardingSphere、对象存储服务和Redis等技术,提供了详细的原理、应用场景和优缺点分析。
|
2月前
|
人工智能 前端开发 JavaScript
前端架构思考 :专注于多框架的并存可能并不是唯一的方向 — 探讨大模型时代前端的分层式微前端架构
随着前端技术的发展,微前端架构成为应对复杂大型应用的流行方案,允许多个团队使用不同技术栈并将其模块化集成。然而,这种设计在高交互性需求的应用中存在局限,如音视频处理、AI集成等。本文探讨了传统微前端架构的不足,并提出了一种新的分层式微前端架构,通过展示层与业务层的分离及基于功能的横向拆分,以更好地适应现代前端需求。
|
2月前
|
存储 分布式计算 API
大数据-107 Flink 基本概述 适用场景 框架特点 核心组成 生态发展 处理模型 组件架构
大数据-107 Flink 基本概述 适用场景 框架特点 核心组成 生态发展 处理模型 组件架构
85 0
|
19天前
|
监控
SMoA: 基于稀疏混合架构的大语言模型协同优化框架
通过引入稀疏化和角色多样性,SMoA为大语言模型多代理系统的发展开辟了新的方向。
31 6
SMoA: 基于稀疏混合架构的大语言模型协同优化框架
|
2月前
|
JSON 前端开发 Java
Spring Boot框架中的响应与分层解耦架构
在Spring Boot框架中,响应与分层解耦架构是两个核心概念,它们共同促进了应用程序的高效性、可维护性和可扩展性。
54 3
|
3月前
|
机器学习/深度学习
ACM MM24:复旦提出首个基于扩散模型的视频非限制性对抗攻击框架,主流CNN和ViT架构都防不住它
【9月更文挑战第23天】复旦大学研究团队提出了ReToMe-VA,一种基于扩散模型的视频非限制性对抗攻击框架,通过时间步长对抗性潜在优化(TALO)与递归令牌合并(ReToMe)策略,实现了高转移性且难以察觉的对抗性视频生成。TALO优化去噪步骤扰动,提升空间难以察觉性及计算效率;ReToMe则确保时间一致性,增强帧间交互。实验表明,ReToMe-VA在攻击转移性上超越现有方法,但面临计算成本高、实时应用受限及隐私安全等挑战。[论文链接](http://arxiv.org/abs/2408.05479)
77 3
|
3月前
|
Kubernetes Java Android开发
用 Quarkus 框架优化 Java 微服务架构的设计与实现
Quarkus 是专为 GraalVM 和 OpenJDK HotSpot 设计的 Kubernetes Native Java 框架,提供快速启动、低内存占用及高效开发体验,显著优化了 Java 在微服务架构中的表现。它采用提前编译和懒加载技术实现毫秒级启动,通过优化类加载机制降低内存消耗,并支持多种技术和框架集成,如 Kubernetes、Docker 及 Eclipse MicroProfile,助力开发者轻松构建强大微服务应用。例如,在电商场景中,可利用 Quarkus 快速搭建商品管理和订单管理等微服务,提升系统响应速度与稳定性。
78 5
|
2月前
|
存储 SQL 消息中间件
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
49 0
|
3月前
|
存储 Java Maven
从零到微服务专家:用Micronaut框架轻松构建未来架构
【9月更文挑战第5天】在现代软件开发中,微服务架构因提升应用的可伸缩性和灵活性而广受欢迎。Micronaut 是一个轻量级的 Java 框架,适合构建微服务。本文介绍如何从零开始使用 Micronaut 搭建微服务架构,包括设置开发环境、创建 Maven 项目并添加 Micronaut 依赖,编写主类启动应用,以及添加控制器处理 HTTP 请求。通过示例代码展示如何实现简单的 “Hello, World!” 功能,并介绍如何通过添加更多依赖来扩展应用功能,如数据访问、验证和安全性等。Micronaut 的强大和灵活性使你能够快速构建复杂的微服务系统。
105 5