最近更新于 2024-05-05 14:18
环境
Windows 11 专业工作站版 22H2
Visual Studio 2022 专业版
Unicode 字符集,C17 标准编译,64 位编译
代码
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS // 关闭对不使用 _s 安全函数时的报错
#endif
#include <Windows.h>
HANDLE g_stdo; // 用于存储标准输出句柄
WCHAR g_output_info[64]; // 用于存储输出到控制台的信息
/**
* @brief 窗口过程函数 - 窗口消息处理
* @param hWnd 窗口的句柄
* @param message 消息代码
* @param wParam 其他消息信息,确切含义取决于消息代码
* @param lParam 其他消息信息,确切含义取决于消息代码
* @return 消息处理的结果
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = { 0 };
WORD h = 0, v = 0; // 存储水平方向和垂直方向的坐标或长度
memset(g_output_info, 0, sizeof(g_output_info));
switch (message)
{
case WM_CREATE: // 在窗口创建后,显示窗口之前会收到一次该消息代码。一般用于做初始化,创建子窗口等工作。
{
CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam; // CREATESTRUCT 结构保存创建窗口函数的 12 个参数
wsprintf(g_output_info, L"WM_CREATE:%s\n", (PWSTR)pCreate->lpCreateParams);
// 设置定时器
// 句柄
// ID
// 时间间隔(ms)
// 处理函数,一般不用
SetTimer(hWnd, 100, 2000, NULL);
SetTimer(hWnd, 200, 4000, NULL);
break;
}
case WM_TIMER: // 匹配定时器 ID
switch (wParam)
{
case 100:
wcscpy(g_output_info, L"定时器1 消息\n");
break;
case 200:
wcscpy(g_output_info, L"定时器2 消息\n");
break;
default:
break;
}
break;
case WM_KEYDOWN: // 键盘按键按下
wsprintf(g_output_info, L"按键按下,键码值为:%I64d\n", wParam);
break;
case WM_KEYUP: // 键盘按键放开
wsprintf(g_output_info, L"按键放开,键码值为:%I64d\n", wParam);
break;
// Alt 和 F10 为系统键,以及按 Alt 键和其它键的组合
case WM_SYSKEYDOWN: // 键盘系统键按下
wsprintf(g_output_info, L"系统键按下,键码值为:%I64d\n", wParam);
break;
case WM_SYSKEYUP: // 键盘系统键放开
wsprintf(g_output_info, L"系统键放开,键码值为: %I64d\n", wParam);
break;
case WM_CHAR: // 该消息由 TranslateMessage 翻译后发出,对大小写字符会区分以及配合上档键输出的按键第二字符
wsprintf(g_output_info, L"按键字符为:%lc,对应键值码为:%I64d\n", (WCHAR)wParam, wParam);
break;
case WM_LBUTTONDOWN: // 鼠标左键按下
wsprintf(g_output_info, L"鼠标左键按下,其它按键状态:%I64d,鼠标位置:%d %d\n", wParam, LOWORD(lParam), HIWORD(lParam));
break;
case WM_LBUTTONUP: // 鼠标左键放开
wsprintf(g_output_info, L"鼠标左键放开,其它按键状态:%I64d,鼠标位置:%d %d\n", wParam, LOWORD(lParam), HIWORD(lParam));
break;
case WM_RBUTTONDOWN: // 鼠标右键按下
wsprintf(g_output_info, L"鼠标右键按下,其它按键状态:%I64d,鼠标位置:%d %d\n", wParam, LOWORD(lParam), HIWORD(lParam));
break;
case WM_RBUTTONUP: // 鼠标右键放开
wsprintf(g_output_info, L"鼠标右键放开, 其它按键状态:%I64d,鼠标位置:%d %d\n", wParam, LOWORD(lParam), HIWORD(lParam));
break;
//case WM_MOUSEMOVE: // 鼠标移动
// wsprintf(g_output_info, L"鼠标移动到:%d %d\n", LOWORD(lParam), HIWORD(lParam));
// break;
case WM_MOUSEWHEEL: // 鼠标滚轮滚动
wsprintf(g_output_info, L"鼠标滚动量(前为正,后为负):%d,其它按键状态:%d,鼠标位置:%d %d\n", (INT16)HIWORD(wParam), LOWORD(wParam), LOWORD(lParam), HIWORD(lParam));
break;
case WM_LBUTTONDBLCLK: // 鼠标左键双击
wsprintf(g_output_info, L"鼠标左键双击,其它按键状态:%I64d,鼠标位置:%d %d\n", wParam, LOWORD(lParam), HIWORD(lParam));
break;
case WM_RBUTTONDBLCLK: // 鼠标右键双击
wsprintf(g_output_info, L"鼠标右键双击,其它按键状态:%I64d,鼠标位置:%d %d\n", wParam, LOWORD(lParam), HIWORD(lParam));
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 5, 5, L"你好,世界!", wcslen(L"你好,世界!"));
EndPaint(hWnd, &ps);
break;
case WM_SIZE:
h = LOWORD(lParam);
v = HIWORD(lParam);
wsprintf(g_output_info, L"窗口大小修改为:%dx%d\n", h, v);
break;
case WM_SYSCOMMAND: // 点击最小化、最大化和关闭等等操作会发出该信号
if (wParam == SC_CLOSE)
{
if (MessageBox(hWnd, L"确认退出?", L"确认信息", MB_OKCANCEL) == IDOK)
{
DestroyWindow(hWnd); // 销毁窗口
}
}
else if (wParam == SC_MAXIMIZE)
{
wcscpy(g_output_info, L"SC_MAXIMZE:点击最大化按钮\n");
break;
}
else if (wParam == SC_MINIMIZE)
{
wcscpy(g_output_info, L"SC_MINIMZE:点击最小化按钮\n");
break;
}
else
{
DefWindowProc(hWnd, message, wParam, lParam);
}
h = LOWORD(lParam);
v = HIWORD(lParam);
wsprintf(g_output_info, L"鼠标点击位置:%dx%d\n", h, v); // 只有点击边框和标题栏时
break;
case WM_DESTROY: // 执行销毁窗口会收到该消息,可以在这里处理善后
PostQuitMessage(0); // 在消息队列上放置 WM_QUIT 消息,会让消息循环停止(GetMessage 收到该消息返回 0)
// 关闭定时器
KillTimer(hWnd, 100);
KillTimer(hWnd, 200);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam); // 默认消息处理
}
WriteConsole(g_stdo, g_output_info, wcslen(g_output_info), NULL, NULL);
}
/**
* @brief Windows 程序入口点函数
* @param hInstance 称为“实例句柄”或“模块句柄”。操作系统使用此值在内存中加载可执行文件时标识可执行文件 (EXE) 。 某些Windows函数需要实例句柄,例如加载图标或位图
* @param hPreInstance 没有意义。 它在 16 位Windows中使用,但现在始终为零
* @param lpCmdLine 包含命令行参数作为 Unicode 字符串
* @param nCmdShow 是一个标志,指示主应用程序窗口是最小化、最大化还是正常显示
* @return 返回 int 值。 操作系统不使用返回值,但可以使用返回值将状态代码传达给你编写的其他程序
*/
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
AllocConsole(); // 申请控制台窗口用于显示调试信息
g_stdo = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄
PCWSTR szWindowClass = L"DesktopAPP"; // 设置的窗口类的名字
WNDCLASSEX wcex = { 0 };
wcex.cbSize = sizeof(WNDCLASSEX); // 本结构的大小 - 必须设置 1
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; // 类样式 // 添加 CS_DBLCLKS 后,支持接收鼠标双击消息
wcex.lpfnWndProc = WndProc; // 指向窗口过程的指针 - 必须设置 2
wcex.cbClsExtra = 0; // 要按照窗口类结构分配的额外字节数。 系统将字节初始化为零
wcex.cbWndExtra = 0; // 在窗口实例之后分配的额外字节数。 系统将字节初始化为零
wcex.hInstance = hInstance; // 包含类的窗口过程的实例的句柄
wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION); // 类图标的句柄。 此成员必须是图标资源的句柄。 如果此成员为 NULL,则系统提供默认图标
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // 类游标的句柄。 此成员必须是游标资源的句柄。 如果此成员为 NULL,则每当鼠标移动到应用程序的窗口中时,应用程序都必须显式设置光标形状
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 类背景画笔的句柄。 此成员可以是用于绘制背景的画笔的句柄,也可以是颜色值(颜色值宏可查阅开发文档)
wcex.lpszMenuName = NULL; // 指向一个 null 终止的字符串的指针,该字符串指定类菜单的资源名称,因为名称显示在资源文件中
wcex.lpszClassName = szWindowClass; // 窗口类名字 - 必须设置 3
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); // 与窗口类关联的小图标的句柄
if (!RegisterClassEx(&wcex)) // 注册窗口类
{
MessageBox(NULL, L"注册窗口失败!", L"错误", MB_OK);
return 1;
}
// 创建窗口
// 第一个参数:允许为窗口指定一些可选行为 (,例如透明窗口) 。 对于默认行为,此参数设置为零
// 第二个参数:窗口类的名字
// 第三个参数:窗口文本,通过不同类型的窗口以不同的方式使用。 如果窗口具有标题栏,则文本将显示在标题栏中
// 第四个参数:窗口样式为一组标志,用于定义窗口的一些外观。 常量 WS_OVERLAPPEDWINDOW 实际上是几个标志与按位 OR 组合。
// 这些标志一起为窗口提供标题栏、边框、系统菜单,以及 最小化 和 最大化 按钮。 此标志集是顶级应用程序窗口最常见的样式
// 第五和第六个参数:窗口的初始位置,设置 CW_USEDEFAULT 为默认,在左上角位置
// 第七和第八个参数:窗口的初始大小,设置 CW_USEDEFAULT 为默认,系统为窗口选择默认宽度和高度
// 第九个参数:设置新窗口的父窗口或所有者窗口。 如果要创建子窗口,请设置父窗口。 对于顶级窗口,请将此窗口设置为 NULL
// 第十个参数:定义窗口的菜单,不使用菜单设置 NULL
// 第十一个参数:实例句柄
// 第十二个参数:LPVOID 即 void *,用于向窗口过程传递数据
HWND hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, szWindowClass, L"第一个桌面程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 100, NULL, NULL, hInstance, L"我是窗口创建函数传入的信息");
if (!hWnd)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
return 1;
}
ShowWindow(hWnd, nCmdShow); // 设置指定窗口的显示状态,比如最小化和最大化等等
UpdateWindow(hWnd); // 发送 WM_PAINT 到窗口过程,用于更新指定窗口的工作区(如果有需要更新的内容的话)
MSG msg = { 0 };
// 从消息队列拉取消息
// 第一个参数用于存储抓取的消息
// 第二个参数可以指定句柄,只抓取某个窗口的消息,设为 NULL,抓取所有窗口的
// 第三第四参数指定抓取的消息代码范围,都为 0 全部抓取
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); // 将键盘可见字符消息翻译为对应的字符内容
DispatchMessage(&msg); // 将消息调度到窗口过程
}
// 使用 PostQuitMessage 发送 WM_QUIT 消息
// WM_QUIT 对 wParam 内容的定义就是 PostQuitMessage 的参数
// 也就是 PostQuitMessage 参数设置的值是多少,这里的 msg.wParam 就是多少
return (int)msg.wParam;
}
运行效果
最简版
前面那个示例程序,我尽可能把最基础的部分都包含进去了,新手看起来可能会比较头疼,这里就搞一个简版的,只是单纯窗口中显示“你好,世界!”
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, L"你好,世界!", wcslen(L"你好,世界!"));
EndPaint(hWnd, &ps);
break;
}
case WM_CLOSE:
{
DestroyWindow(hWnd);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
return 0;
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreHinstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
PCWSTR window_class_name = L"Desktop";
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = window_class_name;
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"注册窗口类失败!", L"错误", MB_OK);
return 1;
}
HWND hWnd = CreateWindowEx(0, window_class_name, L"简单窗口程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 260, 80, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
桌面程序入门 hello world – Windows API