从“文件”菜单中创建新文档以响应“新建”或“打开”命令时,文档模板还会创建一个新的框架窗口,用于查看文档。
文档模板构造函数指定模板能够创建的文档、窗口和视图类型。 这是由传递给文档模板构造函数的参数确定的。 以下代码演示了如何为示例应用程序创建 CMultiDocTemplate:
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_CMyDocTypeTYPE,RUNTIME_CLASS(CMyDoc),RUNTIME_CLASS(CChildFrame), // custom MDI child frameRUNTIME_CLASS(CMyView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
*指向新 CMultiDocTemplate 对象的指针用作 AddDocTemplate 的参数。 CMultiDocTemplate 构造函数的参数包括与文档类型的菜单和加速器相关联的资源 ID,以及 RUNTIME_CLASS 宏的三个用法。 RUNTIME_CLASS 为命名为其参数的 C++ 类返回 CRuntimeClass 对象。 传递给文档模板构造函数的三个 CRuntimeClass 对象提供在文档创建过程中创建指定类的新对象所需的信息。
该示例演示如何创建一个文档模板,该模板创建附加 CScribView 对象的 CScribDoc 对象。 视图由标准 MDI 子框架窗口构成。
某些应用程序支持多种文档类型。 例如,一个应用程序可能支持文本文档和图形文档。 在此类应用程序中,当用户选择“文件”菜单上的“新建”命令时,一个对话框会显示可能要打开的新文档类型的列表。 对于每种受支持的文档类型,应用程序使用不同的文档模板对象。 下图阐释了支持两种文档类型和显示多个打开的文档的 MDI 应用程序的配置。

文档模板由应用程序对象创建和维护。 在应用程序的 InitInstance 函数中执行的一个关键任务是构造一个或多个适当类型的文档模板。
文档模板创建中对此功能进行了介绍。 应用程序对象存储指向其模板列表中的每个文件模板的指针,并提供一个用于添加文件模板的接口。
如果需要支持两种或更多种文档类型,必须为每种文档类型添加一个对 AddDocTemplate 的额外调用。
根据文档模板在应用程序的文档模板列表中的位置,为每个文档模板注册一个图标。 文档模板的顺序由调用 AddDocTemplate 来添加它们的顺序决定。 MFC 假定应用程序中的第一个图标资源是应用程序图标,下一个图标资源是第一个文档图标,依此类推。
例如,文档模板是应用程序的三个图标资源中的第三个。 如果在应用程序中的索引 3 处有一个图标资源,则该图标用于文档模板。 否则,索引 0 处的图标则用作默认图标。
对于“文件”菜单上的命令,该框架可实现“New”和“Open”命令。 创建新文档及其关联的视图和框架窗口需要结合使用应用程序对象、文档模板、新创建的文档和新创建的框架窗口。 下表总结了哪些对象创建哪些内容。
创建者 创建
应用程序对象 文档模板
文档模板 文档
文档模板 框架窗口
框架窗口 视图
为了管理创建文档及其相关视图和框架窗口的复杂过程,框架使用两个文档模板类:用于 SDI 应用程序的 CSingleDocTemplate 和用于 MDI 应用程序的 CMultiDocTemplate。 CSingleDocTemplate 可以一次创建和存储一种类型的一个文档。 CMultiDocTemplate 保留一个类型的多个打开的文档的列表。


MFCLearn2.cpp: 定义应用程序的类行为。
// MFCLearn2.cpp: 定义应用程序的类行为。
//#include "pch.h"
#include "framework.h"
#include "afxwinappex.h"
#include "afxdialogex.h"
#include "MFCLearn2.h"
#include "MainFrm.h"#include "MFCLearn2Doc.h"
#include "MFCLearn2View.h"#ifdef _DEBUG
#define new DEBUG_NEW
#endif// CMFCLearn2AppBEGIN_MESSAGE_MAP(CMFCLearn2App, CWinApp)ON_COMMAND(ID_APP_ABOUT, &CMFCLearn2App::OnAppAbout)// 基于文件的标准文档命令ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
END_MESSAGE_MAP()
ID_FILE_NEW和ID_FILE_OPEN这两个消息直接调用父类CWinApp::OnFileNew和CWinApp::OnFileOpen函数
CWinApp 类
派生出 Windows 应用程序对象的基类。
注意,它是派生自CwinThread类的,说明它本身也是一个线程,一个程序的主线程。
class CWinApp : public CWinThread
应用程序对象提供成员函数,用于初始化应用程序(及其每个实例)和运行应用程序。
使用 Microsoft 基础类的每个应用程序只能包含一个派生自 CWinApp 的对象。 在构造其他 C++ 全局对象时会构造此对象,它在 Windows 调用 WinMain 函数时已可用,其中该函数由 Microsoft 基础类库提供。
从 CWinApp 中派生应用程序类时,替代 InitInstance 成员函数来创建应用程序的主窗口对象。
此宏将命令消息映射到成员函数。
ON_COMMAND(
id
,
memberFxn )
它指示哪个功能将处理来自命令用户界面对象(如菜单项或工具栏按钮)的命令消息。
当命令目标对象接收到具有指定ID的Windows WM_command消息时,ON_command将调用成员函数memberFxn来处理该消息。
使用ON_COMMAND将单个命令映射到成员函数。
使用ON_COMMAND_RANGE将一系列命令id映射到一个成员函数。
只有一个消息映射条目可以匹配给定的命令id。也就是说,不能将一个命令映射到多个处理程序。
在Windows的传统程序中,Windows消息是在窗口过程中的大型switch语句中处理的。MFC使用消息映射将直接消息映射到不同的类成员函数。为此,消息映射比虚拟函数更高效,并且它们允许由最合适的C++对象(应用程序、文档、视图等)处理消息。您可以映射单个消息或一系列消息、命令ID或控件ID。
WM_COMMAND消息——通常由菜单、工具栏按钮或加速器生成——也使用消息映射机制。MFC定义了程序中应用程序、框架窗口、视图和活动文档之间的命令消息的标准路由。如果需要,您可以覆盖此路由。
消息映射还提供了一种更新用户界面对象(如菜单和工具栏按钮)的方法,启用或禁用它们以适应当前上下文。
与基于 MS-DOS 的应用程序不同,基于Windows的应用程序是事件驱动的。 它们不会 (进行显式函数调用,例如 C 运行时库调用) 以获取输入。 相反,他们等待系统向其传递输入。
系统将应用程序的所有输入传递到应用程序中的各个窗口。 每个窗口都有一个函数,称为窗口过程,每当系统具有窗口的输入时,系统都会调用该函数。 窗口过程处理输入并将控件返回到系统。
如果顶级窗口停止响应消息超过几秒钟,系统将认为窗口不会响应。 在这种情况下,系统会隐藏窗口,并将其替换为具有相同 Z 顺序、位置、大小和视觉属性的幽灵窗口。 这样,用户就可以移动它、调整大小,甚至关闭应用程序。 但是,这些操作是唯一可用的操作,因为应用程序实际上没有响应。 在调试器模式下,系统不会生成虚影窗口。
系统以 消息的形式将输入传递给窗口过程。 消息由系统和应用程序生成。 系统在每个输入事件(例如,用户键入时、移动鼠标或单击滚动条等控件)生成消息。 系统还会生成消息,以响应应用程序带来的系统更改,例如应用程序更改系统字体资源的池或调整其窗口的大小。 应用程序可以生成消息,以指示其自己的窗口执行任务或与其他应用程序中的窗口通信。
系统使用一组四个参数将消息发送到窗口过程:一个窗口句柄、一个消息标识符和两个名为 消息参数的值。 窗口句柄标识消息的预期窗口。 系统使用它来确定应接收消息的窗口过程。
消息标识符是标识消息用途的命名常量。 当窗口过程收到消息时,它使用消息标识符来确定如何处理消息。 例如,消息标识符 WM_PAINT 告知窗口过程窗口的工作区已更改,必须重新绘制。
消息参数指定处理消息时窗口过程使用的数据或数据的位置。 消息参数的含义和值取决于消息。 消息参数可以包含整数、打包位标志、指向包含其他数据的结构的指针等。 当消息不使用消息参数时,它们通常设置为 NULL。 窗口过程必须检查消息标识符以确定如何解释消息参数。
两种类型的消息:
系统定义的消息
应用程序定义的消息
系统定义的消息
当系统与应用程序通信时,系统会发送或发布 系统定义的消息 。 它使用这些消息来控制应用程序的操作,并为要处理的应用程序提供输入和其他信息。 应用程序还可以发送或帖子系统定义的消息。 应用程序通常使用这些消息来控制通过使用预注册的窗口类创建的控件窗口的操作。
每个系统定义消息都具有唯一的消息标识符和在软件开发工具包 (SDK 中定义的相应符号 (常量) 头文件) 声明消息目的。 例如,窗口绘制其内容的 WM_PAINT 常量请求。
符号常量指定系统定义消息所属的类别。 常量前缀标识可以解释和处理消息的窗口类型。
Application-Defined消息
应用程序可以创建消息,供其自己的窗口使用,或与其他进程中的窗口通信。 如果应用程序创建自己的消息,则接收它们的窗口过程必须解释消息并提供适当的处理。
消息标识符值如下所示:
系统通过0x03FF (系统定义消息WM_USER - 1) 的值,在范围0x0000中保留消息标识符值。 应用程序不能将这些值用于专用消息。
范围中的值0x0400 (通过0x7FFF WM_USER) 的值可用于专用窗口类的消息标识符。
如果应用程序标记为版本 4.0,则可以通过专用消息的0xBFFF,在0x8000 (WM_APP) 范围内使用消息标识符值。
当应用程序调用 RegisterWindowMessage 函数以注册消息时,系统会在范围0xC000中返回消息标识符0xFFFF。 保证此函数返回的消息标识符在整个系统中是唯一的。 如果其他应用程序出于不同目的使用相同的消息标识符,则使用此函数可防止发生冲突。
消息路由
系统使用两种方法将消息路由到窗口过程:将消息发布到一个名为 消息队列的先出队列、一个临时存储消息的系统定义的内存对象,以及将消息直接发送到窗口过程。
已排队的消息
系统一次可以显示任意数量的窗口。 若要将鼠标和键盘输入路由到相应的窗口,系统使用消息队列。
系统为每个 GUI 线程维护单个系统消息队列和一个特定于线程的消息队列。 为避免为非 GUI 线程创建消息队列的开销,所有线程最初都是在没有消息队列的情况下创建的。 仅当线程首次调用某个特定用户函数时,系统才会创建特定于线程的消息队列:没有 GUI 函数调用会导致创建消息队列。
每当用户移动鼠标、单击鼠标按钮或键盘上的类型时,鼠标或键盘的设备驱动程序会将输入转换为消息,并将其放置在系统消息队列中。 系统一次从系统消息队列中删除消息,检查消息以确定目标窗口,然后将消息发布到创建目标窗口的线程的消息队列。 线程的消息队列接收线程创建的窗口的所有鼠标和键盘消息。 线程从队列中删除消息,并将系统定向到相应的窗口过程进行处理。
除了 WM_PAINT 消息、 WM_TIMER 消息和 WM_QUIT 消息之外,系统始终在消息队列末尾发布消息。 这可确保窗口先在正确的第一个中接收其输入消息,先传出 (FIFO) 序列。 但是 ,WM_PAINT 消息、 WM_TIMER 消息和 WM_QUIT 消息保留在队列中,并且仅在队列不包含其他消息时才转发到窗口过程。 此外,同一窗口的多个 WM_PAINT 消息合并为单个 WM_PAINT 消息,将工作区的所有无效部分合并为单个区域。 合并 WM_PAINT 消息可以减少窗口必须重新绘制其工作区内容的次数。
系统通过填充 MSG 结构并将其复制到消息队列,将消息发布到线程的消息队列。 MSG 中的信息包括:消息的预期窗口的句柄、消息标识符、两个消息参数、消息发布时间以及鼠标光标位置。 线程可以使用 PostMessage 或 PostThreadMessage 函数将消息帖子到其自己的消息队列或另一个线程的队列。
应用程序可以使用 GetMessage 函数从队列中删除消息。 若要检查消息而不将其从队列中删除,应用程序可以使用 PeekMessage 函数。 此函数使用有关消息的信息填充 MSG 。
从队列中删除消息后,应用程序可以使用 DispatchMessage 函数将消息定向到窗口过程进行处理。 DispatchMessage 使用指向之前调用 GetMessage 或 PeekMessage 函数填充的 MSG 的指针。 DispatchMessage 将窗口句柄、消息标识符和两个消息参数传递给窗口过程,但它不会传递消息发布时间或鼠标光标位置。 应用程序可以通过在处理消息时调用 GetMessageTime 和 GetMessagePos 函数来检索此信息。
当线程在其消息队列中没有消息时,线程可以使用 WaitMessage 函数向其他线程生成控制。 该函数会暂停线程,并且不会返回,直到新消息放置在线程的消息队列中。
可以调用 SetMessageExtraInfo 函数,将值与当前线程的消息队列相关联。 然后调用 GetMessageExtraInfo 函数以获取与 GetMessage 或PeekMessage 函数检索的最后一条消息关联的值。
未排队的消息
未排队的消息会立即发送到目标窗口过程,绕过系统消息队列和线程消息队列。 系统通常会发送非排队消息,以通知影响它的事件的窗口。 例如,当用户激活新的应用程序窗口时,系统会发送一系列消息,包括 WM_ACTIVATE、 WM_SETFOCUS和 WM_SETCURSOR。 这些消息通知窗口已激活,键盘输入将定向到窗口,并且鼠标光标已在窗口边框内移动。 当应用程序调用某些系统函数时,非排队消息也可能会导致。 例如,系统在应用程序使用 SetWindowPos 函数移动窗口后发送WM_WINDOWPOSCHANGED消息。
发送非排队消息的某些函数包括 BroadcastSystemMessage、BroadcastSystemMessageEx、SendMessage、SendMessageTimeout 和 SendNotifyMessage。
// CMFCLearn2App 构造CMFCLearn2App::CMFCLearn2App() noexcept
{// 支持重新启动管理器m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;
#ifdef _MANAGED// 如果应用程序是利用公共语言运行时支持(/clr)构建的,则: // 1) 必须有此附加设置,“重新启动管理器”支持才能正常工作。// 2) 在您的项目中,您必须按照生成顺序向 System.Windows.Forms 添加引用。System::Windows::Forms::Application::SetUnhandledExceptionMode(System::Windows::Forms::UnhandledExceptionMode::ThrowException);
#endif// TODO: 将以下应用程序 ID 字符串替换为唯一的 ID 字符串;建议的字符串格式//为 CompanyName.ProductName.SubProduct.VersionInformationSetAppID(_T("MFCLearn2.AppID.NoVersion"));// TODO: 在此处添加构造代码,// 将所有重要的初始化放置在 InitInstance 中
}
将所有重要的初始化放置在 InitInstance 中
CWinApp::SetAppID 显式设置应用程序的应用程序用户模型 ID。 应在向用户显示任何用户界面之前调用此方法(最佳位置是应用程序构造函数)。
SetAppID(_T("MFCLearn2.AppID.NoVersion"));
重启管理器是添加到 Visual Studio for Windows Vista 或更高版本操作系统的功能。 如果发生意外关闭或重启,重新启动管理器将为你的应用程序添加支持。 重新启动管理器的行为取决于应用程序的类型。 如果你的应用程序是文档编辑器,则重新启动管理器让应用程序能够自动保存任何打开文档的状态和内容,并在意外关闭后重启应用程序。 如果你的应用程序不是文档编辑器,则重新启动管理器将重启该应用程序,但无法默认保存应用程序的状态。
重启后,如果应用程序采用 Unicode 编码,则该应用程序将显示任务对话框。 如果是 ANSI 应用程序,则该应用程序将显示 Windows 消息框。 此时,用户可以选择是否要还原自动保存的文档。 如果用户不还原自动保存的文档,则重新启动管理器将放弃临时文件。
// 支持重新启动管理器m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;
// 唯一的 CMFCLearn2App 对象CMFCLearn2App theApp;
InitInstance 成员函数
Windows 操作系统允许您运行同一应用程序的多个副本(也称为“实例”)。 每当应用程序的新实例启动时,WinMain 都将调用 InitInstance。
MFC 应用程序向导创建的标准 InitInstance 实现将执行以下任务:
// CMFCLearn2App 初始化BOOL CMFCLearn2App::InitInstance()
{// 如果一个运行在 Windows XP 上的应用程序清单指定要// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,//则需要 InitCommonControlsEx()。 否则,将无法创建窗口。INITCOMMONCONTROLSEX InitCtrls;InitCtrls.dwSize = sizeof(InitCtrls);// 将它设置为包括所有要在应用程序中使用的// 公共控件类。InitCtrls.dwICC = ICC_WIN95_CLASSES;InitCommonControlsEx(&InitCtrls);CWinApp::InitInstance();EnableTaskbarInteraction(FALSE);// 使用 RichEdit 控件需要 AfxInitRichEdit2()// AfxInitRichEdit2();// 标准初始化// 如果未使用这些功能并希望减小// 最终可执行文件的大小,则应移除下列// 不需要的特定初始化例程// 更改用于存储设置的注册表项// TODO: 应适当修改该字符串,// 例如修改为公司或组织名SetRegistryKey(_T("应用程序向导生成的本地应用程序"));LoadStdProfileSettings(4); // 加载标准 INI 文件选项(包括 MRU)// 注册应用程序的文档模板。 文档模板// 将用作文档、框架窗口和视图之间的连接CSingleDocTemplate* pDocTemplate;pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CMFCLearn2Doc),RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口RUNTIME_CLASS(CMFCLearn2View));if (!pDocTemplate)return FALSE;AddDocTemplate(pDocTemplate);// 分析标准 shell 命令、DDE、打开文件操作的命令行CCommandLineInfo cmdInfo;ParseCommandLine(cmdInfo);// 调度在命令行中指定的命令。 如果// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。if (!ProcessShellCommand(cmdInfo))return FALSE;// 唯一的一个窗口已初始化,因此显示它并对其进行更新m_pMainWnd->ShowWindow(SW_SHOW);m_pMainWnd->UpdateWindow();return TRUE;
}
运行在 Windows XP 上
// 如果一个运行在 Windows XP 上的应用程序清单指定要// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,//则需要 InitCommonControlsEx()。 否则,将无法创建窗口。INITCOMMONCONTROLSEX InitCtrls;InitCtrls.dwSize = sizeof(InitCtrls);// 将它设置为包括所有要在应用程序中使用的// 公共控件类。InitCtrls.dwICC = ICC_WIN95_CLASSES;InitCommonControlsEx(&InitCtrls);
InitCommonControlsEx确保加载公共控制DLL(Comctl32.DLL),并从DLL中注册特定的公共控制类。在创建公共控件之前,应用程序必须调用此函数。
父类的InitInstance
CWinApp::InitInstance();
EnableTaskbarInteraction 启用任务栏交互。
EnableTaskbarInteraction(FALSE);
CWinApp::EnableTaskbarInteraction
启用任务栏交互。
BOOL EnableTaskbarInteraction(BOOL bEnable = TRUE);
参数
bEnable
指定是应启用 (TRUE) 还是禁用 (FALSE) 与 Windows 7 任务栏的交互。
返回值
如果可启用或禁用任务栏交互,则返回 TRUE。
注解
必须在创建主窗口之前调用此方法,否则它将断言并返回 FALSE。
使用 RichEdit 控件
// 使用 RichEdit 控件需要 AfxInitRichEdit2()
// AfxInitRichEdit2();
如果在对话框中使用 Rich Edit 控件(无论应用程序是 SDI、MDI 还是基于对话框),必须先调用一次
AfxInitRichEdit,然后才能显示对话框。 调用此函数的一个典型位置位于程序的 InitInstance 成员函数中。
您无需在每次显示对话框时调用它,仅第一次需要。 如果您使用 AfxInitRichEdit,则不必调用 CRichEditView。
Rich Edit 控件 (CRichEditCtrl) 提供一个编程接口来设置文本格式。 但是,应用程序必须实现使用户可进行格式设置操作所需的任何用户界面组件。 也就是说,格式文本编辑控件支持更改选定文本的字符或段落特性。 一些字符特性的示例包括粗体、斜体、字体系列和磅值。 段落特性的示例包括对齐、边距和制表位。 但是,提供用户界面由您决定,无论提供工具栏按钮、菜单项还是格式字符对话框。 还有用于查询当前选择的特性的格式文本编辑控件的函数。 使用这些函数可显示特性的当前设置,例如,如果选择具有粗体字符格式设置特性,则在命令 UI 上设置复选标记。
未完下一节继续