现在在windows上实现窗口程序,大部分都是使用qt和mfc来实现的,这两种框架都是基于c++来实现的,也就是说都是通过面向对象的思想来实现的,但在windows底层都是通过面向过程的思想来实现的,所以今天来分享一下如何通过windows底层的方法来实现一个简单的窗口。
windows底层实现窗口主要分为以下6个步骤:
1、设计窗口
2、注册窗口
3、创建窗口
4、显示更新
5、循环获取消息队列
6、处理消息
首先需要创建一个窗口结构体WNDCLASS,然后需要对窗口的属性进行设置,具体如下
//1、设计窗口
WNDCLASS wc;
wc.cbClsExtra = 0;//类的额外内存
wc.cbWndExtra = 0;//窗口的额外内存
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//设置背景
wc.hCursor = LoadCursor(NULL,IDC_HAND);//设置光标,如果第一个参数为NULL,代表使用系统提供的光标
wc.hIcon = LoadIcon(NULL,IDI_ERROR);//图标
wc.hInstance = hInstance;//应用程序实力句柄 传入WinMain的形参
wc.lpfnWndProc = WindowProc;//回调函数 窗口过程
wc.lpszClassName = TEXT("WIN");//指定窗口类名称
wc.lpszMenuName = NULL;//菜单名称
wc.style = 0;//显示风格 0代表默认风格
第二步:RegisterClass(&wc); 注册窗口
第三步:创建窗口
//3、创建窗口
/*
lpClassName, 类名
lpWindowName, 标题名
dwStyle, 风格
x, 显示坐标
y,\
nWidth, 宽高
nHeight,
hWndParent, 父窗口 NULL
hMenu, 菜单 NULL
hInstance, 实例句柄
lpParam 附加值 鼠标
*/
HWND hwnd = CreateWindow(wc.lpszClassName,TEXT("WINDOWS"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT
, CW_USEDEFAULT, CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
第四步:显示和刷新窗口
//4、显示和更新
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
第五步:利用死循环一直读取消息队列中的消息
底层中的消息传递如图所示
先由用户触发消息后传递给操作系统,操作系统再将消息放进消息队列中,应用程序一直获取消息队列中的消息,然后再将消息传递给操作系统,操作系统再去调用对应的回调函数(窗口过程)。完成消息的相应。
消息传递主要利用MSG结构体,代码如下
MSG msg;
while (1)
{
/*
_Out_ LPMSG lpMsg, 消息
_In_opt_ HWND hWnd, 捕获窗口 填NULL代表捕获所有的窗口
_In_ UINT wMsgFilterMin, 最小和最大的过滤的消息 一般填入0
_In_ UINT wMsgFilterMax);填0代表捕获所有消息
*/
if (GetMessage(&msg,NULL,0,0) == FALSE)
{
break;
}
//翻译消息
TranslateMessage(&msg);
//不为false
//分发消息
DispatchMessage(&msg);
}
这边为什么会有个翻译消息的函数呢?
主要是由于用户操作如果是正常点击一个按键的话是不会有问题,但是如果当用户同时点击两个按键,例如同时按下ctrl+c,这时候正常操作应该是复制操作,但是如果不进行翻译的话,就会被系统识别成按下ctrl键和c,明显不是想要的结果,所以需要先进行翻译消息后再分发消息给操作系统。
最后是回调函数,即处理消息
//CALLBACK 代表__stdcall 参数的传递顺序,从右到左 依次入栈
LRESULT CALLBACK WindowProc(
HWND hwnd, //消息所属的窗口句柄
UINT uMsg, //具体的消息名称 WM_XXXX 消息名
WPARAM wParam, //键盘附加消息
LPARAM lParam) //鼠标附加消息
{
switch (uMsg)
{
case WM_CLOSE:
//所有xxxWindow为结尾的方法,都不会进入到消息队列中,而是直接执行
DestroyWindow(hwnd); //DestroyWindow 发送另一个消息 WM_DESTROY
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN: //鼠标左键按下
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
char buf[1024] = { 0 };
wsprintf(buf,TEXT("x = %d,y = %d"), xPos, yPos);
MessageBox(hwnd, buf, TEXT("鼠标左键") ,MB_OK);
break;
}
case WM_KEYDOWN:
{
MessageBox(hwnd, TEXT("键盘按下"), TEXT("键盘按下"), MB_OK);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;//绘图结构体
HDC hdc = BeginPaint(hwnd, &ps);
TextOut(hdc,100,100,TEXT("hello world"),strlen("hello world"));
EndPaint(hwnd, &ps);
break;
}
}
//返回值用默认处理方式
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
这边需要注意的是,如果不进行PostQuitMessage(0);退出消息操作的话,程序会一直存在,因为程序还在处理消息的处理中。
接下来是完整代码的分享
#include <windows.h>
//程序入口函数
//CALLBACK 代表__stdcall 参数的传递顺序,从右到左 依次入栈
LRESULT CALLBACK WindowProc(
HWND hwnd, //消息所属的窗口句柄
UINT uMsg, //具体的消息名称 WM_XXXX 消息名
WPARAM wParam, //键盘附加消息
LPARAM lParam) //鼠标附加消息
{
switch (uMsg)
{
case WM_CLOSE:
//所有xxxWindow为结尾的方法,都不会进入到消息队列中,而是直接执行
DestroyWindow(hwnd); //DestroyWindow 发送另一个消息 WM_DESTROY
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN: //鼠标左键按下
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
char buf[1024] = { 0 };
wsprintf(buf,TEXT("x = %d,y = %d"), xPos, yPos);
MessageBox(hwnd, buf, TEXT("鼠标左键") ,MB_OK);
break;
}
case WM_KEYDOWN:
{
MessageBox(hwnd, TEXT("键盘按下"), TEXT("键盘按下"), MB_OK);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;//绘图结构体
HDC hdc = BeginPaint(hwnd, &ps);
TextOut(hdc,100,100,TEXT("hello world"),strlen("hello world"));
EndPaint(hwnd, &ps);
break;
}
}
//返回值用默认处理方式
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
//WINAPI 代表__stdcall 参数的传递顺序,从右到左 依次入栈
//并且在函数返回前 清空堆栈
int WINAPI WinMain(
HINSTANCE hInstance, //应用程序实例句柄
HINSTANCE hPrevInstance, //上一个应用程序句柄
LPSTR lpCmdLine, //char *argv[]
int nShowCmd) //显示命令
{
//1、设计窗口
WNDCLASS wc;
wc.cbClsExtra = 0;//类的额外内存
wc.cbWndExtra = 0;//窗口的额外内存
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//设置背景
wc.hCursor = LoadCursor(NULL,IDC_HAND);//设置光标,如果第一个参数为NULL,代表使用系统提供的光标
wc.hIcon = LoadIcon(NULL,IDI_ERROR);//图标
wc.hInstance = hInstance;//应用程序实力句柄 传入WinMain的形参
wc.lpfnWndProc = WindowProc;//回调函数 窗口过程
wc.lpszClassName = TEXT("WIN");//指定窗口类名称
wc.lpszMenuName = NULL;//菜单名称
wc.style = 0;//显示风格 0代表默认风格
//2、注册窗口
RegisterClass(&wc);
//3、创建窗口
/*
lpClassName, 类名
lpWindowName, 标题名
dwStyle, 风格
x, 显示坐标
y,\
nWidth, 宽高
nHeight,
hWndParent, 父窗口 NULL
hMenu, 菜单 NULL
hInstance, 实例句柄
lpParam 附加值 鼠标
*/
HWND hwnd = CreateWindow(wc.lpszClassName,TEXT("WINDOWS"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT
, CW_USEDEFAULT, CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
//4、显示和更新
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
//5、通过循环取消息
/*
HWND hwnd; 主窗口句柄
UINT message; 具体消息名称
WPARAM wParam; 附加消息 键盘消息
LPARAM lParam; 附加消息 鼠标消息
DWORD time; 消息产生的时间
POINT pt; 附加消息 鼠标消息 x,y
*/
MSG msg;
while (1)
{
/*
_Out_ LPMSG lpMsg, 消息
_In_opt_ HWND hWnd, 捕获窗口 填NULL代表捕获所有的窗口
_In_ UINT wMsgFilterMin, 最小和最大的过滤的消息 一般填入0
_In_ UINT wMsgFilterMax);填0代表捕获所有消息
*/
if (GetMessage(&msg,NULL,0,0) == FALSE)
{
break;
}
//翻译消息
TranslateMessage(&msg);
//不为false
//分发消息
DispatchMessage(&msg);
}
//6、处理消息(窗口过程)
return 0;
}