win32 - 定时器、菜单/图标/光标/字符串 资源

应用程序分类

  1. 控制台程序
  2. 窗口程序
  3. 静态库
    LIB文件,链接时将代码放入exe
  4. 动态库
    DLL文件,执行时动态获取代码

编译工具

  1. 编译器 CL.EXE 将源代码编译成目标代码 .obj
  2. 链接器 LINKl.EXE 将目标代码、库链接
  3. 资源编译器 RC.EXE 将rc资源文件编译,最终通过链接器存入最终文件

库:

C:\Windows\System32

  1. kernel32.dll  提供核心的API,例如进程、线程、内存管理
  2. user32.dll 提供了窗口、消息等API
  3. gdi32.dll 绘图相关的API

头文件:

  1. windows.h 所有windows头文件集合
  2. windef.h    windows数据类型
  3. winbase.h  kernel32的API
  4. wingdi.h    gdi32的API
  5. winuser     user32的API
  6. winnt.h     UNICODE字符集支持

编译流程:

  1. cl.exe -c xxx.cpp
  2. 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. 菜单分类

  1. 窗口顶层菜单
    窗口上部菜单
  2. 弹出式菜单
    鼠标右键菜单
    顶层菜单点击后出现的菜单
  3. 系统菜单

HMENU类型表示菜单
ID表示菜单项。

2. 资源相关

rc脚本描述资源文件,rc.exe编译器翻译rc脚本。cpp文件通过cl.exe编译为obj文件,rc脚本使用rc.exe编译为res文件,再使用链接器生成exe。

3. 菜单资源使用

  1. 添加菜单资源


    创建菜单资源后会生成一个图形化界面,顶部就是顶层菜单资源。点击框并输入名称,右键点击查看属性。

    自己设置一个ID,便于以后接收

    图中 IDR_MENU1 为资源ID,用于获取刚刚制作的菜单资源。
    rc文件内容:

    生成rc文件时配套生成了 resource.h 文件,可以通过这个头文件找到定义。
  2. 加载菜单资源
    三种方法,注意导入头文件。
    1. 注册窗口类时设置菜单
      wndClass.lpszMenuName = (char *)IDR_MENU1;
    2. 创建窗口传参设置菜单
      	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
      	);
    3. 在主窗口 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);
      }
  3. 相关函数
    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);
}

图标资源

  1. 添加资源
  2. 加载
    WINUSERAPI
    HICON
    WINAPI
    LoadIconA(
        _In_opt_ HINSTANCE hInstance, // handle to application instance
        _In_ LPCSTR lpIconName);      // name string or resource identifier
  3. 注册窗口类

1. 添加资源

注意图标有大小之分,一个图标项目由多个图标。

项目右键->添加资源

新建:
可以自己画图

最好用导入

2. 加载

在创建窗口类时指定图标 wndclass.hIcon = LoadIcon(hInstance, (PCHAR)IDC_ICON);

3. 注册窗口类

光标资源

光标大小默认 32X32 像素,每个光标由HotSpot,是当前鼠标的热点。

热点默认左上角。

  1. 添加资源
    新建 Cursor 类型资源,再设置热点。选中工具后双击图片上某个点。
  2. 加载资源
    WINUSERAPI
    HCURSOR
    WINAPI
    LoadCursorW(
        _In_opt_ HINSTANCE hInstance,
        _In_ LPCWSTR lpCursorName);
  3. 设置
    1. 注册窗口时设置
      wndclass.hCursor = LoadCursor(hInstance, (LPCWSTR)IDC_CURSOR1);
    2. 使用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

字符串资源

实现多语言切换

  1. 添加字符串资源 - 添加字符串表,在表中添加资源

  2. 字符串资源使用
    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 等,每个加速键都在菜单项中有相应选项,二者原理上没有关系,只是一般习惯绑定使用。

  1. 添加:资源添加加速键表,增加命令ID对应的加速键

    添加菜单加速键

    注意:其中菜单的新建项ID和快捷键中的某个键ID相同。
  2. 使用
    // 加载加速键表,返回加速键表句柄
    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;
}

      

 

posted @ 2022-07-30 14:58  某某人8265  阅读(177)  评论(0编辑  收藏  举报