窗口程序创建流程
1.设计注册窗口类:向操作系统写入一些数据
2.创建窗口实例:内存中创建窗口
3.显示窗口:绘制窗口的图像
4.更新窗口
5.建立消息循环:获取/翻译/派发消息
6.实现窗口过程函数:自定义,处理消息
我们以下以一个具体真实的程序进行整体的讲解:
程序预先处理
#include <Windows.h> 包含头文件
const WCHAR *wszClassName = L"rkvir"; 定义一个全局窗口类名
const WCHAR *wszTitle = L"GrkDemo"; 定义一个全局标题名
设计与注册窗口类
窗口类的概念
1.窗口类包含了窗口的各种参数信息的数据结构:
窗口类是定义窗口属性的模板,这些属性包括窗口式样,鼠标形状,菜单等等。在创建窗口时,
还可以指定窗口独有的附加特性
2.每个窗口都具有自己的窗口类,基于窗口类创建窗口:
不同的窗口种类只能处理该类中所有窗口消息的窗口函数,也就是说只有先建立窗口种类,才能根
据窗口种类来创建Windows应用程序的一个或多个窗口。
3.每个窗口类都具有一个名称,使用前必须注册到系统:
窗口类不能重名,并且窗口类名,是操作系统识别窗口类的唯一标识符,在建立窗口类后,必须向
Windows登记(注册窗口类)。
注意:不能注册相同名字的窗口类。根据窗口类名字来确定是否已经注册过,如果注册过,则注册
失败
窗口类的分类
窗口是分类的,同一种类的窗口差别不大,不同种类的窗口差别差别非常大,比如记事本的帮助窗
口和文本编辑窗口,这两个窗口区别就很大。
窗口具体分为以下三种:
1.系统窗口类:系统已经注册好的窗口类,如按钮和编辑框,所有应用程序都可以直接使用
2.应用程序全局窗口类:由用户自己定义,当前应用程序所有模块都可以使用。
3.应用程序局部窗口类:由用户自己定义,当前应用程序中本模块可以使用。
设计窗口类
WinMain()是程序的入口,它相当于一个中介人的角色,用于把应用程序(指小窗口)介绍给windows。
在此之前首要的一步是登记设计应用程序的窗口类:
如下是Windows中窗口类结构的设计模板,当我们设计窗口时,使用以下模板:
style窗口类风格:
全局窗口类的注册,需要在窗口类的风格中增加 CS_GLOBALCLASS,但局部窗口类不需要
实际应用如下:
WNDCLASS wce = {0}; 创建一个类名为 wce 的窗口类
wce.style = ….|CS_GLOBALCLASS;
以下是常见的几种窗口风格:
CS_HREDRAW - 当窗口水平变化时,窗口重新绘制
CS_VREDRAW - 当窗口垂直变化时,窗口重新绘制
CS_DBLCLKS - 允许窗口接收鼠标双击
CS_NOCLOSE - 窗口没有关闭按钮
现实际构造一个窗口结构
ATOM MyRegisterClass(HINSTANCE hInstance) ATOM是WORD别名
{
WNDCLASS wcex; 创建一个类名为 wcex 的窗口类,以下是对该窗口类的结构赋值
wcex.cbSize = sizeof(WNDCLASS); 填充结构尺寸
wcex.style = CS_HREDRAW | CS_VREDRAW; 填充窗口的风格,默认填CS_VREDRAM |
CS_HREDRAM 的组合,它表示当窗口的纵横坐标发生变化时要重画整个窗口,可通过位运算增加
风格或删除风格
wcex.lpfnWndProc = WndProc; Windows窗口过程函数,它将接收Windows发送给窗口的消
息,并执行相应的任务。并且必须在模快定义中回调它。
wcex.cbClsExtra = 0; 跟在窗口类结构后面的附加字节数,给类预留的空间,默认填0
wcex.cbWndExtra = 0; 跟在窗口实例后面的附加字节数,给实例预留的空间,默认填0
wcex.hInstance = hInstance; 作为实例句柄标注这个窗口类属于哪个进程,使Windows连接到正
确的程序,便于从消息队列获取该进程的消息
wcex.hIcon = 0; 图标 如果没有则值为0,若有则调用oadIcon接口,图标的格式是ico
wcex.hCursor = 0; 光标 如果没有则值为0,若有则调用LoadCursor接口加载,LoadCursor可返
回固有光标句柄或者应用程序定义的光标句柄。
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 窗口类的背景画刷的句柄,决定
Windows用于着色窗口背景的刷子颜色,系统默认对所选的颜色加1
wcex.lpszMenuName = 0; 菜单的句柄,用来指定菜单名,没有时值为0
wcex.lpszClassName = wszClassName; 指向本窗口类名称的句柄,一般说类名称必须有,类
名是操作系统识别类的唯一ID
wcex.hIconSm = 0; 任务栏图标,如果没有则值为0,若有调用oadIcon接口。到此为止窗口结构
设计完毕
return RegisterClassExW(&wcex); 返回注册窗口类
}
注册窗口类
ATOM Ret = MyRegisterClass(hInstance); 此函数可告知操作系统此窗口类,并返回一个数字标识
if (Ret == 0) 此处用于判断是否注册了该窗口,注册失败时,返回的是0
{
MessageBox(NULL,"注册窗口类失败","提示",MB_OK);
}
创建窗口
创建窗口的函数
创建窗口的实例
HWND hWnd = CreateWindow(
wszClassName , 登记的窗口类名
wszTitle, 用来表明窗口的标题
WS_OVERLAPPEDWINDOW, 用来表明窗口的风格,如有无最大化,最小化按纽
CW_USEDEFAULT, 用来表明程序运行后窗口在屏幕中的坐标值,此为默认值
0, 用来表明程序运行后窗口在屏幕中的坐标值。
500, 用来表明窗口初始化时(即程序初运行时)窗口的宽度
500, 用来表明窗口初始化时(即程序初运行时)窗口的长度
nullptr, 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。
nullptr, 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。
hInstance, 应用程序实例句柄,与程序绑定
nullptr 最后一个参数是附加数据,一般都是0。
); 创建窗口实例,此时窗口在内存中被创建,但并没有被显示
创建窗口的原理
1.系统根据传入的窗口类名称,在应用程序局部窗口类中查找,若找到执行2,若未找到执行3。
2.比较局部窗口类与创建窗口时传入的HINSTANCE变量。如果发现相等,创建和注册的窗口类在
同一模块,创建窗口返回。如果不相等,继续执行3。
3.在应用程序全局窗口类,如果找到,执行4,如果未找到执行5。
4.使用找到的窗口类的信息,创建窗口返回。
5.在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败。
子窗口的创建
1.创建时要设置父窗口句柄
2.创建风格要增加 WS_CHILD|WS_VISIBLE
显示和更新窗口
API函数CreateWindow创建完窗口后,要想把它显示出现,还必须调用另一个API函数
ShowWindow,把nCmdShow参数传送给这个窗口。调用完ShowWindow后,还需要调用函数
UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,
这个消息将使窗口重画,即使窗口得到更新.且不通过消息循环。他们的原型是
ShowWindow的参数如下:
参数1:窗口句柄,告诉ShowWindow()显示哪一个窗口,函数UpdateWindow也一样
参数2:显示这个窗口的方式:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),最大化 (SW_SHOWMAXIMIZED)。
如下程序进行演示
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow); 初始化即创建窗口
{
if (!hWnd) 判断窗口是否被创建
{
return FALSE; 窗口创建失败,直接返回
}
ShowWindow(hWnd, nCmdShow); 显示窗口
UpdateWindow(hWnd); 更新窗口 参数为句柄
return TRUE;
}
创建消息循环
消息的概念和作用
消息的组成
1.窗口句柄
2.消息ID
3.消息的两个参数(两个附带信息)
4.消息产生的时间
5.消息产生时的鼠标位置
消息的作用
当系统通知窗口工作时,就采用消息的方式派发给窗口。
封装的消息结构体
操作系统将消息封装成MSG结构体投递到消息队列
MSG结构体原型如下:
typedef struct tagMSG
{
HWND hwnd; 要发送的窗口句柄。在多个窗口的应用程序中,该参数决定让哪个窗口接收消息
UINT message; 消息编号
WPARAM wParam; 消息的附加信息
LPARAM lParam; 消息的附加信息
DWORD time; 消息放入消息队列中的时间,其值是从Windows启动后所测量的时间值
POINT pt; 消息放入消息队列时的鼠标坐标.
}MSG;MSG结构:消息结构 GetMessage在消息队列中获取消息
GetMessage函数
消息循环以GetMessage调用开始,它从消息队列中取出一个消息:
BOOL GetMessage
(
LPMSG lpMsg, 接收消息的MSG结构的地址
HWND hWnd, 窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息
UINT wMsgFilterMin, 指定消息范围,此处为消息最小ID,后面三个参数被设置为默认值
UINT wMsgFilterMax 指定消息范围,此处为消息最大ID,后面三个参数被设置为默认值
);
lpMsg:当获取到消息后,将消息的参数存放到MSG结构中。
hWnd:获取到hWnd所指定窗口的消息。
wMsgFilterMin和wMsgFilterMax:只能获取到由它们指定的消息范围内的消息,如果都为0,表示
没有范围。
后三个参数被设置为默认值意味着应用程序会接收到发送到该应用程序任何一个窗口的所有消息。
该函数接收到除WM_QUIT之外的任何一个消息后,GetMessage()返回TRUE,否则返回FALSE
程序只有接受WM_QUIT才会结束消息循环,因此,在接收到WM_QUIT之前,带有GetMessage()
的消息循环可以一直循环下去。
TranslateMessage函数
TranslateMessage函数用于翻译消息,将按键消息,翻译成字符消息。
BOOL TranslateMessage
(
CONST MSG *lpMsg 要翻译的消息地址
);
该函数会事先检查消息是否是按键的消息,如果不是按键消息,不做任何处理,继续执行。
DispatchMessage函数
DispatchMessage函数用于派发消息,将消息派发到该消息所属窗口的窗口处理函数上。
LRESULT DispatchMessage
(
CONST MSG *lpmsg 要派发的消息
);
常见消息
WM_DESTROY消息
产生时间:窗口被销毁时的消息。
附带信息:wParam:为0,lParam : 为0。
一般用法:常用于在窗口被销毁之前,做相应的善后处理,例如资源、内存等。
WM_SYSCOMMAND消息
产生时间:当点击窗口的最大化、最小化、关闭等。
附带信息:
wParam : 具体点击的位置,例如关闭SC_CLOSE等.
lParam : 鼠标光标的位置。
LOWORD(lParam);水平位置
HIWORD(lParam);垂直位置
一般用法:常用在窗口关闭时,提示用户处理。
WM_CREATE消息
产生时间:在窗口创建成功但还未显示时。
附带信息:
wParam : 为0。
lParam : 为CREATESTRUCT类型的指针。通过这个指针可以获取CreatWindowEx中的全部12个
参数的信息。
一般用法:常用于初始化窗口的参数、资源等等,包括创建子窗口等。
WM_SIZE消息
产生时间:在窗口的大小发生变化后。
附带信息:wParam : 窗口大小变化的原因。
lParam : 窗口变化后的大小。
LOWORD(lParam) 变化后的宽度
HIWORD(lParam) 变化后的高度
一般用法:常用于窗口大小变化后,调整窗口内各个部分的布局。
WM_QUIT消息
产生时间:程序员发送时。
附带信息:
wParam : PostQuitMessage 函数传递的参数。
lParam : 0。
一般用法:用于结束消息循环,当GetMessage收到这个消息后,会返回FALSE,结束while处理,
退出消息循环
消息队列的概念
1.消息队列是用于存放消息的队列。
2.消息在队列中先入先出。
3.所有窗口程序都具有消息队列。
4.程序可以从队列中获取消息。
消息队列的分类
系统消息队列:由系统维护的消息队列。存放系统产生的消息,例如鼠标、键盘等。
程序消息队列:属于每一个应用程序(线程)的消息队列。由应用程序(线程)维护。
消息和队列关系
一.消息和消息队列的关系:
1.当鼠标、键盘产生消息时,会将消息存放到系统消息队列
2.系统会根据存放的消息,找到对应程序的消息队列。
3.将消息投递到程序的消息队列中。
二.根据消息和消息队列之间使用关系,将消息分成两类:
1.队列消息 - 消息的发送和获取,都是通过消息队列完成。
2.非队列消息 - 消息的发送和获取,是直接调用消息的窗口处理完成。
三.队列消息:消息发送后,首先放入队列,然后通过消息循环,从队列当中获取。
GetMessage - 从消息队列中获取消息
PostMessage - 将消息投递到消息队列
常见队列消息:WM_PAINT、键盘、鼠标、定时器。
四:非队列消息:消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成
消息。
SendMessage - 直接将消息发送给窗口的处理函数,并等候处理结果。
常见消息:WM_CREATE、WM_SIZE等。
消息循环的原理
Windows是以消息驱动的操作系统,它为每个正在运行的应用程序都保持一个消息队列,该队列用
于存储消息。当你按下鼠标或者键盘时,Windows并不是把这个输入事件直接送给应用程序,而是
将输入的事件先翻译成一个消息,然后把这个消息放入到这个应用程序的消息队列中去。
当主窗口显示出来后,应用程序的WinMain函数通过执行一段代码从应用程序的消息队列中来检索
Windows送往自身的消息。然后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段
代码是一段循环代码,故称为”消息循环”。
消息循环的流程
1.Windows不停获取消息并分发消息给相应的应用程序
2.应用程序获取消息以后,通过获取的消息对应处理调用窗口回调函数
消息循环的建立
MSG msg; 定义消息名
while (GetMessage(&msg,nullptr,0,0)) 窗口主循环,它是个死循环
{
TranslateMessage(&msg); 翻译传入的消息,如键盘打字传入的虚拟键码转换成字符消息
DispatchMessage(&msg); 派发翻译后的消息给窗口过程,由WindowsProc()来处理这个消息。
}
return msg.wParam ;
当WindowProc()处理完消息后,代码又循环到开始去接收另一个消息,此时完成了一个消息循环
窗口过程函数
当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。在窗口
处理函数中,不处理的消息,使用缺省窗口处理函数。例如:DefWindowProc。
LRESULT CALLBACK WndProc(
HWND hWnd, 窗口句柄
UINT message, 消息ID
WPARAM wParam, 参数wParam表明窗口表现的大小形式,如最小化或非最大化等等
LPARAM lParam 参数lParam包含了新窗口的大小,新宽度和新高度均为16位值,合在一起成
为32位的lParam。
) 声明主窗口过程 CALLBACK为回调函数
{
switch (message) 根据接收到的消息进行不同的分发
{
case WM_COMMAND: 处理应用的程序菜单,如点击程序帮助,接收的消息都会发送到此处
{
break;
}
case WM_PAINT: 绘制主窗口
{
PAINTSTRUCT ps; PAINTSTRUCT为一个结构,包含窗口的信息
HDC hDC = BeginPaint(hWnd, &ps); 绘制窗口,并返回用于在客户端区域中绘制的显示设备
上下文的句柄,在BeginPaint呼叫中,如果显示区域的背景还未被删除,则由Windows来删除
EndPaint(hWnd, &ps); 释放ps函数资源,结束绘制请求并释放设备上下文。
break;
}
case WM_DESTROY: 退出程序,必须有,否则点击窗口关闭,窗口消失但线程无法结束
{
PostQuitMessage(0); 向操作系统发送已经终止/退出的请求WM_QUIT以结束消息循环
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam); 默认窗口过程,操作系统将处理
我们不处理的消息
break;
}
return 0;
}
至此,我们完成了一个窗口的创建,接下来我们将实际应用该窗口:
窗口应用
int APIENTRY wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nCmdShow
)
{
MyRegisterClass(hInstance); 设置注册窗口类
if (!InitInstance(hInstance, nCmdShow)) 判断该窗口是否注册成功
{
return FALSE;
}
MSG msg; 创建信息循环体
while (GetMessage(&msg,nullptr,0,0)) 进行信息循环
{
TranslateMessage(&msg); 翻译信息
DispatchMessage(&msg); 分发信息
}
return 0;
}
此时一个窗口构建完毕,运行程序结果是
深谈GetMessage
在程序(线程)消息队列查找消息,如果队列有消息,检查消息是否满足指定条件(HWND,ID范
围),不满足条件就不会取出消息,否则从队列取出消息返回。
如果程序(线程)消息队列没有消息,向系统消息队列获取属于本程序的消息。如果系统队列的当
前消息属于本程序,系统会将消息转发到程序消息队列中。
如果系统消息队列也没有消息,检查当前进程的所有窗口的需要重新绘制的区域,如果发现有需要
绘制的区域,产生WM_PAINT消息,取得消息返回处理。
如果没有重新绘制区域,检查定时器如果有到时的定时器,产生WM_TIMER,返回处理执行。
如果没有到时的定时器,整理程序的资源、内存等等。
GetMessage会继续等候下一条消息。PeekMessage会返回FALSE,交出程序的控制权。注意:
GetMessage如果获取到是WM_QUIT,函数会返回FALSE。
补充
GetMessage用于从系统获取消息,将消息从系统中移除,阻塞函数。当系统无消息时,会等候下
一条消息。
PeekMessage用于以查看的方式从系统获取消息,可以不将消息从系统移除,非阻塞函数。当系
统无消息时,返回FALSE,继续执行后续代码。
SendMessage用于发送消息,会等候消息处理的结果。
PostMessage用于投递消息,消息发出后立刻返回,不等候消息执行结果。
BOOL SendMessage/PostMessage
(
HWND hWnd, 消息发送的目的窗口
UINT Msg, 消息ID
WPARAM wParam, 消息参数
LPARAM lParam 消息参数
);
系统消息 - ID范围 0 - 0x03FF
由系统定义好的消息,可以在程序中直接使用。
用户自定义消息 - ID范围 0x0400 - 0x7FFF(31743)
由用户自己定义,满足用户自己的需求。由用户自己发出消息,并响应处理。
自定义消息宏:WM_USER