【问题标题】:MFC basic structure questionsMFC基本结构题
【发布时间】:2009-12-24 12:28:25
【问题描述】:

有几件事我不确定:

当您使用 MFC 应用程序向导(我们称之为 TestMfc)创建基本 SDI 时,您会得到:

4大类:

CTestMfcApp
CTestMfcView
CTestMfcDoc
CMainFrame

我注意到CTestMfcApp 有这些声明

    ON_COMMAND(ID_APP_ABOUT, &CTestMfcApp::OnAppAbout)
    // Standard file based document commands
    ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
    // Standard print setup command
    ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinApp::OnFilePrintSetup)

CTestMfcView 有这些:

BEGIN_MESSAGE_MAP(CTestMfcView, CEditView)
    // Standard printing commands
    ON_COMMAND(ID_FILE_PRINT, &CEditView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_DIRECT, &CEditView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CEditView::OnFilePrintPreview)
END_MESSAGE_MAP()

我不明白为什么 MFC 会创建这种分离? 我不明白为什么应用程序类需要声明处理事件的函数......这不是视图的工作吗?例如,如果窗口很少,它甚至会变得更加清晰。

其次,这些事件是如何被调用的?我知道应该有 WINPROC 函数应该获取 MSG 并调用正确的处理程序。是 ON_COMMAND 宏,它设置某种指针函数,稍后可用于 WINPROC 函数。 为什么 ON_COMMAND 没有获得 WINDOWS 句柄...如果程序中有另一个具有相同 ID 的 WINDOW?

第三个也是最后一个,假设我想将某些窗口的线程更改为警报状态。 为此,我想更改主循环(不断调用 getmessage/dispatchmessage 等等......并插入waitformultibleonject函数。 winmain函数在哪里?当应用程序向导为我完成所有工作时,我找不到它。

谢谢!!

【问题讨论】:

    标签: c++ windows multithreading mfc event-handling


    【解决方案1】:

    在 MFC 中,消息“冒泡”直到找到处理程序。 IIRC 它是视图 -> 文档 -> 文档模板 -> MainFrame -> 应用程序。这允许处理视图中特定于视图的事件、文档中特定于文档的事件等。

    通常,全局处理程序最终位于大型机类中。但是,您可以使用多个大型机窗口(即使具有不同的行为),其中“MainFrame”和“App 类”之间的区别变得很重要。

    尽管如此,特定于控件的处理程序确实属于视图类。我只会将 WM_COMMAND 处理程序放入更高级别的类中。


    对于您的第三个问题:我不会那样做。虽然 MFC 避免了一些常见的模态循环,但您不能全部避免。 OnIdle 是实现延迟更新的好地方。


    [update] MFC 使用一个全局 WNDPROC 来处理所有消息。它使用 HWND 到 CWnd 映射来定位 MFC 对象。当 WNDPROC 看到 WM_COMMAND 消息时,它将首先检查接收窗口的消息映射,如果它包含该消息的处理程序。如果没有,它将进行各种检查,例如“这不仅仅是一个CWnd,而是一个CView?”是 --> 获取文档并查看文档消息映射是否有针对此特定命令的处理程序。

    【讨论】:

    • 我仍然不明白 ONCOMMAND 宏如何知道要指定处理函数的窗口句柄。我的第三个担心是为了使用 APC,我必须将我的线程插入到警报状态才能获得那些......所以除了使用对我来说似乎正确的 waitformultipleobjects 之外,你如何建议我这样做
    • Idan,我在问题中添加了更多信息。其他问题最好单独问。
    【解决方案2】:

    MFC 架构简介

    Microsoft Foundation Class library framework 在提供MVC design pattern 的基础方面做得很合理,它使用各种消息来提供各个部分之间的通信。但是,与任何框架一样,它提供的结构可以提供许多您不必编写的功能以及相当多的限制和约束,如果您的方法与框架不一致。

    该框架使用协作实体之间的消息,扩展了 Windows 消息的思想,并使用了 Windows 操作系统的消息基础结构。其中一些合作实体有自己的 Windows 消息泵(模式对话框和线程),而其余的则使用由主线程或 UI 线程提供服务的应用程序消息泵。

    发送到应用程序主窗口的消息(例如键盘或鼠标事件或使用PostMessage() 发布的消息)由主窗口线程(也称为主 UI 线程)从 Windows 消息队列中拉出,为消息泵提供服务,然后由 MFC 框架转发给它所属的实体。这就是暂停或等待主 UI 线程的操作使应用程序无响应的原因。主 UI 线程必须不断运行以从 Windows 消息队列中提取 Windows 消息,然后将它们分发到应用程序的其余部分。

    MFC 框架通过查看消息映射列表来转发消息,以查找哪个实体或 MFC 类实例正在等待该消息,如果找到,则使用参数调用消息映射条目中指定的 C++ 函数MFC 规范要求。期望任何消息处理函数要么快速完成动作并返回到 MFC 框架的消息调度功能,要么使用线程执行任何需要一些时间的动作然后返回。

    MVC design pattern 将应用程序视为由三个协作实体组成,模型实体包含数据,视图实体呈现模型及其数据的视图,控制器实体是 affordance用户操作以更改所查看的内容以及模型及其数据。

    MFC 框架体系结构从几个基类开始,然后通过继承进行专门化。例如,有一个通用视图类,它为通用显示提供消息基础结构,然后子类扩展并专门化通用视图类。例如,CView 类专用于 CScrollView 类。

    MFC UI 类的主要类型

    通常,文档类将处理涉及文档内容更改的消息,而视图类将处理涉及文档内容呈现的消息。文档类负责将所有数据或内容序列化到文件对象或从文件对象序列化。

    文档类使用Observer design pattern 让视图知道何时需要更新。文档类有一个视图实例列表,因此当文档或模型或数据发生更改时,所有已注册的视图都会收到一条消息,指示更改并需要更新。

    视图类负责将文档数据的视图显示到设备上,例如屏幕上的窗口或打印机。因此,CView 类具有处理通过消息 ID 为 ID_FILE_PRINTID_FILE_PRINT_PREVIEW 的消息发送的打印请求的函数是正常的。

    当文档中的数据发生变化时,可以向文档中注册的所有视图发送一条消息,通知这些视图数据发生了变化,以便视图可以决定是否对视图进行更改。

    框架的第三个主要类CWinApp 或应用程序类是文档和视图类的容器和管理器。在 app 对象中,它是应用程序的入口点(也用于 MFC DLL 以及提供 DLL 加载和卸载入口点的构造),是主消息泵的挂钩。 app 类的目的是设置初始环境,然后允许程序员将他们的特定文档和视图对象挂钩到 app 类的 MFC 框架中。

    由于CWinApp类是应用程序的容器,它处理ID_FILE_NEWID_FILE_OPEN这样的事件是正常的,因为它会导致文档类关闭当前文档并重新初始化应用程序从新文档及其关联视图开始。

    我的意见是关于对话框处理和帮助处理被放入CWinApp 类,因为它是最容易放置的地方。通常,关于是一个包含应用程序基本描述的对话框。帮助和关于可能是您无需先启动文档或查看即可访问的内容。

    MFC 框架还有其他支持主要 UI 功能的类。自 1990 年代初首次引入 MFC 以来,还添加了其他其他库。

    关于消息的一些细节

    Windows 消息具有指示消息类型的消息 ID。有不同范围的消息 Id 值分配给不同的目的。一个范围是WM_USER 范围,应用程序可以使用该范围而不必担心使用分配给Windows 消息(如键盘消息)的消息ID。此范围的一部分分配给 MFC 消息(请参阅 WM_USER vs WM_APP 以及 Sending and receiving Windows messages 以及 How to handle a posted, registered windows message within a VCL application? )。

    这些消息是使用PostMessage()SendMessage() 的标准Windows 消息,带有目标窗口句柄、消息标识符和用于提供附加信息的两个参数的标准Windows 格式。 MFC 框架为各种消息标识符定义了许多不同的 C 预处理器。如果您遵循类派生层次结构,您会发现文档类CDocument 最终派生自CCmdTarget 类,就像视图类CViewCView 的派生一样。 CCmdTarget 又派生自最基本的 MFC 类 CObject

    大部分消息映射功能似乎来自CCmdTarget 类的功能,该类使用接受消息的方法,检查消息标识符是否在特定对象的CCmdTarget 消息列表中,以及如果不将其传递给链中的下一个 CCmdTarget 对象。所以 MFC 框架使用了一种Strategy Design Pattern,这样消息就会沿着组件链传递,直到找到可以处理消息的对象。

    由于 MFC 框架的时代,相当多的暴露部分使用 C 预处理器和宏。您可以检查宏以查看它们在做什么以及它们是如何使用 Visual Studio IDE 实现的。而那些作为模板实现的 MFC 框架部分也可以通过 Visual Studio IDE 轻松读取。宏和模板都在 MFC 包含文件中。

    但是,要阅读实际类中的代码,您需要找到 MFC 源代码主体的副本。来自 Microsoft 网站的 Using the MFC Source Files 提供了一个起点,包括从哪里可以找到安装 Visual Studio 的源代码。

    Microsoft 基础类 (MFC) 库提供完整源代码 代码。头文件 (.h) 位于 \atlmfc\include 目录中; 实现文件 (.cpp) 位于 \atlmfc\src\mfc 目录中。

    我不建议您使用自己的更改来分叉。

    您的第三个问题是关于线程的,您描述的方法在 MFC 框架中没有意义。使用 MFC,您通常会使用 AfxBeginThread()AfxBeginThreadEx() 函数创建线程。线程有两种,一种有用户界面,一种没有。

    多线程 MFC 应用程序需要修改消息泵是没有意义的。使用PostThreadMessage() 之类的东西创建线程后,您将改为向线程发送消息。线程可以通过调用GetMessage()PeekMessage() 函数来接收消息。每个 MFC 线程都有一个消息泵和消息队列,它们在“线程第一次调用用户或 GDI 函数之一时”设置(请参阅PostThreadMessageW function)。

    参考文献

    Is MFC is based on any design pattern,if so which design pattern?

    Windows Messages

    SendMessage/PostMessage to a derived CView class not working for MFC application

    MFC - How to post a message to all views that were derived from CView class?

    Is the 'C' in MVC really necessary?

    本文Threads with MFC在代码项目中提供了概述和源示例。

    【讨论】:

      【解决方案3】:

      打印通常在视图中处理,因为在 win32 中,您通过调用 OnPaint 事件到打印机而不是屏幕来打印。此外,对于涉及获取鼠标位置的任何事件,在视图中执行此操作更容易。

      您可以轻松地从视图中获取当前文档,但从文档中获取视图更费力。

      【讨论】:

        猜你喜欢
        • 2019-12-04
        • 2020-08-25
        • 1970-01-01
        • 2019-01-27
        • 2011-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多