深入浅出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,如需转载请自行联系原作者




相关文章
|
4月前
|
存储 边缘计算 Cloud Native
“论模型驱动架构设计方法及其应用”写作框架,软考高级,系统架构设计师
模型驱动架构设计是一种用于应用系统开发的软件设计方法,以模型构造、模型转换和精化为核心,提供了一套软件设计的指导规范。在模型驱动架构环境下,通过创建出机器可读和高度抽象的模型实现对不同问题域的描述,这些模型独立于实现技术,以标准化的方式储存,利用模型转换策略来驱动包括分析、设计和实现等在内的整个软件开发过程。
133 3
|
11天前
|
机器学习/深度学习
ACM MM24:复旦提出首个基于扩散模型的视频非限制性对抗攻击框架,主流CNN和ViT架构都防不住它
【9月更文挑战第23天】复旦大学研究团队提出了ReToMe-VA,一种基于扩散模型的视频非限制性对抗攻击框架,通过时间步长对抗性潜在优化(TALO)与递归令牌合并(ReToMe)策略,实现了高转移性且难以察觉的对抗性视频生成。TALO优化去噪步骤扰动,提升空间难以察觉性及计算效率;ReToMe则确保时间一致性,增强帧间交互。实验表明,ReToMe-VA在攻击转移性上超越现有方法,但面临计算成本高、实时应用受限及隐私安全等挑战。[论文链接](http://arxiv.org/abs/2408.05479)
24 3
|
14天前
|
Kubernetes Java Android开发
用 Quarkus 框架优化 Java 微服务架构的设计与实现
Quarkus 是专为 GraalVM 和 OpenJDK HotSpot 设计的 Kubernetes Native Java 框架,提供快速启动、低内存占用及高效开发体验,显著优化了 Java 在微服务架构中的表现。它采用提前编译和懒加载技术实现毫秒级启动,通过优化类加载机制降低内存消耗,并支持多种技术和框架集成,如 Kubernetes、Docker 及 Eclipse MicroProfile,助力开发者轻松构建强大微服务应用。例如,在电商场景中,可利用 Quarkus 快速搭建商品管理和订单管理等微服务,提升系统响应速度与稳定性。
30 5
|
29天前
|
存储 Java Maven
从零到微服务专家:用Micronaut框架轻松构建未来架构
【9月更文挑战第5天】在现代软件开发中,微服务架构因提升应用的可伸缩性和灵活性而广受欢迎。Micronaut 是一个轻量级的 Java 框架,适合构建微服务。本文介绍如何从零开始使用 Micronaut 搭建微服务架构,包括设置开发环境、创建 Maven 项目并添加 Micronaut 依赖,编写主类启动应用,以及添加控制器处理 HTTP 请求。通过示例代码展示如何实现简单的 “Hello, World!” 功能,并介绍如何通过添加更多依赖来扩展应用功能,如数据访问、验证和安全性等。Micronaut 的强大和灵活性使你能够快速构建复杂的微服务系统。
61 5
|
28天前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
58 2
|
1月前
|
设计模式 开发框架 前端开发
在开发框架中实现事件驱动架构
【9月更文挑战第2天】事件驱动架构(EDA)通过事件机制让组件间解耦交互,适用于动态扩展和高响应性的系统。本文提供一个基于Beego框架实现事件驱动的示例,通过事件管理器注册和触发事件,实现用户注册和登录时的不同处理逻辑,展示了其在Web应用中的灵活性和高效性。
64 5
|
2月前
|
XML 开发框架 .NET
.NET框架:软件开发领域的瑞士军刀,如何让初学者变身代码艺术家——从基础架构到独特优势,一篇不可错过的深度解读。
【8月更文挑战第28天】.NET框架是由微软推出的统一开发平台,支持多种编程语言,简化应用程序的开发与部署。其核心组件包括公共语言运行库(CLR)和类库(FCL)。CLR负责内存管理、线程管理和异常处理等任务,确保代码稳定运行;FCL则提供了丰富的类和接口,涵盖网络、数据访问、安全性等多个领域,提高开发效率。此外,.NET框架还支持跨语言互操作,允许开发者使用C#、VB.NET等语言编写代码并无缝集成。这一框架凭借其强大的功能和广泛的社区支持,已成为软件开发领域的重要工具,适合初学者深入学习以奠定职业生涯基础。
90 1
|
2月前
|
JSON 前端开发 API
Django 后端架构开发:通用表单视图、组件对接、验证机制和组件开发
Django 后端架构开发:通用表单视图、组件对接、验证机制和组件开发
47 2
|
2月前
|
JSON 数据处理 API
Django后端架构开发:视图与模板的正确使用
Django后端架构开发:视图与模板的正确使用
14 1
|
2月前
|
消息中间件 Kafka Java
Spring 框架与 Kafka 联姻,竟引发软件世界的革命风暴!事件驱动架构震撼登场!
【8月更文挑战第31天】《Spring 框架与 Kafka 集成:实现事件驱动架构》介绍如何利用 Spring 框架的强大功能与 Kafka 分布式流平台结合,构建灵活且可扩展的事件驱动系统。通过添加 Spring Kafka 依赖并配置 Kafka 连接信息,可以轻松实现消息的生产和消费。文中详细展示了如何设置 `KafkaTemplate`、`ProducerFactory` 和 `ConsumerFactory`,并通过示例代码说明了生产者发送消息及消费者接收消息的具体实现。这一组合为构建高效可靠的分布式应用程序提供了有力支持。
85 0
下一篇
无影云桌面