窗口创建
首先了解一下创建窗口的API,CreateWindow以及CreateWindowEx。
CreateWindow函数是Windows API中一个非常重要的函数,用于创建一个新的窗口。
- 原型
HWND CreateWindow(
LPCTSTR lpClassName,//指定窗口类名。可以是预定义的窗口类(如"Button"、"Edit"等)或自定义的窗口类。
LPCTSTR lpWindowName,//指定窗口标题栏文本,也就是窗口标题栏的名字
DWORD dwStyle,//指定窗口样式,可以是多个样式的组合,如WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU等。
int x,//窗口左上角水平坐标位置
int y,//窗口左上角垂直坐标位置
int nWidth,//窗口的宽度
int nHeight,//窗口的高度
HWND hWndParent,//指定父窗口的句柄,如果窗口没有父窗口则传入NULL。
HMENU hMenu,//指定窗口菜单的句柄,如果没有菜单则传入NULL。
HINSTANCE hInstance,//指定创建窗口的应用程序实例句柄。
LPVOID lpParam//窗口创建时附加参数,可以为NULL。
);
- 返回值
该函数返回一个HWND类型的值,代表新创建窗口的句柄。如果创建失败,返回NULL。
CreateWindowEx与CreateWindow函数相比只是增加了更多的窗口样式选项。
HWND CreateWindowEx(
DWORD dwExStyle,//指定额外的窗口样式。可以是多个样式的组合,如WS_EX_CLIENTEDGE、WS_EX_LAYERED等。
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
这里重点是第三个参数,窗口风格
微软官网详解: 窗口样式
窗口创建执行过程
也就是CreateWindow这个API是怎么实现的
- 1.CreateWindow根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到执行2如果未找到执行3。
- 2.比较局部窗口类与创建窗口时传入的HINSTANCE变量。如果发现相等,创建和注册的窗口类在同一模块,创建窗口返回。如果不相等,继续执行3。
- 3.在应用程序全局窗口类,如果找到,执行4,如果未找到执行5
- 4.使用找到的窗口类的信息,创建窗口返回
- 5.在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败
简单来说内部的实现过程可以概述为:
1.检查输入参数的合法性,如窗口类名、窗口样式、父窗口句柄等。如果参数不合法,函数返回NULL并设置相应的错误码。
2.查找已注册的窗口类,如果没有找到对应的窗口类,则返回错误。
3.分配内存空间来保存新窗口的数据结构,包括窗口句柄、窗口类信息、窗口样式、位置大小等。
4.初始化新窗口的数据结构,填充相关信息。
5.将新窗口添加到系统维护的窗口链表中。
6.调用低层次的窗口创建函数,如NtUserCreateWindowEx,来向操作系统发出创建窗口的请求。
7.如果窗口创建成功,返回新窗口的句柄;否则释放之前分配的内存空间,返回NULL并设置错误码。
关于1.3中程序关闭窗口后依然在后台运行如何退出的问题
可以看到,即使关闭了窗口,程序依旧在后台运行着
原因分析:
这里简单讲下这个API,后续会有详细的讲解
简单理解就是PostQuitMessage可以让GetMessage函数返回0
PostQuitMessage函数是Windows API中用于向应用程序的消息队列发送WM_QUIT消息的函数。
PostQuitMessage函数向应用程序的主线程消息队列发送WM_QUIT消息。
当应用程序的主消息循环收到WM_QUIT消息时,它会退出消息循环,应用程序也随之结束运行。
通常在应用程序的主窗口过程中,当用户关闭窗口时会调用PostQuitMessage函数,以结束应用程序的主消息循环。
除了在窗口过程中调用,您也可以在应用程序的其他地方调用PostQuitMessage函数,以结束应用程序的执行。
子窗口创建过程
- 创建时要设置父窗口句柄
- 创建风格要增加WS_CHILD | WS_VISIBLE 这两个风格
源码如下:
#include <windows.h>
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)
{
switch (msgID)
{
case WM_DESTROY:
PostQuitMessage(0);//向应用程序的消息队列发送WM_QUIT消息的函数
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(_In_ HINSTANCE hIns, _In_opt_ HINSTANCE hPreIns, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
//注册窗口类(向向操作系统写入一些数据)
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0; //申请缓冲区
wc.cbWndExtra = 0; //申请缓冲区
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //窗口背景色
wc.hCursor = NULL; //光标,NULL表示默认由系统指定
wc.hIcon = NULL; //图标,NULL表示默认图标,即窗口左上角小图标
wc.hInstance = hIns; //当前程序实例句柄 ******
wc.lpfnWndProc = WndProc; //窗口处理函数名
wc.lpszClassName = "Main"; //窗口类名字 ******
wc.lpszMenuName = NULL; //无菜单
wc.style = CS_HREDRAW | CS_VREDRAW; //当窗口宽度货高度发生改变时,重绘整个窗口
RegisterClass(&wc); //将以上所有赋值全部写入操作系统
//在内存中创建窗口
HWND hWnd = CreateWindowEx(0,"Main", "windows", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hIns, NULL);
wc.cbClsExtra = 0; //申请缓冲区
wc.cbWndExtra = 0; //申请缓冲区
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 3); //窗口背景色
wc.hCursor = NULL; //光标,NULL表示默认由系统指定
wc.hIcon = NULL; //图标,NULL表示默认图标,即窗口左上角小图标
wc.hInstance = hIns; //当前程序实例句柄 ******
wc.lpfnWndProc = DefWindowProc; //窗口处理函数名
wc.lpszClassName = "Child"; //窗口类名字 ******
wc.lpszMenuName = NULL; //无菜单
wc.style = CS_HREDRAW | CS_VREDRAW; //当窗口宽度货高度发生改变时,重绘整个窗口
RegisterClass(&wc); //将以上所有赋值全部写入操作系统
//创建子窗口
HWND hChild1 = CreateWindowEx(0, "child", "c1", WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 200, 200, hWnd, NULL, hIns, NULL);
HWND hChild2 = CreateWindowEx(0, "child", "c2", WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW, 200, 0, 200, 200, hWnd, NULL, hIns, NULL);
//显示窗口
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd); //刷新窗口
//消息循环
MSG nMsg = { 0 };
while (GetMessage(&nMsg, NULL, 0, 0))
{
TranslateMessage(&nMsg);
DispatchMessage(&nMsg); //将消息交给窗口处理函数来处理
}
return 0;
}