02 MFC - Windows 编程模型

简介: 02 MFC - Windows 编程模型

一个完整的Win32程序(#include <windows.h>),该程序实现的功能是创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序的实现步骤为:

  1. WinMain函数的定义
  2. 创建一个窗口
  3. 进行消息循环
  4. 编写窗口过程函数

1. 项目的创建

2. WinMain函数的定义

int WINAPI WinMain(
  HINSTANCE hInstance,  //应用程序实例
  HINSTANCE hPrevInstance,  //上一个应用程序实例
  LPSTR lpCmdLine,    //命令行参数
  int nShowCmd);    //窗口显示的样式

WINAPI :是一个宏,它代表的是__stdcall(注意是两个下划线),表示的是参数传递的顺序:从右往左入栈,同时在函数返回前自动清空堆栈。

hInstance :表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例, 才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给 WinMain 函数。

hPrevInstance :表示当前实例的前一个实例的句柄。在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用。

lpCmdLine :是一个以空终止的字符串, 指定传递给应用程序的命令行参数,相当于C或C++中的main函数中的参数char *argv[]

nShowCmd :表示一个窗口的显示,表示它是要最大化显示、最小化显示、正常大小显示还是隐藏显示。

3. 创建一个窗口

创建一个完整的窗口,需要经过下面几个步骤:

a.设计一个窗口类
b.注册窗口类
c.创建窗口
d.显示及更新窗口

3.1设计一个窗口类

一个完整的窗口具有许多特征, 包括光标(鼠标进入该窗口时的形状)、图标、背景色等。窗口的创建过程类似于汽车的制造过程。

我们在生产一个型号的汽车之前, 首先要对该型号的汽车进行设计, 在图纸上画出汽车的结构图, 设计各个零部件, 同时还要给该型号的汽车取一个响亮的名字, 例如“宝马 x6”。

类似地, 在创建一个窗口前, 也必须对该类型的窗口进行设计, 指定窗口的特征。在Windows中,窗口的特征就是由WNDCLASS结构体来定义的,我们只需给WNDCLASS结构体对应的成员赋值,即可完成窗口类的设计。

WNDCLASS结构体的定义如下:

typedef struct _WNDCLASS{
  UINT        style;
  WNDPROC     lpfnWndProc;
  int         cbClsExtra;
  int         cbWndExtra;
  HINSTANCE   hInstance;
  HICON       hIcon;
  HCURSOR     hCursor;
  HBRUSH      hbrBackground;
  LPCWSTR     lpszMenuName;
  LPCWSTR     lpszClassName;
} WNDCLASS;

style:指定窗口的样式(风格),常用的样式如下:

lpfnWndProc:指定一个窗口回调函数,是一个函数的指针。

当应用程序收到给某一窗口的消息时,就应该调用某一函数来处理这条消息。这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。对于一条消息,操作系统调用的是接受消息的窗口所属的类型中的lpfnWndProc成员指定的函数。每一种不同类型的窗口都有自己专用的回调函数,该函数就是通过lpfnWndProc成员指定的。

回调函数的定义形式如下:

LRESULT CALLBACK WindowProc(
  HWND hWnd,    //信息所属的窗口句柄
  UINT uMsg,    //消息类型
  WPARAM wParam,  //附加信息(如键盘哪个键按下)
  LPARAM lParam //附加信息(如鼠标点击坐标)
  );

cbClsExtra:类的附加内存,通常数情况下为0。

cbWndExtra:窗口附加内存,通常情况下为0。

hInstance:当前实例句柄,用WinMain中的形参hInstance为其赋值。

hIcon:指定窗口类的图标句柄,设置为NULL,则使用默认图标,也可用如下函数进行赋值:

HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
如:LoadIcon(NULL, IDI_WARNING); //第一个参数为NULL,加载系统默认图标

hCursor:指定窗口类的光标句柄,设置为NULL,则使用默认图标,也可用如下函数进行赋值:

HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
如:LoadCursor(NULL, IDC_HELP); //第一个参数为NULL,加载系统默认光标

hbrBackground:指示窗口的背景颜色,可用如下函数进行赋值:

HGDIOBJ GetStockObject(int fnObject);
如:GetStockObject(WHITE_BRUSH);

lpszMenuName:指定菜单资源的名字。如果设置为NULL,那么基于这个窗口类创建的窗口将没有默认菜单。

llpszClassName:指定窗口类的名字。

示例代码如下:

WNDCLASS wc;  //窗口类变量
  wc.cbClsExtra = 0;  //类附加内存
  wc.cbWndExtra = 0;  //窗口附加内存
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景色为白色
  wc.hCursor = (HCURSOR)LoadCursor(NULL, IDC_HELP); //帮助光标
  wc.hIcon = (HICON)LoadIcon(NULL, IDI_WARNING);  //警告图标
  wc.hInstance = hInstance; //应用程序实例,为WinMain第1个形参
  wc.lpfnWndProc = WinProc; //窗口过程函数名字
  wc.lpszClassName = TEXT("MyWin"); //类的名字
  wc.lpszMenuName = NULL; //没有菜单
  wc.style = 0; //类的风格,填0,使用默认风格
3.2 注册窗口类

设计完窗口类(WNDCLASS)后, 需要调用RegisterClass函数对其进行注册,注册成功后,才可以创建该类型的窗口。

注册函数的原型声明如下:

ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
使用示例:RegisterClass(&wc);
3.3 创建窗口

设计好窗口类并且将其成功注册之后, 即可用CreateWindow函数产生这种类型的窗口了。

CreateWindow函数的原型声明如下:

HWND CreateWindow(
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam);

参数说明:

lpClassName:指定窗口类的名称,此名字必须和WNDCLASS的lpszClassName成员指定的名称一样。

llpWindowName:指定窗口的名字,即窗口的标题。

ldwStyle:指定创建的窗口的样式,常指定为指WS_OVERLAPPEDWINDOW类型,这是一种多种窗口类型的组合类型。

lx, y:指定窗口左上角的x,y坐标。如果参数x被设为CW_USEDEFAULT,那么系统为窗口选择默认的左上角坐标并忽略y参数。

nWidth,nHeight:指定窗口窗口的宽度,高度。如果参数nWidth被设为 CW_USEDEFAULT,那么系统为窗口选择默认的宽度和高度,参数nHeight被忽略。

hWndParent:指定被创建窗口的父窗口句柄,没有父窗口,则设置NULL。

hMenu:指定窗口菜单的句柄,没有,则设置为NULL。

hInstance:窗口所属的应用程序实例的句柄,用WinMain中的形参hInstance为其赋值。

lpParam:作为WM_CREATE消息的附加参数lParam传入的数据指针。通常设置为NULL。

返回值说明:如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则,返回NULL。

示例代码:

HWND  hWnd = CreateWindow(
TEXT("MyWin"),  //窗口类名字
TEXT("测试"),      //窗口标题
WS_OVERLAPPEDWINDOW,  //窗口风格  
CW_USEDEFAULT, CW_USEDEFAULT,  //窗口x,y坐标,使用默认值
CW_USEDEFAULT, CW_USEDEFAULT,  //窗口宽度,高度,使用默认值
NULL,   //无父窗口
NULL,   //无菜单
hInstance,  //应用程序实例句柄,为WinMain第1个形参
NULL);  //附件信息,通常设置为NULL
3.4 显示及更新窗口

显示窗口函数原型:

ShowWindow(hWnd, SW_SHOWNORMAL); //SW_SHOWNORMAL为普通模式
  UpdateWindow(hWnd);
BOOL ShowWindow(HWND hWnd, int nCmdShow);

更新窗口函数原型:

BOOL UpdateWindow(HWND hWnd);

示例代码:

ShowWindow(hWnd, SW_SHOWNORMAL); //SW_SHOWNORMAL为普通模式
UpdateWindow(hWnd);
3.5 示例代码
//设计一个窗口类
  WNDCLASS wc;  //窗口类变量
  wc.cbClsExtra = 0;  //类附加内存
  wc.cbWndExtra = 0;  //窗口附加内存
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景色为白色
  wc.hCursor = LoadCursor(NULL, IDC_HELP);  //帮助光标
  wc.hIcon = LoadIcon(NULL, IDI_WARNING); //警告图标
  wc.hInstance = hInstance; //应用程序实例,为WinMain第1个形参
  wc.lpfnWndProc = WinProc; //窗口过程函数名字
  wc.lpszClassName = TEXT("MyWin"); //类的名字
  wc.lpszMenuName = NULL; //没有菜单
  wc.style = 0; //类的风格,填0,使用默认风格
  //注册窗口类
  RegisterClass(&wc);
  //创建窗口
  HWND  hWnd = CreateWindow(TEXT("MyWin"), TEXT("测试"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
  //显示及更新窗口
  ShowWindow(hWnd, SW_SHOWNORMAL);
  UpdateWindow(hWnd);

4. 消息循环

在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。

4.1 消息结构体

在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下:

typedef struct tagMSG {
  HWND hWnd;   
  UINT message;   
  WPARAM wParam;
  LPARAM lParam;   
  DWORD time;   
  POINT pt;
} MSG;

hWnd:消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。

message:消息的标识符,是由一个数值来表示的,不同的消息对应不同的数值。Windows将消息对应的数值定义为WM_XXX宏(WM是Windows Message的缩写)的形式, XXX对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是 WM_CHAR……。

wParam: 指定消息的附加信息,如键盘按下会触发WM_KEYDOWN消息,但是,具体按下哪个按键需要wParam区分。

lParam:指定消息的附加信息,如鼠标左击会触发WM_LBUTTONDOWN消息,但是,具体点击的坐标需要lParam区分。

ltime:标识一个消息产生时的时间。

pt:表示产生这个消息时光标或鼠标的坐标。

4.2 取消息

要从消息队列中取出消息,我们需要调用GetMessage()函数,该函数的原型声明如下:

BOOL GetMessage(
  LPMSG lpMsg,
  HWND hWnd,
  UINT wMsgFilterMin,
  UINT wMsgFilterMax);

参数说明:

lpMsg:指向一个消息结构体(MSG),GetMessage从线程的消息队列中取出的消息信息将保存在该结构体变量中。

hWnd:指定接收属于哪一个窗口的消息。通常我们将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息。

wMsgFilterMin指定消息的最小值。

wMsgFilterMax:指定消息的最大值。如果wMsgFilterMin和wMsgFilterMax都设置为0, 则接收所有消息。

返回值说明:GetMessage函数接收到除 WM_QUIT外的消息均返回非零值。对于WM_QUIT消息,该函数返回零。如果出现了错误,该函数返回-1,例如,当参数hWnd是无效的窗口句柄或lpMsg是无效的指针时。

4.3 建立消息循环
MSG msg;
  while (GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

TranslateMessage:用于翻译、处理和转换消息并把新消息投放到消息队列中,并且此过程不会影响原来的消息队列。

DispatechMessage:用于把收到的消息传到窗口回调函数进行分析和处理。即将消息传递给操作系统,让操作系统调用窗口回调函数,来对信息进行处理。

4.4 消息处理机制

① 操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。

② 应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用TranslateMessage产生新的消息。

③ 应用程序调用DispatchMessage,将消息回传给操作系统。消息是由 MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此, DispatchMessage函数总能进行正确的传递。

④ 系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理。

5. 窗口过程函数(消息处理函数)

在完成上述步骤后,剩下的工作就是编写一个窗口过程函数,用于处理发送给窗口的消息。

窗口过程函数的名字可以随便取, 如WinProc, 但函数定义的形式必须和下面声明的形式相同:

LRESULT CALLBACK WinProc( //CALLBACK 和WINAPI 作用一样
  HWND hWnd,    //信息所属的窗口句柄
  UINT uMsg,    //消息类型
  WPARAM wParam,  //附加信息(如键盘哪个键按下)
  LPARAM lParam //附加信息(如鼠标点击坐标)
  );

示例代码:

LRESULT CALLBACK WinProc(
  HWND hWnd,    //信息所属的窗口句柄
  UINT uMsg,    //消息类型
  WPARAM wParam,  //附加信息(如键盘按键)
  LPARAM lParam //附加信息(如鼠标点击坐标)
  )
{
  switch (uMsg)
  {
  case WM_KEYDOWN: //键盘按下
    //……
    break;
  case WM_LBUTTONDOWN: //鼠标右键按下
    //……
    break;
  case WM_PAINT: //绘图事件
    //……
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  case WM_CLOSE: 
    DestroyWindow(hWnd); 
    break;
  default:
    //以windows默认方式处理
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
    break;
  }
  return 0;

DefWindowProc函数:DefWindowProc函数调用默认的窗口过程,对应用程序没有处理的其他消息提供默认处理。

WM_CLOSE:对WM_CLOSE消息的响应并不是必须的,如果应用程序没有对该消息进行响应,系统将把这条消息传给DefWindowProc函数而 DefWindowProc函数则调用DestroyWindow函数来响应这条WM_CLOSE消息。

WM_DESTROY:DestroyWindow函数在销毁窗口后,会给窗口过程发送 WM_DESTROY消息,我们在该消息的响应代码中调用PostQuitMessage函数。

PostQuitMessage函数向应用程序的消息队列中投递一条WM_QUIT消息并返回。WinMain函数中,GetMessage 函数只有在收到WM_QUIT消息时才返回0,此时消息循环才结束,程序退出。传递给 PostQuitMessage函数的参数值将作为WM_QUIT消息的wParam参数,这个值通常用做WinMain函数的返回值。

6. 完整示例代码

#include <windows.h>
//窗口过程函数
LRESULT CALLBACK WinProc(
  HWND hWnd,    //信息所属的窗口句柄
  UINT uMsg,    //消息类型
  WPARAM wParam,  //附加信息(如键盘按键)
  LPARAM lParam //附加信息(如鼠标点击坐标)
  )
{
  switch (uMsg)
  {
  case WM_KEYDOWN: //键盘按下
    MessageBox(hWnd, TEXT("键盘按下"), TEXT("键盘"), MB_OK);
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    //以windows默认方式处理
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
  }
  return 0;
}
//程序入口地址
int WINAPI WinMain(
  HINSTANCE hInstance,  //应用程序实例
  HINSTANCE hPrevInstance,   //上一个应用程序实例
  LPSTR lpCmdLine,    //命令行参数
  int nShowCmd)   //窗口显示的样式
{
  //设计一个窗口类
  WNDCLASS wc;  //窗口类变量
  wc.cbClsExtra = 0;  //类附加内存
  wc.cbWndExtra = 0;  //窗口附加内存
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景色为白色
  wc.hCursor = LoadCursor(NULL, IDC_HELP);  //帮助光标
  wc.hIcon = LoadIcon(NULL, IDI_WARNING); //警告图标
  wc.hInstance = hInstance; //应用程序实例,为WinMain第1个形参
  wc.lpfnWndProc = WinProc; //窗口过程函数名字
  wc.lpszClassName = TEXT("MyWin"); //类的名字
  wc.lpszMenuName = NULL; //没有菜单
  wc.style = 0; //类的风格,填0,使用默认风格
  //注册窗口类
  RegisterClass(&wc);
  //创建窗口
  HWND  hWnd = CreateWindow(TEXT("MyWin"), TEXT("测试"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
  //显示及更新窗口
  ShowWindow(hWnd, SW_SHOWNORMAL);
  UpdateWindow(hWnd);
  //消息循环
  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg); //翻译
    DispatchMessage(&msg); //分发信息
  }
  return msg.wParam;
}


目录
相关文章
|
3月前
|
网络协议 API Windows
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
|
3月前
|
Windows
[原创]用MASM32编程获取windows类型
[原创]用MASM32编程获取windows类型
|
3月前
|
JavaScript 前端开发 API
MASM32编程通过WMI获取Windows计划任务
MASM32编程通过WMI获取Windows计划任务
|
3月前
|
API Windows
MASM32编程获取Windows当前桌面主题名
MASM32编程获取Windows当前桌面主题名
|
4月前
|
编译器 开发工具 C语言
解锁QtCreator跨界神技!Windows下轻松驾驭OpenCV动态库,让你的跨平台开发如虎添翼,秒变视觉编程大师!
【8月更文挑战第4天】QtCreator是一款强大的跨平台IDE,便于创建多平台应用。本教程教你如何在Windows环境下集成OpenCV库至Qt项目。首先,下载匹配MinGW的OpenCV预编译版并解压。接着,在QtCreator中新建或打开项目,并在.pro文件中添加OpenCV的头文件和库文件路径。确保编译器设置正确。随后编写测试代码,例如加载和显示图片,并进行编译运行。完成这些步骤后,你就能在QtCreator中利用OpenCV进行图像处理开发了。
233 6
|
4月前
|
数据库 Windows
超详细步骤解析:从零开始,手把手教你使用 Visual Studio 打造你的第一个 Windows Forms 应用程序,菜鸟也能轻松上手的编程入门指南来了!
【8月更文挑战第31天】创建你的第一个Windows Forms (WinForms) 应用程序是一个激动人心的过程,尤其适合编程新手。本指南将带你逐步完成一个简单WinForms 应用的开发。首先,在Visual Studio 中创建一个“Windows Forms App (.NET)”项目,命名为“我的第一个WinForms 应用”。接着,在空白窗体中添加一个按钮和一个标签控件,并设置按钮文本为“点击我”。然后,为按钮添加点击事件处理程序`button1_Click`,实现点击按钮后更新标签文本为“你好,你刚刚点击了按钮!”。
289 0
|
6月前
|
Java C++
jni编程(windows+JDK11+clion)
jni编程(windows+JDK11+clion)
|
6月前
|
消息中间件 程序员 Windows
Windows消息机制《MFC深度详解》
Windows消息机制《MFC深度详解》
95 1
|
6月前
|
C++ UED 开发者
逆向学习 MFC 篇:视图分割和在 C++ 的 Windows 窗口程序中添加图标的方法
逆向学习 MFC 篇:视图分割和在 C++ 的 Windows 窗口程序中添加图标的方法
82 0
|
7月前
|
API C++ Windows
windows编程入门_链接错误的配置
windows编程入门_链接错误的配置
53 0