MFC是vc+的核心部分,需要一定的编程功底。
Windows编程基础
编制一个功能强大和易操作的Windows应用程序所需要的代码肯定会比一般的C++程序要多得多,但并不是所有的代码都需要自己从头开始编写,因为Visual C++不仅提供了常用的Windows应用程序的基本框架,而且可以在框架程序中直接调用Win32 API(Application Programming
Interface, 应用程序接口)函数。这样,用户仅需要在相应的框架位置中添加自己的代码或修改部分代码就可实现Windows应用程序的许多功能。
简单的Windows应用程序
先来看一个最简单的Windows应用程序Ex_HelloMsg。
[例Ex_HelloMsg] 一个简单的Windows应用程序
#include <windows.h>
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MessageBox (NULL, "你好,我的Visual C++世界!", "问候", 0) ;
return 0 ;
}
在Visual C++ 6.0运行上述程序需要进行以下步骤:
(1) 选择“文件”→“新建”菜单命令,显示出“新建”对话框。在“工程”标签页面的列表框
中,选中Win32 Application项。
(2) 在工程编辑框中键入Win32应用程序项目名称Ex_HelloMsg。在“位置”编辑框中直
接键入文件夹名称,或单击浏览按钮选择一个已有的文件夹。
(3) 单击[确定]按钮继续。一个询问项目类型的Win32应用程序向导将被显示,选中An
empty project项。单击[完成]按钮,系统将显示该应用程序向导的创建信息,单击[确
定]按钮系统将自动创建此应用程序。
(4) 再次选择“文件”→“新建”菜单命令,显示出“新建”对话框。在“文件”标签页面左边的
列表框中选择C++ Source File项,在右边的文件框中键入Ex_HelloMsg.cpp,单击
[确定]按钮。
[例Ex_HelloMsg] 一个简单的Windows应用程序
(5) 输入上面的代码,运行程序,结果如图3.1所示。
从上面的程序可以看出:
● C++控制台应用程序以main函数作为进入程序的初始入口点,但在Windows应用程
序中,main主函数被WinMain函数取代。WinMain函数的原型如下:
int WINAPI WinMain (
HINSTANCE hInstance, // 当前实例句柄
HINSTANCE hPrevInstance, // 前一实例句柄
LPSTR lpCmdLine, // 指向命令行参数的指针
int nCmdShow) // 窗口的显示状态
所谓句柄是一个标识Windows资源(如菜单、图标、窗口等)和设备等对象的数据指针类型。通常,一个句柄变量可用来对系统中某些资源的间接引用。
●每一个C++ Windows应用程序都需要Windows.h头文件,它还包含了其他的一些Windows头文件。这些头文件定义了Windows的所有数据类型、函数调用、数据结构和符号常量。
●程序中,MessageBox是一个Win32 API函数,用来弹出一个消息对话框。该函数第一个参数用来指定父窗口句柄,即对话框所在的窗口句柄。第二、三个参数分别用来指定显示的消息内容和对话框窗口的标题,最后一个参数用来指定在对话框中显示的按钮。
[例Ex_HelloWin] 一个完整的Windows应用程序
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); // 窗口过程
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd ; // 窗口句柄
MSG msg ; // 消息
WNDCLASS wndclass ; // 窗口类
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = "HelloWin"; // 窗口类名
if (!RegisterClass (&wndclass))
{ // 注册窗口
MessageBox (NULL, "窗口注册失败!", "HelloWin", 0) ;
return 0 ;
}
hwnd = CreateWindow ("HelloWin", // 窗口类名
"我的窗口", // 窗口标题
WS_OVERLAPPEDWINDOW, // 窗口样式
CW_USEDEFAULT, // 窗口最初的 x 位置
CW_USEDEFAULT, // 窗口最初的 y 位置
CW_USEDEFAULT, // 窗口最初的 x 大小
CW_USEDEFAULT, // 窗口最初的 y 大小
NULL, // 父窗口句柄
NULL, // 窗口菜单句柄
hInstance, // 应用程序实例句柄
NULL) ; // 创建窗口的参数
ShowWindow (hwnd, nCmdShow) ; // 显示窗口
UpdateWindow (hwnd) ; // 更新窗口,包括窗口的客户区
// 进入消息循环:当从应用程序消息队列中检取的消息是WM_QUIT时,则退出循环。
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ; // 转换某些键盘消息
DispatchMessage (&msg) ; // 将消息发送给窗口过程,这里是WndProc
}
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE: // 窗口创建产生的消息
return 0 ;
case WM_LBUTTONDOWN:
MessageBox (NULL, "你好,我的Visual C++世界!", "问候", 0) ;
return 0 ;
case WM_DESTROY: // 当窗口关闭时产生的消息
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ; // 执行默认的消息处理
}
程序运行后,单击鼠标左键,就会弹出一个对话框,结果如图3.2所示
窗口过程函数WndProc用来接收和处理各种不同的消息,而主函数WinMain通常要完成以下几步工作:
(1) 调用API函数RegisterClass注册应用程序的窗口类。
(2) 调用相关API函数创建和显示窗口,并进行其它必要的初始化处理。其中,函数CreateWindow用来创建已注册窗口类的窗口。Windows每一个窗口都有一些基本属性,如窗口标题、窗口位置和大小、应用程序图标、鼠标指针、菜单和背景颜色等。窗口类就是充当这些属性的模板。
(3) 创建和启动应用程序的消息循环。Windows应用程序接受各种不同的消息,包括键盘消息、鼠标以及窗口产生的各种消息。Windows系统首先将消息放入消息队列中,应用程序的消息循环就是从应用程序的消息队列中检取消息,并将消息发送相应的窗口过程函数中作进一步处理。API函数GetMessage 和DispatchMessage就是起到这样的作用。
(4) 如果接收到WM_QUIT消息,则退出应用程序。
图3.3 Windows应用程序的基本流程
Windows编程特点
一个完整的Windows应用程序除了WinMain函数外,还包含用于处理用户动作和
窗口消息的窗口函数。这不同于一个C++的控制台应用程序,可以将整个程序包
含在main函数中。事实上,它们的区别还远不止这些,不久还会发现一个
Windows应用程序还常常具有这样的一些特性:
● 消息驱动机制
●图形设备接口(GDI)
●基于资源的程序设计
●动态链接库
●进程和线程
1. 消息驱动机制
在Windows操作环境中,无论是系统产生的动作或是用户运行应用程序产生的动
作,都称为事件(Events)产生的消息(Message)。例如,在Windows 桌面(传统风
格)上,双击应用程序的快捷图标,系统就会执行该应用程序。在Windows的应
用程序中,也是通过接收消息、分发消息、处理消息来和用户进行交互的。
这种消息驱动的机制是Windows编程的最大特点。需要注意的是,许多Windows
消息都经过了严格的定义,并且适用于所有的应用程序。例如,当用户按下鼠标
的左键时系统就会发送WM_LBUTTONDOWN消息,而当用户敲了一个字符键时
系统就会发送WM_CHAR消息,若用户进行菜单选择或工具按钮单击等操作时,
系统又会相应地发送WM_COMMAND消息给相应的窗口等等。
2. 图形设备接口(GDI)
在传统的DOS环境中,想要在打印机上打印一幅图形是一件非常复杂的事情,因
为用户必须根据打印机类型和指令规则向打印机输送数据。而Windows则提供了
一个抽象的接口,称为图形设备接口(Graphical Device Interface,简称GDI),
使得用户直接利用系统的GDI函数就能方便实现输入或输出,而不必关心与系统
相连的外部设备的类型。
3. 基于资源的程序设计
Windows应用程序常常包含众多图形元素,例如光标、菜单、工具栏、位图、对
话框等,在Windows环境下,每一个这样的元素都作为一种可以装入应用程序的
资源来存放。这些资源就像C++程序中的常量一样,可以被编辑、修改,也可以
被其他应用程序所共享。Visual C++ 6.0中就提供这样的编辑器,可“所见即所
得”地对这些不同类型的资源进行设计、编辑等。
4. 动态链接库
动态链接库提供了一些特定结构的函数,能被应用程序在运行过程中装
入和连接,且多个程序可以共享同一个动态链接库,这样就可以大大节
省内存和磁盘空间。从编程角度来说,动态链接库可以提高程序模块的
灵活性,因为它本身是可以单独设计、编译和调试的。
Windows提供了应用程序可利用的丰富的函数调用,大多数用于实现其
用户界面和在显示器上显示的文本和图形,都是通过动态链接库来实现
的。这些动态链接库是一些具有.DLL扩展名或者有时是.EXE扩展名的文
件。
在Windows操作系统中,最主要的DLL有KERNEL32.DLL、GDI32.DLL
和USER32.DLL三个模块。其中,KERNEL32用来处理存储器低层功
能、任务和资源管理等Windows核心服务; GDI32用来提供图形设备接
口,管理用户界面和图形绘制,包括Windows元文件、位图、设备描述
表和字体等;而USER32负责窗口的管理,包括消息、菜单、光标、计
时器以及其它与控制窗口显示相关的一些功能。
5 . 进程和线程
在32位Windows多任务操作系统中,采用了进程和线程的管理模式。进程是装入
内存中正在执行的应用程序。进程包括私有的虚拟地址空间、代码、数据及其它
操作系统资源,如文件、管道以及对该进程可见的同步对象等。进程包括了一个
或多个在进程上下文内运行的线程。线程是操作系统分配CPU时间的基本实体。
线程可以执行应用程序代码的任何部分,包括当前正在被其它线程执行的那些部
分。同一进程的所有线程共享同样的虚拟地址空间、全局变量和操作系统资源。
在一个应用程序中,可以包括一个或多个进程,每个进程由一个或多个线程构
成。
表3.1列出了一些在Windows编程中常用的基本数据类型
编制一个MFC应用程序
前面的Ex_HelloMsg和Ex_HelloWin都是基于Windows API的C++应用程序。显然,随着应用程序的复杂性,C++应用程序代码也必然越复杂。
为了帮助用户处理那些经常使用又复杂繁琐的各种Windows操作,
Visual C++设计了一套基础类库(Microsoft Foundation Class Library,简称MFC)。MFC把Windows编程规范中的大多数内容封装成为各种类,
使程序员从繁杂的编程中解脱出来,提高了编程和代码效率。
3.2.1 设计一个MFC程序
在理解MFC机制之前,先来看一个MFC应用程序。
[例Ex_HelloMFC] 一个MFC应用程序
#include <afxwin.h> // MFC头文件
class CHelloApp : public CWinApp // 声明应用程序类
{
public:
virtual BOOL InitInstance();
};
CHelloApp theApp; // 建立应用程序类的实例
class CMainFrame: public CFrameWnd // 声明主窗口类
{
public:
CMainFrame()
{ // 创建主窗口
Create(NULL, "我的窗口", WS_OVERLAPPEDWINDOW, CRect(0,0,400,300));
}
protected:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
};
// 消息映射入口
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_LBUTTONDOWN() // 单击鼠标左键消息的映射宏
END_MESSAGE_MAP()
//定义消息映射函数
void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point)
{
MessageBox ("你好,我的Visual C++世界!", "问候", 0) ;
CFrameWnd::OnLButtonDown(nFlags, point);
}
BOOL CHelloApp::InitInstance() // 每当应用程序首次执行时都要调用的初始化函数
{
m_pMainWnd = new CMainFrame();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
在Visual C++ 6.0运行上述MFC程序需要进行以下步骤:
(1) 选择“文件”→“新建”菜单命令,显示出“新建”对话框。在“工程”标签页面的列表
框中,选中Win32 Application项,创建一个Ex_HelloMFC空应用程序项目。
(2) 再次选择“文件”→“新建”菜单命令,显示出“新建”对话框。在文件标签页面左
边的列表框中选择C++ Source File项,在右边的文件框中键入
Ex_HelloMFC.cpp,单击[确定]按钮。
(3) 输入上面的代码。选择“工程”→“设置”菜单命令,在出现的对话框中选择
“General”标签。然后在“Microsoft Foundation Classes”组合框中,选择“Use
MFC in a Shared DLL”,如图3.4所示。单击[确定]按钮。
(4) 程序运行后,单击鼠标左键,就会弹出一个对话框,结果同Ex_HelloWin
创建一个单文档应用程序
用MFC AppWizard(MFC应用程序向导)可以方便地创建一个通用的Windows单文档应用程
序,其步骤如下。
1. 开始
选择“文件”→“新建”菜单,在弹出的“新建”对话框中,可以看到工程标签页面中,显示出一
系列的应用程序项目类型;选择MFC AppWizard(exe)的项目类型(该类型用于创建可执行
的Windows应用程序),将项目工作文件夹定位在“D:\Visual C++ 6.0程序”,并在工程编辑
框中输入项目名Ex_SDIHello,结果如图
第一步
单击[确定]按钮,出现如图3.6所示的对话框,进行下列选择:
(1)从应用程序类型单个文档(SDI)、多重文档(MDI)和基本对话框(基于对话框的
应用程序)中选择“单个文档”。
(2) 决定应用程序中是否需要MFC的文档视图(“文档/查看体系结构支持”)结构的
支持。若不选定此项,则程序中的磁盘文件的打开、保存以及文档和视图的相互
作用等功能需要用户来实现,且将跳过Step 2~Step 5,直接弹出“Step 6”对话
框。一般情况下,应选中此项。
(3) 选择资源所使用的语言,这里是“中文[中国]”。
3.第二步
单击[下一个]按钮,出现如图3.7所示的对话框,让用户选择程序中是否加入数据
库的支持(有关数据库的内容将在以后的章节中介绍)。
第三步
单击[下一个]按钮进入下一步,出现如图3.8所示的对话框。允许用户在程序中加入复
合文档、自动化、ActiveX控件的支持。
5. 第四步
单击[下一个]按钮进入下一步,出现如图3.9所示的对话框,对话框的前几项依次确定
对浮动工具条、打印与预览以及通信等特性的支持。
第五步
保留以上默认值,单击[下一个]按钮进入下一步。弹出如图3.10所示的对话框,这里出现三
个方面的选项,供用户来选择:
(1) 应用程序的主窗口是MFC标准风格还是窗口左边有切分窗口的浏览器风格;
(2) 在源文件中是否加入注释用来引导用户编写程序代码;
(3) 使用动态链接库还是静态链接库。
第六步
保留默认选项,单击[下一步]按钮进行下一步,出现如图3.11所示的对话框。在
这里,用户可以对MFC AppWizard提供的默认类名、基类名、各个源文件名进
行修改。
单击[完成]按钮出现一个信息对话框,显示出用户在前面几个步骤中作出的选择
内容,单击[确定]按钮系统开始创建,并又回到了Visual C++ 6.0的主界面。
8.编译并运行
到这里为止,用户虽然没有编写任何程序代码,但MFC AppWizard已经根据用
户的选择内容自动生成基本的应用程序框架。单击编译工具栏上的运行工具按钮
“”或按快捷键Ctrl+F5,系统开始编连并运行生成的单文档应用程序可执行文件
Ex_SDIHello.exe,运行结果如图3.12所示。
MFC应用程序项目组织
1. 项目的文件组织
在Visual C++ 6.0中,项目中所有的源文件都是采用文件夹的方式进行管理的,它将
项目名作为文件夹名,在此文件夹下包含源程序代码文件(.cpp,.h)、项目文件(.dsp)以
及项目工作区文件(.dsw)等。表3.4列出了这些文件类型的的含义。
3.1 Windows编程基础
编制一个功能强大和易操作的Windows应用程序所需要的代码肯定会比一般的C++程序要多得多,但并不是所有的代码都需要自己从头开始编写,因为Visual C++不仅提供了常用的Windows应用程序的基本框架,而且可以在框架程序中直接调用Win32 API(Application Programming
Interface, 应用程序接口)函数。这样,用户仅需要在相应的框架位置中添加自己的代码或修改部分代码就可实现Windows应用程序的许多功能。
3.1.1 简单的Windows应用程序
先来看一个最简单的Windows应用程序Ex_HelloMsg。
[例Ex_HelloMsg] 一个简单的Windows应用程序
#include <windows.h>
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MessageBox (NULL, "你好,我的Visual C++世界!", "问候", 0) ;
return 0 ;
}
在Visual C++ 6.0运行上述程序需要进行以下步骤:
(1) 选择“文件”→“新建”菜单命令,显示出“新建”对话框。在“工程”标签页面的列表框
中,选中Win32 Application项。
(2) 在工程编辑框中键入Win32应用程序项目名称Ex_HelloMsg。在“位置”编辑框中直
接键入文件夹名称,或单击浏览按钮选择一个已有的文件夹。
(3) 单击[确定]按钮继续。一个询问项目类型的Win32应用程序向导将被显示,选中An
empty project项。单击[完成]按钮,系统将显示该应用程序向导的创建信息,单击[确
定]按钮系统将自动创建此应用程序。
(4) 再次选择“文件”→“新建”菜单命令,显示出“新建”对话框。在“文件”标签页面左边的
列表框中选择C++ Source File项,在右边的文件框中键入Ex_HelloMsg.cpp,单击
[确定]按钮。
[例Ex_HelloMsg] 一个简单的Windows应用程序
(5) 输入上面的代码,运行程序,结果如图3.1所示。
从上面的程序可以看出:
● C++控制台应用程序以main函数作为进入程序的初始入口点,但在Windows应用程
序中,main主函数被WinMain函数取代。WinMain函数的原型如下:
int WINAPI WinMain (
HINSTANCE hInstance, // 当前实例句柄
HINSTANCE hPrevInstance, // 前一实例句柄
LPSTR lpCmdLine, // 指向命令行参数的指针
int nCmdShow) // 窗口的显示状态
所谓句柄是一个标识Windows资源(如菜单、图标、窗口等)和设备等对象的数据指针类型。通常,一个句柄变量可用来对系统中某些资源的间接引用。
●每一个C++ Windows应用程序都需要Windows.h头文件,它还包含了其他的一些Windows头文件。这些头文件定义了Windows的所有数据类型、函数调用、数据结构和符号常量。
●程序中,MessageBox是一个Win32 API函数,用来弹出一个消息对话框。该函数第一个参数用来指定父窗口句柄,即对话框所在的窗口句柄。第二、三个参数分别用来指定显示的消息内容和对话框窗口的标题,最后一个参数用来指定在对话框中显示的按钮。
[例Ex_HelloWin] 一个完整的Windows应用程序
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); // 窗口过程
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd ; // 窗口句柄
MSG msg ; // 消息
WNDCLASS wndclass ; // 窗口类
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = "HelloWin"; // 窗口类名
if (!RegisterClass (&wndclass))
{ // 注册窗口
MessageBox (NULL, "窗口注册失败!", "HelloWin", 0) ;
return 0 ;
}
hwnd = CreateWindow ("HelloWin", // 窗口类名
"我的窗口", // 窗口标题
WS_OVERLAPPEDWINDOW, // 窗口样式
CW_USEDEFAULT, // 窗口最初的 x 位置
CW_USEDEFAULT, // 窗口最初的 y 位置
CW_USEDEFAULT, // 窗口最初的 x 大小
CW_USEDEFAULT, // 窗口最初的 y 大小
NULL, // 父窗口句柄
NULL, // 窗口菜单句柄
hInstance, // 应用程序实例句柄
NULL) ; // 创建窗口的参数
ShowWindow (hwnd, nCmdShow) ; // 显示窗口
UpdateWindow (hwnd) ; // 更新窗口,包括窗口的客户区
// 进入消息循环:当从应用程序消息队列中检取的消息是WM_QUIT时,则退出循环。
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ; // 转换某些键盘消息
DispatchMessage (&msg) ; // 将消息发送给窗口过程,这里是WndProc
}
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE: // 窗口创建产生的消息
return 0 ;
case WM_LBUTTONDOWN:
MessageBox (NULL, "你好,我的Visual C++世界!", "问候", 0) ;
return 0 ;
case WM_DESTROY: // 当窗口关闭时产生的消息
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ; // 执行默认的消息处理
}
程序运行后,单击鼠标左键,就会弹出一个对话框,结果如图3.2所示
窗口过程函数WndProc用来接收和处理各种不同的消息,而主函数WinMain通常要完成以下几步工作:
(1) 调用API函数RegisterClass注册应用程序的窗口类。
(2) 调用相关API函数创建和显示窗口,并进行其它必要的初始化处理。其中,函数CreateWindow用来创建已注册窗口类的窗口。Windows每一个窗口都有一些基本属性,如窗口标题、窗口位置和大小、应用程序图标、鼠标指针、菜单和背景颜色等。窗口类就是充当这些属性的模板。
(3) 创建和启动应用程序的消息循环。Windows应用程序接受各种不同的消息,包括键盘消息、鼠标以及窗口产生的各种消息。Windows系统首先将消息放入消息队列中,应用程序的消息循环就是从应用程序的消息队列中检取消息,并将消息发送相应的窗口过程函数中作进一步处理。API函数GetMessage 和DispatchMessage就是起到这样的作用。
(4) 如果接收到WM_QUIT消息,则退出应用程序。
图3.3 Windows应用程序的基本流程
3.1.2 Windows编程特点
一个完整的Windows应用程序除了WinMain函数外,还包含用于处理用户动作和
窗口消息的窗口函数。这不同于一个C++的控制台应用程序,可以将整个程序包
含在main函数中。事实上,它们的区别还远不止这些,不久还会发现一个
Windows应用程序还常常具有这样的一些特性:
● 消息驱动机制
●图形设备接口(GDI)
●基于资源的程序设计
●动态链接库
●进程和线程
1. 消息驱动机制
在Windows操作环境中,无论是系统产生的动作或是用户运行应用程序产生的动
作,都称为事件(Events)产生的消息(Message)。例如,在Windows 桌面(传统风
格)上,双击应用程序的快捷图标,系统就会执行该应用程序。在Windows的应
用程序中,也是通过接收消息、分发消息、处理消息来和用户进行交互的。
这种消息驱动的机制是Windows编程的最大特点。需要注意的是,许多Windows
消息都经过了严格的定义,并且适用于所有的应用程序。例如,当用户按下鼠标
的左键时系统就会发送WM_LBUTTONDOWN消息,而当用户敲了一个字符键时
系统就会发送WM_CHAR消息,若用户进行菜单选择或工具按钮单击等操作时,
系统又会相应地发送WM_COMMAND消息给相应的窗口等等。
2. 图形设备接口(GDI)
在传统的DOS环境中,想要在打印机上打印一幅图形是一件非常复杂的事情,因
为用户必须根据打印机类型和指令规则向打印机输送数据。而Windows则提供了
一个抽象的接口,称为图形设备接口(Graphical Device Interface,简称GDI),
使得用户直接利用系统的GDI函数就能方便实现输入或输出,而不必关心与系统
相连的外部设备的类型。
3. 基于资源的程序设计
Windows应用程序常常包含众多图形元素,例如光标、菜单、工具栏、位图、对
话框等,在Windows环境下,每一个这样的元素都作为一种可以装入应用程序的
资源来存放。这些资源就像C++程序中的常量一样,可以被编辑、修改,也可以
被其他应用程序所共享。Visual C++ 6.0中就提供这样的编辑器,可“所见即所
得”地对这些不同类型的资源进行设计、编辑等。
4. 动态链接库
动态链接库提供了一些特定结构的函数,能被应用程序在运行过程中装
入和连接,且多个程序可以共享同一个动态链接库,这样就可以大大节
省内存和磁盘空间。从编程角度来说,动态链接库可以提高程序模块的
灵活性,因为它本身是可以单独设计、编译和调试的。
Windows提供了应用程序可利用的丰富的函数调用,大多数用于实现其
用户界面和在显示器上显示的文本和图形,都是通过动态链接库来实现
的。这些动态链接库是一些具有.DLL扩展名或者有时是.EXE扩展名的文
件。
在Windows操作系统中,最主要的DLL有KERNEL32.DLL、GDI32.DLL
和USER32.DLL三个模块。其中,KERNEL32用来处理存储器低层功
能、任务和资源管理等Windows核心服务; GDI32用来提供图形设备接
口,管理用户界面和图形绘制,包括Windows元文件、位图、设备描述
表和字体等;而USER32负责窗口的管理,包括消息、菜单、光标、计
时器以及其它与控制窗口显示相关的一些功能。
5 . 进程和线程
在32位Windows多任务操作系统中,采用了进程和线程的管理模式。进程是装入
内存中正在执行的应用程序。进程包括私有的虚拟地址空间、代码、数据及其它
操作系统资源,如文件、管道以及对该进程可见的同步对象等。进程包括了一个
或多个在进程上下文内运行的线程。线程是操作系统分配CPU时间的基本实体。
线程可以执行应用程序代码的任何部分,包括当前正在被其它线程执行的那些部
分。同一进程的所有线程共享同样的虚拟地址空间、全局变量和操作系统资源。
在一个应用程序中,可以包括一个或多个进程,每个进程由一个或多个线程构
成。
表3.1列出了一些在Windows编程中常用的基本数据类型
表3.2列出了常用的预定义句柄,它们的类型均为void *,即一个32位指针。
3.2 编制一个MFC应用程序
前面的Ex_HelloMsg和Ex_HelloWin都是基于Windows API的C++应用程序。显然,随着应用程序的复杂性,C++应用程序代码也必然越复杂。
为了帮助用户处理那些经常使用又复杂繁琐的各种Windows操作,
Visual C++设计了一套基础类库(Microsoft Foundation Class Library,简称MFC)。MFC把Windows编程规范中的大多数内容封装成为各种类,
使程序员从繁杂的编程中解脱出来,提高了编程和代码效率。
3.2.1 设计一个MFC程序
在理解MFC机制之前,先来看一个MFC应用程序。
[例Ex_HelloMFC] 一个MFC应用程序
#include <afxwin.h> // MFC头文件
class CHelloApp : public CWinApp // 声明应用程序类
{
public:
virtual BOOL InitInstance();
};
CHelloApp theApp; // 建立应用程序类的实例
class CMainFrame: public CFrameWnd // 声明主窗口类
{
public:
CMainFrame()
{ // 创建主窗口
Create(NULL, "我的窗口", WS_OVERLAPPEDWINDOW, CRect(0,0,400,300));
}
protected:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
};
// 消息映射入口
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_LBUTTONDOWN() // 单击鼠标左键消息的映射宏
END_MESSAGE_MAP()
//定义消息映射函数
void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point)
{
MessageBox ("你好,我的Visual C++世界!", "问候", 0) ;
CFrameWnd::OnLButtonDown(nFlags, point);
}
BOOL CHelloApp::InitInstance() // 每当应用程序首次执行时都要调用的初始化函数
{
m_pMainWnd = new CMainFrame();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
在Visual C++ 6.0运行上述MFC程序需要进行以下步骤:
(1) 选择“文件”→“新建”菜单命令,显示出“新建”对话框。在“工程”标签页面的列表
框中,选中Win32 Application项,创建一个Ex_HelloMFC空应用程序项目。
(2) 再次选择“文件”→“新建”菜单命令,显示出“新建”对话框。在文件标签页面左
边的列表框中选择C++ Source File项,在右边的文件框中键入
Ex_HelloMFC.cpp,单击[确定]按钮。
(3) 输入上面的代码。选择“工程”→“设置”菜单命令,在出现的对话框中选择
“General”标签。然后在“Microsoft Foundation Classes”组合框中,选择“Use
MFC in a Shared DLL”,如图3.4所示。单击[确定]按钮。
(4) 程序运行后,单击鼠标左键,就会弹出一个对话框,结果同Ex_HelloWin
3.3.2 创建一个单文档应用程序
用MFC AppWizard(MFC应用程序向导)可以方便地创建一个通用的Windows单文档应用程
序,其步骤如下。
1. 开始
选择“文件”→“新建”菜单,在弹出的“新建”对话框中,可以看到工程标签页面中,显示出一
系列的应用程序项目类型;选择MFC AppWizard(exe)的项目类型(该类型用于创建可执行
的Windows应用程序),将项目工作文件夹定位在“D:\Visual C++ 6.0程序”,并在工程编辑
框中输入项目名Ex_SDIHello,结果如图3.5所示。
MFC类结构
在开发环境中,Visual C++ 6.0是通过左边的项目工作区窗口来对项目进行各种管理。项目
工作区窗口包含3个页面,它们分别是ClassView页、ResourceView页和FileView页,通过
单击项目区窗口底部的页面标签进行切换。
将Visual C++ 6.0项目工作区窗口切换到ClassView页面,可以看到MFC为单文档应用程序
项目Ex_SDIHello自动创建了类CAboutDlg、CEx_SDIHelloApp、 CEx_SDIHelloDoc、
CEx_SDIHelloView和CMainFrame。这些MFC类之间的继承和派生关系如图3.14所示
使用类向导
MFC类向导(ClassWizard)是Visual C++ 6.0中又一个非常有用的工具。
它能自动为一个项目添加一个类、进行消息和数据映射、创建OLE
Automation(自动化)属性和方法以及进行ActiveX事件处理等。
3.4.1 类向导概述
打开MFC类向导可以使用下列几种方法:
(1) 选择“查看”→“建立类向导”菜单或直接使用Ctrl+W快捷键。
(2) 在源代码文件的文档编辑窗口中,右击鼠标,从弹出的快捷菜单中选择“建立
类向导”命令。
当MFC类向导打开后,就会弹出如图3.15所示的MFC ClassWizard对话框
1. 消息分类
Windows应用程序中的消息主要有三种类型。
(1)窗口消息(Windows message)。这类消息主要是指由WM_开头的消息(WM_
COMMAND除外),一般由窗口类和视图类对象来处理。窗口消息往往带有参
数,以标志处理消息的方法。
(2)控件的通知消息(Control notifications)。当控件的状态发生改变(例如用户在控
件中进行输入)时,控件就会向其父窗口发送WM_COMMAND通知消息。应用程
序框架处理控件消息的方法和窗口消息相同,但按钮的BN_CLICKED通知消息
除外,它的处理方法与命令消息相同。
(3)命令消息(Command message)。命令消息主要包括由用户交互对象(菜单、工
具条的按钮、快捷键等)发送的WM_COMMAND通知消息。命令消息的处理方式
与其他两种消息不同,它能够被多种对象接收、处理,这些对象包括文档类、文
档模板类、应用程序本身以及窗口和视类等;而窗口消息和控件的通知消息是由
窗口对象接收并处理的,这里的窗口对象是指从CWnd中派生的类的对象,它包
括CFrameWnd、CMDIFrameWnd、CMDIChildWnd、CView、CDialog以及从
这些类派生的对象等。
2. ClassWizard映射消息的一般方法
在MFC中,绝大多数消息都可由MFC的ClassWizard来映射。将ClassWizard对
话框切换到Message Maps页面(参看图3.15),可以看到它有许多选项,如项目
组合框、类组合框等。各项功能说明如表3.5所示。
. ClassWizard映射消息的一般方法
例如,若向CEx_SDIHelloView中添加WM_LBUTTOMDOWN的消息映射,则可按下列步
骤进行:
(1) 按Ctrl+W快捷键打开MFC ClassWizard对话框。
(2) 在Class name组合框中,将类名选定为CEx_SDIHelloView。此时,Object IDs和
Messages列表内容会相应的改变。
(3) 在Object IDs列表框中选定CEx_SDIHelloView,而在Messages列表中选定
WM_LBUTTOMDOWN消息。
(4) 双击Messages列表中的WM_LBUTTOMDOWN消息或单击[Add Function]按钮,都会在
CEx_SDIHelloView类中添加该消息的映射函数OnLButtonDown,同时在Member funcions
列表中显示这一消息映射函数和被映射的消息,结果如图3.16所示。
(5) 单击[Edit Code]按钮后,ClassWizard对话框退出,并转向文档窗口,定位到
OnLButtonDown函数源代码处。
(6) 添加下列代码:
void CEx_SDIHelloView::OnLButtonDown(UINT nFlags, CPoint point)
{
MessageBox ("你好,我的Visual C++世界!", "问候", 0) ;
CView::OnLButtonDown(nFlags, point);
}
(7) 这样就完成了一个消息映射过程。程序运行后,在窗口客户区单击鼠标左键,就会弹出
一个消息对话框。
消息映射代码
查看CEx_SDIHelloView程序代码,可以发现:ClassWizard为WM_LBUTTOMDOWN
的消息映射作了以下三个方面内容的安排:
(1) 在头文件Ex_SDIHelloView.h中声明消息处理函数OnLButtonDown:
protected:
//{{AFX_MSG(CEx_SDIHelloView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
代码中的//{{AFX_MSG(CEx_SDIHelloView)和//}}AFX_MSG之间的部分是ClassWizard
定义的专门用作消息映射函数声明的标记。表示该程序块中的消息映射声明是由
ClassWizard来自动管理的,用户一般不需要去更改。需要说明的是,凡//{{和//}}之间
的程序代码块均由ClassWizard自动管理。
3. 消息映射代码
(2) 在Ex_SDIHelloView.cpp源文件前面的消息映射入口处,添加了相应的映射宏:
BEGIN_MESSAGE_MAP(CEx_SDIHelloView, CView) // 消息映射开始
//{{AFX_MSG_MAP(CEx_SDIHelloView)
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
…
END_MESSAGE_MAP() // 消息映射结束
(3) 在Ex_SDIHelloView.cpp文件中写入一个空的消息处理函数的模板,以便用户填入
具体代码,如下面的框架:
void CEx_SDIHelloView::OnLButtonDown(UINT nFlags, CPoint point)
{ // TODO: Add your message handler code here and/or call default
CView::OnLButtonDown(nFlags, point);
}
事实上,根据ClassWizard产生的上述消息映射过程,用户可以自己手动添加一些
MFC ClassWizard不支持的消息映射函数,以完成特定的功能。例如,Ex_HelloMFC
示例就是按照上述过程添加消息映射的。
4. 键盘消息
当用户按下一个键或组合键时,Windows将WM_KEYDOWN或WM_SYSKEYDOWN
放入具有输入焦点的应用程序窗口的消息队列中。当键被释放时,Windows则把
WM_KEYUP或WM_SYSKEYUP消息放入消息队列中。对于字符键来说,还会在这
两个消息之间产生WM_CHAR消息。
MFC ClassWizard能自动添加了当前类的WM_KEYDOWN和WM_KEYUP击键消息
处理函数的调用,它们具有下列函数原型:
afx_msg void OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags );
afx_msg void OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );
afx_msg是MFC用于定义消息函数的标志,参数nChar表示虚拟键代码,nRepCnt表
示当用户按住一个键时的重复计数,nFlags表示击键消息标志。
所谓虚拟键代码,是指与设备无关的键盘编码。在Visual C++中,最常用的虚拟键代
码已被定义在Winuser.h中,例如:VK_SHIFT表示SHIFT键,VK_F1表示功能键F1
等。
同击键消息一样,MFC中的ClassWizard也提供相应的字符消息处理框架,并自动添
加了当前类的WM_CHAR消息处理函数调用,它具有下列函数原型:
afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags );
参数nChar表示键的ASCII码,nRepCnt表示当用户按住一个键时的重复计数,
nFlags表示字符消息标志。
5. 鼠标消息
当用户对鼠标进行操作时,像键盘一样也会产生对应的消息。通常,Windows只
将键盘消息发送给具有输入焦点的窗口,但鼠标消息不受这种限制。只要鼠标移
过窗口的客户区时,就会向该窗口发送WM_MOUSEMOVE(移动鼠标)消息。
这里的客户区是指窗口中用于输出文档的区域。由于MFC头文件中定义的与鼠标
按钮相关的标识使用了LBUTTON(左)、MBUTTON(中)和RBUTTON(右),因而
当在窗口的客户区中按下或释放一个鼠标键时,还会产生如表3.6所示的消息
6. 计时器消息
应用程序是通过CWnd的SetTimer函数来设置并启动计时器的,这个函数的原型如下:
UINT SetTimer( UINT nIDEvent, UINT nElapse,
void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
参数nIDEvent用来指定该计时器的标识值(不能为0),当应用程序需要多个计时器时可多次
调用该函数,但每一个计时器的标识值应是唯一的,各不相同。nElapse表示计时器的时
间间隔(单位为毫秒),lpfnTimer是一个回调函数的指针,该函数由应用程序来定义,用来
处理计时器WM_TIMER消息。一般情况下该参数为NULL,此时WM_TIMER消息被放入到
应用程序消息队列中供CWnd对象处理。
SetTimer函数成功调用后返回新计时器的标识值。当应用程序不再使用计时器时,可调用
CWnd:: KillTimer函数来停止WM_TIMER消息的传送,其函数原型如下:
BOOL KillTimer( int nIDEvent );
其中nIDEvent和用户调用SetTimer函数设置的计时器标识值是一致的。
对于WM_TIMER消息,ClassWizard会将其映射成具有下列原型的消息处理函数:
afx_msg void OnTimer( UINT nIDEvent );
通过nIDEvent可判断出WM_TIMER是哪个计时器传送的。
7. 其他窗口消息
在系统中,除了用户输入产生的消息外,还有许多系统根据应用程序的状态和运
行过程产生的消息,有时也需要用户进行处理。
(1) WM_CREATE消息。该消息是在窗口对象创建后,Windows向视图发送的第
一个消息;如果用户有什么工作需要在初始化时处理,就可在该消息处理函数中
加入所需代码。但是,由于WM_CREATE消息发送时,窗口对象还未完成,窗
口还不可见,因此在该消息处理函数
OnCreate内,不能调用那些依赖于窗口处于完成激活状态的Windows函数,如
窗口的绘图函数等。
(2) WM_CLOSE或WM_DESTROY消息。当用户从系统菜单中关闭窗口或者父
窗口被关闭时,Windows都会发送WM_CLOSE消息;而WM_DESTROY消息是
在窗口从屏幕消失后发送的,因此它紧随WM_CLOSE之后。
(3) WM_PAINT消息。当窗口的大小发生变化、窗口内容发生变化、窗口间的层
叠关系发生变化或调用函数UpdateWindow或RedrawWindow时,系统都将产生
WM_PAINT消息,表示要重新绘制窗口的内容。该消息处理函数的原型是;
afx_msg void OnPaint();
用ClassWizard映射该消息的目的是执行自己的图形绘制代码 。
3.4.3 类的添加和删除
1. 类的添加
给项目添加一个类有很多方法,例如选择“工程”→“添加工程”→“Files”菜单命令,
可将外部源文件所定义的类添加到项目中。但是如果使用MFC的ClassWizard,
就可以从大多数MFC类中派生一个类,并且创建的类代码自动包含MFC所必需
的消息映射等机制。
用MFC ClassWizard给项目添加一个类通常是按下列步骤进行的:
(1) 按快捷键Ctrl+W启动MFC ClassWizard对话框。单击[Add Class]按钮,从弹
出的下拉菜单中选择New命令,弹出如图3.17所示的New Class对话框。
(2) 对话框中,Name是用来输入用户定义的类名,注意要以“C”字母打头,以保
持与MFC标识符命名规则一致;File Name是该类的源代码文件名,单击
[Change]按钮可改变源文件名称及其在磁盘中的位置;Base class用来指定该类
的基类;Dialog ID是当选择CDialog作为基类时指定对话框的资源ID号。最下面
的Automation是用来设置对自动化的支持。
(3) 单击[OK]按钮,一个新类就会自动添加到项目中。
类的添加
类的删除
当添加的类需要删除时,则需要按下列步骤进行:
将Visual C++ 6.0打开的所有文档窗口关闭。
将项目工作区窗口切换到FileView页面,展开Source Files和Header Files结点,
分别选定要删除类的对应.h和.cpp文件,按下Delete键,删除这两个文件。
选择“文件”→“关闭工作区”菜单命令,关闭项目。
从实际的文件夹中删除对应的.h和.cpp文件与.clw文件。
这样,当调入项目文件后,按Ctrl+W快捷键就会弹出一个对话框,询问是否重新
建立ClassWizard数据文件,单击[是]按钮,出现如图3.18所示的Select Source
Files对话框。单击右下的[Add All]按钮即可将.h和.cpp所包含的类删除
总而言之,MFC编程还是多实践。相信你是mfc编程高手