底层实现一个windows窗口
前期铺垫
SDK和API
SDK:软件开发工具包(Software Development Kit),一般都是一些被软件工程师用于特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。
API函数:Windows操作系统提供给应用程序编程的接口(Application Programing Interface),Windows应用程序API函数是通过C语言实现的,所有的Windows函数都在Windows.h头文件中进行了声明。
窗口和句柄
窗口:窗口是屏幕上的一块矩形区域,是Windows应用程序与用户进行交互的接口,利用窗口可以接受用户的输入以及显示输出。一个应用程序窗口通常包括标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框,有的还有滚动条。
句柄:在Windows程序中,有各种各样的资源(窗口、图标、光标、画刷等),系统创建这些资源时会为它们分配内存,并返回这些资源的标识号,即句柄。比如图标句柄(HICON)、光标句柄(HCURSOR)、画刷句柄(HBRUSH)。
消息和消息队列
Windows程序设计是一种事件驱动方式的程序设计模式,主要是基于消息的。
每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息。
例如,当用户在窗口中画图的时候,按下鼠标左键,此时操作系统会感知到这一事件,于是将这一事件包装成一个消息,投递到应用程序的消息队列中,等待应用程序的处理。
然后应用程序通过一个消息循环不断从消息队列中取出消息再进行响应。
在这个处理过程中,操作系统也会给应用程序“发送消息”,实际上是操作系统调用应用程序中一个专门处理消息的函数,这个函数称为窗口过程。
WinMain函数
WinMain函数是Windows程序的入口点函数,当WinMain函数结束或返回时,Windows应用程序结束
具体六个步骤
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_WARNING); //设置图标,如果第一个参数为NULL,代表使用系统提供的图标
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, WS_OVERLAPPEDWINDOW 风格
x, 显示坐标 CW_USEDEFAULT 默认值
y,
nWidth, 宽高
nHeight,
hWndParent, 父窗口 NULL
hMenu, 菜单 NULL
hInstance, 实例句柄 hInstance
lpParam 附加值 NULL
*/
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; 具体消息名称 WM_XXX 消息名
WPARAM wParam; 附加消息 键盘消息
LPARAM lParam; 附加消息 鼠标消息
DWORD time; 消息产生时间
POINT pt; 附加消息 鼠标消息 x y
*/
MSG msg;
while (TRUE)
{
/*
_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、处理消息(窗口过程)
//CALLBACK 代表__stdcall 参数的传递顺序,从右到左依次入栈,并且在函数返回之前清空堆栈
LRESULT CALLBACK WindowProc(
HWND hwnd,//消息所属的窗口句柄
UINT uMsg,//具体消息名称
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);
wchar_t buf[1024];
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"), strlen("Hello"));
break;
}
}
//返回值使用默认处理方式
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
完整的文件
#include<windows.h>//底层实现窗口的头文件
//6、处理窗口过程
//CALLBACK 代表__stdcall 参数的传递顺序,从右到左依次入栈,并且在函数返回之前清空堆栈
LRESULT CALLBACK WindowProc(
HWND hwnd,//消息所属的窗口句柄
UINT uMsg,//具体消息名称
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);
wchar_t buf[1024];
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"), strlen("Hello"));
break;
}
}
//返回值使用默认处理方式
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//程序入口函数
//WINAPI 代表__stdcall 参数的传递顺序,从右到左依次入栈,并且在函数返回之前清空堆栈
int WINAPI WinMain(
HINSTANCE hInstance, //应用程序实例句柄
HINSTANCE hPrevInstance, //上一个应用程序员句柄
LPSTR lpCmdLine, //char * argv[]
int nShowCmd) //显示命令 最大化、最小化 正常
{
//1、设计窗口
//2、注册窗口
//3、创建窗口
//4、显示和更新
//5、通过循环取消息
//6、处理消息(窗口过程)
//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_WARNING); //设置图标,如果第一个参数为NULL,代表使用系统提供的图标
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, WS_OVERLAPPEDWINDOW 风格
x, 显示坐标 CW_USEDEFAULT 默认值
y,
nWidth, 宽高
nHeight,
hWndParent, 父窗口 NULL
hMenu, 菜单 NULL
hInstance, 实例句柄 hInstance
lpParam 附加值 NULL
*/
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; 具体消息名称 WM_XXX 消息名
WPARAM wParam; 附加消息 键盘消息
LPARAM lParam; 附加消息 鼠标消息
DWORD time; 消息产生时间
POINT pt; 附加消息 鼠标消息 x y
*/
MSG msg;
while (TRUE)
{
/*
_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);
}
return 0;
}
效果图: