win32 - 定时器、菜单/图标/光标/字符串 资源
应用程序分类
- 控制台程序
- 窗口程序
- 静态库
LIB文件,链接时将代码放入exe - 动态库
DLL文件,执行时动态获取代码
编译工具
- 编译器 CL.EXE 将源代码编译成目标代码 .obj
- 链接器 LINKl.EXE 将目标代码、库链接
- 资源编译器 RC.EXE 将rc资源文件编译,最终通过链接器存入最终文件
库:
C:\Windows\System32
- kernel32.dll 提供核心的API,例如进程、线程、内存管理
- user32.dll 提供了窗口、消息等API
- gdi32.dll 绘图相关的API
头文件:
- windows.h 所有windows头文件集合
- windef.h windows数据类型
- winbase.h kernel32的API
- wingdi.h gdi32的API
- winuser user32的API
- winnt.h UNICODE字符集支持
编译流程:
- cl.exe -c xxx.cpp
- link.exe xxx.obj user.lib
win32 打印汉字:
WriteConsole 函数可以正常打印汉字,unicode和非Unicode字符都可以打印。
HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE); // win下标准输出不是1,是一个会变化的值,使用此函数和宏获得stdio stdout stderr
WriteConsole(stdout, pszText, wcslen(pszText), NULL, NULL); // 向stdout写汉字
定时器消息
过一定时间间隔,发送一个 WM_TIMER 消息。精度为毫秒,但精度低。wParam: 定时器ID;lParam: 定时器处理函数的指针。
本质上是GetMessage取获取消息,当没有其他消息时会检测定时器,然后产生此消息。因为GetMessage会不断优先获取其他消息,所以定时器并不准;如果一直有其他消息让GetMessage获得,那么将不会产生WM_TIMER消息。
创建销毁定时器:
WINUSERAPI
UINT_PTR
WINAPI
SetTimer(
_In_opt_ HWND hWnd, // 处理消息的窗口句柄
_In_ UINT_PTR nIDEvent, // 定时器ID
_In_ UINT uElapse, // 时间间隔
_In_opt_ TIMERPROC lpTimerFunc); // 定时器处理函数指针,一般为NULL
WINUSERAPI
BOOL
WINAPI
KillTimer(
_In_opt_ HWND hWnd, // 窗口句柄
_In_ UINT_PTR uIDEvent); // 定时器ID
示例:
#include <Windows.h>
#include <stdio.h>
#include <string.h>
// 在 win32 中获取控制台
HANDLE g_hOutput = 0;
LRESULT CALLBACK WnProc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg)
{
case WM_CREATE:
{
SetTimer(hwnd, 1, 1000, NULL);
SetTimer(hwnd, 2, 1000, NULL);
break;
}
case WM_TIMER:
{
WCHAR szText[256] = { 0 };
swprintf_s(szText, L"timer: %lld\n", wparam);
switch (wparam)
{
case 1:
// 定时器 1
WriteConsole(g_hOutput, szText, lstrlen(szText), NULL, NULL);
break;
case 2:
// 定时器 2
WriteConsole(g_hOutput, szText, lstrlen(szText), NULL, NULL);
break;
default:
break;
}
break;
}
case WM_CLOSE:
{
DestroyWindow(hwnd);
PostQuitMessage(0);
}
default:
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int nCmdShow)
{
// 在 win32 中获取控制台
AllocConsole();
g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
WNDCLASS wndclass;
ZeroMemory(&wndclass, sizeof(WNDCLASS));
wndclass.lpfnWndProc = WnProc;
wndclass.lpszClassName = L"MyWndClass";
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("regest windwos class failed!"), L"failed", MB_ICONERROR);
return 0;
}
HWND hwnd = CreateWindow(
wndclass.lpszClassName,
L"windows name",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
ShowWindow(hwnd, SW_NORMAL);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
菜单资源
具体的来说,菜单是一种容器。内部装的菜单项。
1. 菜单分类
- 窗口顶层菜单
窗口上部菜单 - 弹出式菜单
鼠标右键菜单
顶层菜单点击后出现的菜单 - 系统菜单
HMENU类型表示菜单
ID表示菜单项。
2. 资源相关
rc脚本描述资源文件,rc.exe编译器翻译rc脚本。cpp文件通过cl.exe编译为obj文件,rc脚本使用rc.exe编译为res文件,再使用链接器生成exe。
3. 菜单资源使用
- 添加菜单资源
创建菜单资源后会生成一个图形化界面,顶部就是顶层菜单资源。点击框并输入名称,右键点击查看属性。
自己设置一个ID,便于以后接收
图中IDR_MENU1
为资源ID,用于获取刚刚制作的菜单资源。
rc文件内容:
生成rc文件时配套生成了resource.h
文件,可以通过这个头文件找到定义。 - 加载菜单资源
三种方法,注意导入头文件。- 注册窗口类时设置菜单
wndClass.lpszMenuName = (char *)IDR_MENU1;
- 创建窗口传参设置菜单
HWND hwnd = CreateWindow( wndclass.lpszClassName, L"windows name", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, LoadMenu(hInstance, (PWCHAR)IDR_MENU1), // 使用函数从内存中加载资源文件获得 HMENU hInstance, NULL );
- 在主窗口 WM_CREATE 消息中利用 SetMenu 设置菜单
HINSTANCE g_hInstance = 0; // 加载菜单资源时需要使用 hInstance 获得内存地址,使用全局变量保存,在main函数中赋值 LRESULT CALLBACK WnProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { case WM_CREATE: { SetMenu(hwnd, LoadMenu(g_hInstance, (PWCHAR)IDR_MENU1)); break; } default: break; } return DefWindowProc(hwnd, msg, wparam, lparam); }
- 注册窗口类时设置菜单
- 相关函数
WINUSERAPI HMENU WINAPI LoadMenuA( _In_opt_ HINSTANCE hInstance, // handle to module _In_ LPCSTR lpMenuName); // menu name or resource identifier WINUSERAPI BOOL WINAPI SetMenu( _In_ HWND hWnd, _In_opt_ HMENU hMenu);
4. 命令消息处理
点击菜单产生 WM_COMMAND 消息
附带信息:
- wPARAM
- HIWORD 对于菜单为0
- LOWORD 菜单项ID
- lPARAM 对菜单为0,无用
LRESULT CALLBACK WnProc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg)
{
case WM_CREATE:
{
SetMenu(hwnd, LoadMenu(g_hInstance, (PWCHAR)IDR_MENU1));
break;
}
case WM_COMMAND:
{
switch (LOWORD(wparam))
{
case ID_NEW:
{
MessageBox(hwnd, L"新建菜单项", NULL, MB_OK);
break;
}
case ID_EXIT:
{
MessageBox(hwnd, L"退出菜单项", NULL, MB_OK);
break;
}
default:
break;
}
break;
}
default:
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
图标资源
- 添加资源
- 加载
WINUSERAPI HICON WINAPI LoadIconA( _In_opt_ HINSTANCE hInstance, // handle to application instance _In_ LPCSTR lpIconName); // name string or resource identifier
- 注册窗口类
1. 添加资源
注意图标有大小之分,一个图标项目由多个图标。
项目右键->添加资源
新建:
可以自己画图
最好用导入
2. 加载
在创建窗口类时指定图标 wndclass.hIcon = LoadIcon(hInstance, (PCHAR)IDC_ICON);
3. 注册窗口类
光标资源
光标大小默认 32X32 像素,每个光标由HotSpot,是当前鼠标的热点。
热点默认左上角。
- 添加资源
新建 Cursor 类型资源,再设置热点。选中工具后双击图片上某个点。 - 加载资源
WINUSERAPI HCURSOR WINAPI LoadCursorW( _In_opt_ HINSTANCE hInstance, _In_ LPCWSTR lpCursorName);
- 设置
- 注册窗口时设置
wndclass.hCursor = LoadCursor(hInstance, (LPCWSTR)IDC_CURSOR1);
- 使用
SetCursor()
,必须在WM_SETCURSOR
消息的处理部分调用。
LRESULT CALLBACK WnProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { case WM_SETCURSOR: { if (LOWORD(lparam) == HTCLIENT) { SetCursor(LoadCursor(g_hInstance, (LPCWSTR)IDC_CURSOR2)); return 0; } else { // 非客户区 } break; } default: break; } return DefWindowProc(hwnd, msg, wparam, lparam); }
- 注册窗口时设置
只要光标移动就会产生WM_SETCURSOR
消息,这个消息专门用于改光标。这个消息的两个参数:
- wParam:当前使用的光标句柄。
- lParam:
- LOWORD:当前区域的代码(Hit-Test code) HTCLIENT(光标在客户区活动)/HTCAPTION(光标在标题栏区域活动)
- HIWORD:当前鼠标消息ID
如果在注册窗口时设置一种光标1,再使用SetCursor设置了另一种光标2。会发现再移动光标时鼠标变化为光标2,停下后再变为光标1。这是因为处理消息时运行完SetCursor后会执行 DefWindowProc
字符串资源
实现多语言切换
- 添加字符串资源 - 添加字符串表,在表中添加资源
- 字符串资源使用
WINUSERAPI int WINAPI LoadStringA( _In_opt_ HINSTANCE hInstance, _In_ UINT uID, // 字符串ID _Out_writes_to_(cchBufferMax,return + 1) LPSTR lpBuffer, // 存放字符串的BUFF _In_ int cchBufferMax // BUFF 长度 ); // 成功返回字符串长度,失败返回0
TCHAR szTitle[256] = { 0 };
LoadString(hInstance, IDS_WND, szTitle, 256);
HWND hwnd = CreateWindow(
wndclass.lpszClassName,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,//LoadMenu(hInstance, (PWCHAR)IDR_MENU1),
hInstance,
NULL
);
加速键资源
ctrl+C ctrl+V 等,每个加速键都在菜单项中有相应选项,二者原理上没有关系,只是一般习惯绑定使用。
- 添加:资源添加加速键表,增加命令ID对应的加速键
添加菜单和加速键
注意:其中菜单的新建项ID和快捷键中的某个键ID相同。 - 使用
// 加载加速键表,返回加速键表句柄 WINUSERAPI HACCEL WINAPI LoadAcceleratorsA( _In_opt_ HINSTANCE hInstance, // handle to module _In_ LPCSTR lpTableName); // accelerator table name // 翻译加速键,如果是加速键则返回非零 // 此函数放在 GetMessage 后 TranslateMessage 前,如果是加速键事件,无需调用 TranslateMessage WINUSERAPI int WINAPI TranslateAcceleratorA( _In_ HWND hWnd, // 处理消息的窗口句柄 _In_ HACCEL hAccTable, // 加速键表句柄 _In_ LPMSG lpMsg); // 消息 { if(lpMsg.message != WM_KEYDOWN) return 0; 根据 lpMsg.wParam(键码值)获知哪些按键被按下 根据按键到hAccTable中匹配查找 if (not found) return 0; if (found){ SendMessage(hwnd, WM_COMMAND, ID_NEW ...); // 发送一个 WM_COMMAND 消息,和菜单项使用同一个消息处理流程 return 1; } }
TranslateAccelerator()
函数发送一个消息,消息参数就是加速键的ID。wPARAM:HIWORD 为1表示加速键,为0表示菜单;LOWORD 为命令ID。iPARAM:为0。
加速键
#include <Windows.h>
#include <stdio.h>
#include <string.h>
#include "resource.h"
// 用于在 win32 中获取控制台
HANDLE g_hOutput = 0;
HINSTANCE g_hInstance = 0;
LRESULT CALLBACK WnProc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg)
{
case WM_COMMAND:
{
switch (LOWORD(wparam))
{
case ID_NEW:
if (HIWORD(wparam) == 0)
{
MessageBox(hwnd, L"新建项被点击", L"x", MB_OK);
}
else if (HIWORD(wparam) == 1)
{
MessageBox(hwnd, L"快捷键 ctrl + M 被点击", L"x", MB_OK);
}
break;
default:
break;
}
break;
}
default:
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int nCmdShow)
{
g_hInstance = hInstance;
// 在 win32 中获取控制台
AllocConsole();
g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
WNDCLASS wndclass;
ZeroMemory(&wndclass, sizeof(WNDCLASS));
wndclass.lpszMenuName = (LPCWSTR)IDR_MENU1;
wndclass.lpfnWndProc = WnProc;
wndclass.lpszClassName = L"MyWndClass";
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("regist windwos class failed!"), L"failed", MB_ICONERROR);
return 0;
}
HWND hwnd = CreateWindow(
wndclass.lpszClassName,
L"window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,//LoadMenu(hInstance, (PWCHAR)IDR_MENU1),
hInstance,
NULL
);
ShowWindow(hwnd, SW_NORMAL);
UpdateWindow(hwnd);
HACCEL hAcce = LoadAccelerators(hInstance, (LPCWSTR)IDR_ACCELERATOR1);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
if (TranslateAccelerator(hwnd, hAcce, &msg) == 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}