简介:《深入浅出MFC(第二版)》是台湾编程专家候捷所著,全面覆盖MFC框架的各个方面,深入解析C++类库,详细讲解Windows API封装、事件处理机制、文档/视图架构等核心内容,并提供大量实践案例。本书适合各级VC++程序员,帮助他们在Windows平台上高效构建和优化应用程序。
1. MFC基础知识与核心类介绍
MFC(Microsoft Foundation Classes)是微软公司提供的一套面向对象的C++类库,广泛用于Windows应用程序开发。本章将为读者介绍MFC的基础知识和核心类,帮助开发者快速理解并掌握MFC编程。
1.1 MFC的基本概念
MFC通过封装Windows API,提供了丰富的类来处理窗口、控件、图形设备接口(GDI)、数据和网络通信等任务。开发者无需直接调用底层的API,而是通过继承和重写MFC类来实现功能。
1.2 MFC的核心类
在MFC中, CWinApp
、 CFrameWnd
、 CDialog
、 CView
和 CDocument
是一些关键的核心类。 CWinApp
类代表了应用程序本身,而 CFrameWnd
用于创建主框架窗口。 CDialog
支持对话框编程, CView
和 CDocument
一起构成了文档/视图架构的基础。
通过这些核心类,MFC以一种方便的方式实现了Windows编程的复杂性,从而让开发者能够专注于应用程序的业务逻辑,而不是底层细节。
2. 事件处理机制与消息映射
2.1 MFC中的消息处理流程
2.1.1 消息队列与消息循环
在MFC(Microsoft Foundation Classes)框架中,消息处理机制是整个事件驱动编程的核心。一个Windows应用程序接收用户输入或其他事件的方式,是通过消息的形式。这些消息被放置在消息队列中,应用程序通过消息循环来处理这些消息。
消息队列是操作系统为应用程序提供的一个存储消息的缓冲区,所有发往该应用程序窗口的消息都会被放入这个队列中。当应用程序运行时,它会不断地从这个消息队列中检索消息,并将其发送到相应的窗口函数中处理。
消息循环是一个无限循环,它通常位于WinMain函数中。以下是消息循环的基本结构:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GetMessage
函数从消息队列中检索消息,如果成功检索到消息,函数返回非零值。 TranslateMessage
函数转换虚拟按键消息为字符消息,而 DispatchMessage
函数将消息发送到窗口程序的消息处理函数。
2.1.2 消息映射机制详解
MFC消息映射机制是一种将Windows消息映射到类成员函数的方法,以便程序员可以针对特定消息编写事件处理代码。MFC通过消息映射宏将消息映射到类成员函数,从而实现消息的响应。
消息映射宏主要包括 BEGIN_MESSAGE_MAP
, ON_COMMAND
, ON_WM_PAINT
等。这些宏定义在类的消息映射表中,根据消息类型调用对应的成员函数。
例如,下面的代码展示了如何定义一个消息映射:
BEGIN_MESSAGE_MAP(CMyClass, CFrameWnd)
ON_WM_PAINT()
ON_COMMAND(ID_FILE_NEW, &CMyClass::OnFileNew)
END_MESSAGE_MAP()
在上面的宏中, CMyClass
是类名, CFrameWnd
是基类, ON_WM_PAINT
和 ON_COMMAND
分别处理窗口绘制消息和菜单命令消息。实际的消息处理函数会根据消息的标识符被调用。
2.2 事件驱动编程模型
2.2.1 事件与事件映射
在MFC中,事件是一种发生的事情,如用户按键、移动鼠标、窗口大小改变等。当事件发生时,系统生成消息并将消息发送到拥有该事件的窗口或控件的消息队列中。消息映射则是一种机制,它将消息与相应的事件处理函数关联起来。
事件映射是通过在类的定义中使用宏来实现的。开发者可以为特定的事件编写处理函数,并使用宏指定哪个函数来处理哪个事件。
2.2.2 常用控件的事件处理
MFC中包含了多种预定义控件,如按钮、编辑框、列表框等。这些控件拥有自己的事件,例如按钮点击事件、编辑框内容改变事件等。为了处理这些事件,开发者需要在消息映射中添加相应的映射项。
以按钮点击事件为例,可以使用 ON BN_CLICKED
宏来映射按钮的点击消息到一个处理函数。例如:
ON_BN_CLICKED(IDC_MY_BUTTON, &CMyDialog::OnBnClickedMyButton)
IDC_MY_BUTTON
是按钮控件的ID, CMyDialog
是包含此按钮的对话框类, OnBnClickedMyButton
是处理按钮点击的成员函数。
2.3 实际案例:消息映射的应用
2.3.1 设计自定义的消息处理函数
设计自定义消息处理函数时,首先需要了解消息的类型和需要处理的具体事件。例如,如果要处理窗口的关闭事件,可以使用 WM_CLOSE
消息,并通过 ON_WM_CLOSE()
宏来映射。
创建自定义消息处理函数的步骤如下:
- 在类的头文件中,声明消息处理函数。
- 在类的实现文件中,定义消息处理函数。
- 在类的消息映射中,添加消息映射项。
例如,要添加关闭按钮的处理函数,可以在类的实现文件中添加如下代码:
void CMyDialog::OnClose()
{
// 释放资源、保存设置等
CDialogEx::OnClose();
}
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
// 其他消息映射...
ON_WM_CLOSE()
END_MESSAGE_MAP()
2.3.2 调试和测试消息处理代码
调试和测试是确保消息处理函数正常工作的关键步骤。在MFC中,可以使用Visual Studio提供的调试工具,如断点、步进、变量查看等来帮助调试。
测试消息处理函数时,可以模拟用户交互(如点击按钮、输入文本等),或者直接发送消息到窗口函数。使用 PostMessage
函数可以向窗口发送消息:
PostMessage(WM_CLOSE);
在调试过程中,检查代码逻辑和执行路径,确保在不同的消息和事件下,消息处理函数能被正确调用并执行预期的操作。
使用Visual Studio的调试器跟踪程序的执行流,可以帮助开发者确认消息是否被正确地送到了目标函数,并在消息处理过程中是否有异常情况发生。
通过以上内容,我们已经了解了MFC中消息处理机制的核心概念,包括消息队列、消息循环、事件映射、以及如何设计自定义消息处理函数和进行调试测试。这些知识点的深入理解,对于开发高效稳定、响应用户的Windows应用程序是至关重要的。
3. 文档/视图架构设计与管理
文档/视图架构是MFC应用程序的核心部分,它负责维护和管理数据以及提供用户界面的显示。深入了解如何高效地使用和扩展文档与视图类,对于创建功能丰富且响应迅速的应用程序至关重要。
3.1 文档/视图架构概述
3.1.1 架构的基本概念和组成
文档/视图架构(Document/View Architecture)将数据处理与用户界面分离开来,其中“文档”负责数据的存储与管理,而“视图”则负责将文档中的数据以用户界面的形式展现出来。这种设计模式不仅使得代码结构清晰,也便于实现多视图与单文档或多文档的设计。
架构组成
- 文档类(CDocument) :负责文档数据的管理,它通常包含数据的CRuntimeClass对象以及对数据进行加载和保存的方法。
- 视图类(CView) :作为文档和用户界面之间的桥梁,视图类负责响应用户的输入,查询文档数据并将其渲染到屏幕上。
- 框架窗口(CMDIFrameWnd) :负责整个应用程序窗口的管理,如窗口大小调整、菜单处理等。
- 子框架窗口(CMDIChildWnd或CMDICHildWnd) :管理子窗口,特别是文档/视图结构中的视图窗口。
3.1.2 文档与视图的关联方式
文档与视图的关联是通过MFC框架的内部机制实现的。当一个视图被创建时,它会关联一个文档对象,这种关联通常在视图的构造函数中通过调用 SetDocument
方法来建立。
文档/视图关联的流程
- 用户通过菜单或工具栏选择打开一个新文档或已有文档。
- 框架窗口创建一个文档对象,并尝试将其打开。
- 如果文档成功打开,框架会创建一个新的视图对象,或者将已有视图关联到该文档。
- 视图对象的
OnInitialUpdate
方法会被调用,它负责执行与视图有关的初始化任务。
3.2 文档类的使用和扩展
3.2.1 文档类的构造和存储
文档类通常从CDocument类派生,开发者可以在派生类中添加自己的数据成员以及特定的函数来处理文档数据。
构造函数和存储
- 构造函数 :通常不直接使用文档类的构造函数,因为框架会在需要时调用它。如果需要在构造时进行特定的初始化,则需要重写
OnNewDocument
方法。 - 存储 :文档数据的存储通常通过
Serialize
函数来实现。Serialize
方法决定了如何读取和保存文档数据到存储介质(如文件)。
BOOL CMyDocument::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// 对象数据的保存
ar << m_myData;
}
else
{
// 对象数据的加载
ar >> m_myData;
}
return TRUE;
}
3.2.2 实现文档的序列化和反序列化
序列化和反序列化是文档/视图架构的核心部分。开发者可以通过重写 Serialize
函数来实现数据的存储和加载。
序列化与反序列化的实现
- 存储数据 :当用户选择保存文档时,框架调用
Serialize
函数,并传入一个指向CArchive
对象的引用,该对象表示一个用于存储数据的文件或内存区域。 - 加载数据 :当用户打开或加载文档时,框架同样调用
Serialize
函数,但这次是以读取模式。
在实际应用中,您可能需要处理不同类型的文档或视图,因此必须确保 Serialize
方法能够正确处理每种情况。
3.3 视图类的设计与实现
3.3.1 视图类的绘制机制
视图类负责把文档中的数据转化为可视信息。这通常通过处理WM_PAINT消息来完成。
绘制流程
- OnDraw :重写
OnDraw
函数,用于输出文档数据到视图窗口。 - 设备上下文(CDC) :使用
CPaintDC
(在OnDraw
中使用)或CClientDC
(在消息响应函数外使用)类提供的设备上下文来进行绘图。
3.3.2 视图与用户交互的处理
视图类还负责处理用户的交互操作,如鼠标点击、键盘输入等。
用户交互的处理
- 消息映射 :通过消息映射机制来响应用户操作。例如,视图可以通过映射
OnLButtonDown
来响应鼠标左键点击事件。 - 更新机制 :视图的更新通常通过调用
UpdateWindow
或InvalidateRect
等函数来触发。当文档数据发生改变时,需要通知视图进行更新。
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
CView::OnLButtonDown(nFlags, point);
// 处理鼠标点击事件,例如选中视图中的特定对象
}
以上内容仅为每个子章节部分的简要概述,每部分内容深度分析和实例应用需要更详尽的说明,以达到章节内容的深度要求。
4. 控件与对话框创建与应用
在本章节,我们将深入探讨如何在MFC(Microsoft Foundation Classes)应用程序中创建和应用控件以及对话框。控件和对话框是构成用户界面的基本元素,通过本章节的学习,读者将掌握创建这些界面组件的关键技术。
4.1 常用控件的使用与扩展
4.1.1 基本控件介绍
MFC提供了一系列基本控件,如按钮、编辑框、列表框、组合框等,它们是构成应用程序用户界面的基础。了解这些控件的属性、方法和事件,是进行有效用户界面设计的前提。
- 按钮控件 :按钮是最常见的控件之一,用于触发事件。按钮可以是命令按钮、单选按钮或复选框。
- 编辑控件 :编辑控件允许用户输入和编辑文本信息。它既可以是单行的也可以是多行的。
- 列表框控件 :列表框提供了一个选项列表,用户可以从中选择一个或多个项目。
- 组合框控件 :组合框是一个编辑控件和一个下拉列表框的组合,提供了用户输入和选择数据的便捷方式。
为了创建这些控件,开发人员需要在对话框编辑器中拖放控件或使用相应的类库函数。例如,创建一个按钮控件可以通过调用 CButton
类,并使用 Create
方法来实现。
// 创建一个按钮控件
CButton m_btn;
m_btn.Create("Click Me", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
CRect(50, 50, 150, 100), this, IDC_MY_BUTTON);
在上述代码中, Create
方法用于初始化按钮控件,其中 "Click Me"
是按钮的标题, WS_VISIBLE
和 WS_CHILD
是窗口样式, BS_PUSHBUTTON
是按钮的样式, CRect
是按钮的位置和大小, this
指向父窗口, IDC_MY_BUTTON
是按钮控件的唯一标识符。
4.1.2 高级控件的应用案例
MFC还包含了一些高级控件,如树形控件、网格控件、进度条控件等,它们提供了更为丰富和灵活的用户界面功能。
- 树形控件 :用于显示层次结构数据,如目录结构、组织架构等。
- 网格控件 :提供一个表格视图,可以用来显示和编辑二维数据。
- 进度条控件 :用于显示操作进度,提高用户体验。
以树形控件为例,创建树形控件同样可以在对话框编辑器中通过控件工具箱添加,或通过编程方式使用 CTreeCtrl
类来实现。
// 创建一个树形控件
CTreeCtrl m_treeCtrl;
m_treeCtrl.Create(WS_VISIBLE | WS_CHILD, CRect(10, 10, 200, 200), this, IDC_MY_TREE);
在上述代码中, m_treeCtrl
对象被创建并初始化为树形控件。 Create
方法定义了控件的可见性、父窗口、位置和大小以及唯一标识符。
通过这些高级控件的应用,可以大幅增强应用程序的交互性和视觉吸引力。
4.2 对话框的设计与管理
4.2.1 对话框的创建和初始化
对话框是与用户交互的窗口,可以包含各种控件。在MFC中,对话框的创建和初始化通常涉及以下几个步骤:
- 创建对话框资源 :使用资源编辑器设计对话框,并分配一个唯一的资源ID。
- 声明对应的类 :在类视图中为对话框添加一个新类,该类继承自
CDialog
或派生类。 - 初始化对话框 :重写
OnInitDialog
方法,在其中可以进行控件的初始化设置,如设置文本、启用/禁用控件等。
BOOL CMyDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// 设置对话框标题
SetWindowText(_T("My Dialog Title"));
// 初始化控件
CWnd* pWnd = GetDlgItem(IDC_MY_EDIT);
if (pWnd != nullptr)
{
pWnd->SetWindowText(_T("Initial text"));
}
return TRUE; // return TRUE unless you set the focus to a control
}
在这段代码中, OnInitDialog
方法用于初始化对话框。 SetWindowText
方法用于设置对话框标题和控件文本。
4.2.2 对话框与主窗口的交互
对话框通常不是独立存在的,它需要与应用程序的主窗口进行交互。这包括数据的传递、事件的响应等。
- 数据传递 :对话框可以通过传递数据到主窗口的方式进行交互。这通常使用
GetDlgItemInt
、GetWindowText
等函数来实现。 - 事件处理 :对话框可以处理来自主窗口的事件,例如按钮点击事件。这通过消息映射和事件处理函数来完成。
void CMyDialog::OnBnClickedOk()
{
// 获取编辑控件中的文本数据
CString strData;
GetDlgItemText(IDC_MY_EDIT, strData);
// 将数据传递回主窗口
CMyApp* pApp = (CMyApp*)AfxGetApp();
pApp->m_strData = strData;
// 关闭对话框
EndDialog(0);
}
在这个示例中,当用户点击确定按钮时, OnBnClickedOk
方法会被触发。该方法读取编辑控件中的文本数据,并通过全局变量将其传递给应用程序对象,然后关闭对话框。
4.3 实践项目:控件与对话框的综合应用
4.3.1 项目需求分析与设计
为了实践控件和对话框的应用,我们需要一个具体的项目需求。假设我们需要设计一个简单的用户信息输入界面,其中包含:
- 一个树形控件来显示用户的组织结构。
- 一个编辑框供用户输入姓名。
- 一个组合框供用户选择性别。
- 一个按钮用来提交用户信息。
- 一个进度条控件显示用户信息提交的进度。
在设计阶段,我们需要考虑每个控件的布局、属性设置以及与主窗口的交互逻辑。
4.3.2 编码实现与测试
在编码实现阶段,需要逐步创建和配置每个控件,并编写相应的事件处理函数。
// 示例:处理树形控件项点击事件
void CUserInfoDialog::OnSelchangeTree()
{
HTREEITEM hItem = m_treeCtrl.GetSelectedItem();
// 根据选中项获取用户信息,并显示在对话框中
// ...
}
在测试阶段,需要逐一测试每个控件和对话框的功能是否正常工作。重点测试数据传递、事件响应以及用户界面的流畅性。
通过以上步骤,本章节讲述了MFC中控件与对话框创建与应用的全过程。通过理论学习与实践项目相结合,读者应能够独立完成基于MFC的应用程序界面设计与开发。
5. 动态链接库(DLL)与ActiveX开发
在现代软件开发中,模块化和可重用性是关键要素。动态链接库(DLL)和ActiveX控件是实现这些目标的强大工具。本章将深入探讨DLL和ActiveX开发的基础知识和高级应用,为希望深入理解这些技术并将其应用于实际项目的读者提供指导。
5.1 DLL基础与MFC扩展DLL
5.1.1 DLL的工作原理
DLL是一种存储可由多个程序同时使用的代码和数据的库。DLL通过提供一种机制,使得程序可以共享一组代码和资源,从而减少内存占用并提高运行效率。
在Windows操作系统中,DLL文件通常具有.dll扩展名。当程序执行到需要调用DLL中函数的代码时,Windows加载器会动态地将DLL映射到进程的地址空间,进行链接和调用。与静态链接库(.lib文件)不同的是,DLL中的函数和数据是在运行时解析的,这意味着DLL可以被不同的程序共享,并且可以在不重新编译整个应用程序的情况下更新。
5.1.2 MFC扩展DLL的特点和创建
MFC扩展DLL是一种特殊的DLL类型,它允许DLL使用MFC类库,但不要求调用它的应用程序使用MFC。它通常包含一个或多个从 CWinApp
派生的类,用于管理DLL的初始化和终止。
创建MFC扩展DLL需要使用Visual Studio的项目向导,选择“MFC应用程序”并勾选“使用MFC的动态链接库”选项。扩展DLL项目将提供一个 DllMain
函数,这是DLL的入口点函数,用于处理DLL加载和卸载事件。
在编写DLL代码时,应尽量避免全局变量和静态数据,因为它们在DLL和使用它的应用程序之间共享,可能会导致数据冲突。相反,应使用函数参数和返回值来传递数据。
// DllMain的简单示例
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
// 处理DLL加载和卸载逻辑
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
// 初始化代码
break;
case DLL_PROCESS_DETACH:
// 清理代码
break;
}
return TRUE; // 成功加载
}
5.2 ActiveX控件的创建与应用
5.2.1 ActiveX控件的基本概念
ActiveX控件是一种可在互联网上分发的可重用组件,它允许开发者创建可以在Web页面上运行的富客户端应用程序。ActiveX控件通常用于创建具有丰富交互功能的表单和界面。
ActiveX控件基于COM(组件对象模型)技术,因此它们必须实现一套特定的接口。ActiveX控件可以包含自己的用户界面,可以响应事件,并且可以与宿主应用程序进行复杂的数据交换。
5.2.2 开发ActiveX控件的步骤和技巧
开发ActiveX控件通常涉及以下步骤:
- 规划控件功能和接口: 确定控件应该提供哪些功能,以及如何设计公共接口。
- 创建COM类: 使用Visual Studio的类向导创建COM类,并添加必要的方法和属性。
- 实现功能: 编写代码实现类的方法和属性,处理用户界面和事件。
- 注册控件: 编写注册脚本以便在系统注册表中注册控件,使其可以在Web浏览器或其他支持ActiveX的宿主中运行。
在实现ActiveX控件时,需要注意安全问题,尤其是在Web环境中。应确保控件不会执行未授权的操作,并且在设计用户界面时考虑用户体验。
5.3 DLL与ActiveX的高级应用
5.3.1 优化DLL的加载和卸载
优化DLL的加载和卸载可以提高应用程序的性能和稳定性。为了避免DLL地狱(DLL版本冲突问题),可以采取以下措施:
- 使用版本控制: 确保DLL具有版本号,以便在新版本发布时,能够清楚地识别和管理不同版本。
- 避免全局变量和静态数据: 如前所述,全局变量和静态数据可能导致数据冲突和资源泄漏。
- 使用延迟加载: 仅在确实需要时才加载DLL,可以减少应用程序的启动时间和内存占用。
5.3.2 ActiveX控件在Web中的应用实例
ActiveX控件的一个典型应用场景是在线表单填写和富媒体播放器。例如,一个简单的ActiveX控件可以实现一个在线签名工具,用户可以通过鼠标或触摸屏在Web页面上签名。
为了在Web中使用ActiveX控件,需要在HTML中嵌入控件,并在客户端机器上安装控件。控件可以响应事件,如鼠标点击和键盘输入,并与Web应用程序交换数据。
<!-- HTML中的ActiveX控件嵌入示例 -->
<object id="ActiveXControl" classid="clsid:GUID" width="200" height="200">
<!-- ActiveX控件未安装时的回退内容 -->
<p>请安装ActiveX控件以启用此功能。</p>
</object>
在安全方面,开发ActiveX控件时应遵循最佳实践,包括代码签名和使用安全功能,如代码访问安全(CAS)和权限请求,以确保在各种用户环境中安全运行。
通过以上章节,我们了解了DLL和ActiveX的基础知识、创建和高级应用。读者应能够利用这些信息,在软件开发中实现更高效的模块化和可重用性。接下来,我们可以探索其他有趣的技术话题,如MFC架构的深入分析或在实际项目中应用这些技术的最佳实践。
简介:《深入浅出MFC(第二版)》是台湾编程专家候捷所著,全面覆盖MFC框架的各个方面,深入解析C++类库,详细讲解Windows API封装、事件处理机制、文档/视图架构等核心内容,并提供大量实践案例。本书适合各级VC++程序员,帮助他们在Windows平台上高效构建和优化应用程序。