WindowsAPI窗口程序设计-菜单与资源
菜单-菜单资源法
开始
在资源文件区新建.rc资源文件,在Menu字样下新建一个菜单IDR_MENU,然后就可以可视的方式设计菜单。双击每个菜单项,在属性窗口中设置ID,例如ID_FILE_NEW、ID_FILE_OPEN等。
菜单添加
法一:设置WNDCLASSEX结构的lpszMenuName字段,需要把IDR_MENU转为LPCTSTR类型。
1
| wcex.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
|
法二:创建窗口时指定hMenu参数:
1 2 3
| HMENU hMenu; hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU)); HWND hWnd = CreateWindowEx(0, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 180, NULL, hMenu, hInstance, NULL);
|
其中LoadMenu
用来加载菜单资源:
1 2 3 4
| HMENU WINAPI LoadMenu( _In_opt_ HINSTANCE hInstance, _In_ LPCTSTR lpMenuName )
|
法三:用SetMenu
为指定窗口设置菜单:
1 2 3 4 5
| case WM_CREATE: { hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MENU)); SetMenu(hwnd, hMenu); return 0; };
|
其中SetMenu
用法:
1 2 3 4
| BOOL WINAPI SetMenu( _In_ HWND hWnd, _In_opt_ HMENU hMenu )
|
加速键
在.rc文件的Accelerator项中。在键栏填大写字母时,类型填VIRTKEY表示字母键;填“^大写字母”时,类型填ASCII,表示Ctrl键加字母键;填大小写字母或ASCII值时,类型填ASCII,表示字母键。
在消息循环调用中TranslateAccelerator
函数,它监视消息队列中WM_KEYDOWN或WM_KEYUP,并转化为WM_COMMAND或WM_SYSCOMMAND。
先用LoadAccelerators
加载加速键表:
1 2 3 4
| HACCEL WINAPI LoadAccelerators( _In_opt_ HINSTANCE hInstance, _In_ LPCTSTR lpTableName )
|
然后用TranslateAccelerator
函数:
1 2 3 4 5
| INT WINAPI TranslateAccelerator( _In_ HWND hWnd, _In_ HACCEL hAccTable, _In_ LPMSG lpMsg )
|
一般消息处理改为这样写:
1 2 3 4 5 6
| HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1)); while (GetMessage(&msg, NULL, 0, 0)) if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); };
|
对于WM_COMMAND消息,一般参数:
消息来源 |
HIWORD(wParam) |
LOWORD(wParam) |
lParam |
菜单命令项 |
0 |
菜单项ID |
0 |
加速键 |
1 |
菜单项ID |
0 |
子窗口控件 |
通知码 |
控件ID |
控件句柄 |
对于WM_SYSCOMMAND消息,一般直接给DefWindowProc
默认处理,一些常用系统命令:
常量 |
值 |
含义 |
SC_MINIMIZE |
0xF020 |
最小化窗口 |
SC_MAXIMIZE |
0xF030 |
最大化窗口 |
SC_RESTORE |
0xF120 |
恢复窗口位置大小 |
SC_CLOSE |
0xF060 |
关闭窗口 |
SC_SIZE |
0xF000 |
调整窗口大小 |
SC_MOVE |
0xF010 |
移动窗口 |
SC_HSCROLL |
0xF080 |
水平滚动 |
SC_VSCROLL |
0xF070 |
垂直滚动 |
SC_HOTKEY |
0xF150 |
全局热键消息 |
当用鼠标选择系统菜单命令项时,分别用GET_X_LPARAM(lParam)
和GET_Y_LPARAM(lParam)
获取鼠标光标X和Y坐标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| #include <Windows.h> #include <tchar.h> #include "resource.h"
HINSTANCE g_hInstance;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow) { WNDCLASSEX wndclass; TCHAR szClassName[] = TEXT("MyWindow"); TCHAR szAppName[] = TEXT("HelloWindows"); HWND hwnd; MSG msg; g_hInstance = hInstance; wndclass.cbSize = sizeof(WNDCLASSEX); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WindowProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szClassName; wndclass.hIconSm = NULL; RegisterClassEx(&wndclass); hwnd = CreateWindowEx(0, szClassName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACC)); while (GetMessage(&msg, NULL, 0, 0) != 0) if (!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); }; return msg.wParam; }; LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HMENU hMenu; TCHAR szBuf[64] = { 0 }; switch (uMsg) { case WM_CREATE: { hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MENU)); SetMenu(hwnd, hMenu); return 0; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case ID_FILE_NEW: { wsprintf(szBuf, TEXT("您点击了 新建 菜单项,命令ID:%d\n"), ID_FILE_NEW); MessageBox(hwnd, szBuf, TEXT("提示"), MB_OK); break; }; case ID_EDIT_CUT: { wsprintf(szBuf, TEXT("您点击了 剪切 菜单项,命令ID:%d\n"), ID_EDIT_CUT); MessageBox(hwnd, szBuf, TEXT("提示"), MB_OK); break; }; case ID_HELP_ABOUT: { wsprintf(szBuf, TEXT("您点击了 关于HelloWindows 菜单项,命令ID:%d\n"), ID_HELP_ABOUT); MessageBox(hwnd, szBuf, TEXT("提示"), MB_OK); break; }; case ID_FILE_EXIT: { wsprintf(szBuf, TEXT("您点击了 退出 菜单项,命令ID:%d\n"), ID_FILE_EXIT); MessageBox(hwnd, szBuf, TEXT("提示"), MB_OK); SendMessage(hwnd, WM_CLOSE, 0, 0); break; }; }; return 0; }; case WM_SYSCOMMAND: { switch (wParam & 0xFFF0) { case SC_CLOSE: { MessageBox(hwnd, TEXT("您点击了 系统菜单 关闭 菜单项"), TEXT("提示"), MB_OK); SendMessage(hwnd, WM_CLOSE, 0, 0); break; }; default: { return DefWindowProc(hwnd, uMsg, wParam, lParam); }; }; return 0; }; case WM_DESTROY: { PostQuitMessage(0); return 0; }; }; return DefWindowProc(hwnd, uMsg, wParam, lParam); };
|
菜单-动态创建法
太麻烦了,略。
菜单-菜单项加载法
用CreateMenu
创建一个空菜单:
用AppendMenu
向菜单句柄添加菜单项:
1 2 3 4 5 6
| BOOL WINAPI AppendMenu( _In_ HMENU hMenu, _In_ UINT uFlags, _In_ UINT_PTR uIDNewItem, _In_opt_ LPCTSTR lpNewItem )
|
uFlags的常用取值有:
枚举值 |
含义 |
MF_POPUP |
弹出菜单 |
MF_STRING |
文本字符串菜单项 |
MF_CHECKED |
复选标志 |
MF_UNCHECKED |
没有复选标志 |
MF_DISABLED |
禁用 |
MF_ENABLED |
启用 |
MF_GRAYED |
灰化 |
MF_SEPARATOR |
分隔符 |
最后用SetMenu
显示到菜单栏。
实例,添加“文件”和“编辑”两个主菜单项:
1 2 3 4 5 6 7 8 9 10 11 12
| hMenu = CreateMenu(); hMenuPopup = CreateMenu(); AppendMenu(hMenuPopup, MF_STRING, ID_FILE_NEW, TEXT("新建")); AppendMenu(hMenuPopup, MF_STRING, ID_FILE_OPEN, TEXT("打开")); AppendMenu(hMenuPopup, MF_STRING, ID_FILE_SAVE, TEXT("保存")); AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hMenuPopup, TEXT("文件")); hMenuPopup = CreateMenu(); AppendMenu(hMenuPopup, MF_STRING, ID_EDIT_CUT, TEXT("剪切")); AppendMenu(hMenuPopup, MF_STRING, ID_EDIT_COPY, TEXT("复制")); AppendMenu(hMenuPopup, MF_STRING, ID_EDIT_PASTE, TEXT("黏贴")); AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hMenuPopup, TEXT("编辑")); SetMenu(hwnd, hMenu);
|
在已有菜单基础上添加“视图”主菜单项,用GetMenu
获取窗口菜单句柄,用DrawMenuBar
强制刷新菜单栏:
1 2 3 4 5 6
| hMenu = GetMenu(hwnd); hMenuPopup = CreateMenu(); AppendMenu(hMenuPopup, MF_STRING, ID_VIEW_BIG, TEXT("大图标"));
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hMenuPopup, TEXT("视图")); DrawMenuBar(hwnd);
|
用GetSubMenu
获取弹出菜单或子菜单项句柄:
1 2 3 4
| HMENU WINAPI GetSubMenu( _In_ HMENU hMenu, _In_ INT nPos )
|
向“编辑”菜单中添加子菜单项:
1 2 3 4 5 6 7 8 9
| hMenu = GetMenu(hwnd); hMenuPopup = GetSubMenu(hMenu, 1); AppendMenu(hMenuPopup, MF_SEPARATOR, NULL, NULL); AppendMenu(hMenuPopup, MF_STRING, ID_EDIT_RED, TEXT("红色(&R)"));
hMenuPopupSub = CreateMenu(); AppendMenu(hMenuPopupSub, MF_STRING, ID_EDIT_UPPER, TEXT("大写字母(&U)"));
AppendMenu(hMenuPopup, MF_STRING | MF_POPUP, (UINT_PTR)hMenuPopupSub, TEXT("更改大小写(&N)"));
|
用InsertMenu
添加菜单项并指定插入位置:
1 2 3 4 5 6 7
| BOOL WINAPI InsertMenu( _In_ HMENU hMenu, _In_ UINT uPosition, _In_ UINT uFlags, _In_ UINT_PTR uIDNewItem, _In_opt_ LPCTSTR lpNewItem )
|
其中当uFlags为MF_YCOMMAND时,uPosition指定菜单项ID;当为MF_BYPOSITION时,uPosition指定新菜单项从0开始的相对位置,-1则插入菜单末尾。
将“视图”调整为第3个:
1 2 3 4 5 6
| hMenu = GetMenu(hwnd); hMenuPopup = CreateMenu(); AppendMenu(hMenuPopup, MF_STRING, ID_VIEW_BIG, TEXT("大图标"));
InsertMenu(hMenu, 2, MF_STRING | MF_POPUP | MF_BYPOSITION, (UINT_PTR)hMenuPopup, TEXT("视图")); DrawMenuBar(hwnd);
|
系统默认菜单
就是点左上角图标蹦出来的那个。
获取系统菜单句柄:
1 2 3 4
| HMENU WINAPI GetSystemMenu( _In_ HWND hWnd, _In_ BOOL bRevert )
|
其中当bRevert为TRUE时,表示获取系统菜单句柄;为FALSE时,表示系统菜单恢复为默认状态,返回NULL。
系统菜单项ID都为0xF000以上,向系统菜单添加的菜单项ID都必须小于0xF000。
修改菜单项:
1 2 3 4 5 6 7
| BOOL WINAPI ModifyMenu( _In_ HMENU hMenu, _In_ UINT uPosition, _In_ UINT uFlags, _In_ UINT_PTR uIDNewItem, _In_opt_ LPCTSTR lpNewItem )
|
例如灰化系统菜单的关闭菜单项:
1
| ModifyMenu(GetSystemMenu(hwnd, FALSE), SC_CLOSE, MF_GRAYED, SC_CLOSE, TEXT("关闭(&C)\tAlt+F4"));
|
删除菜单项:
1
| RemoveMenu(GetSystemMenu(hwnd, FALSE), SC_CLOSE, MF_BYCOMMAND);
|
系统菜单后添加一个“打开”菜单项:
1
| AppendMenu(GetSystemMenu(hwnd, FALSE), MF_STRING, ID_FILE_NEW, TEXT("打开(&O)"));
|
恢复系统菜单:
1
| GetSystemMenu(hwnd, TRUE);
|
用EnableMenuItem
启用、禁用或灰化一个菜单项,比ModifyMenu
简单:
1 2 3 4 5
| BOOL WINAPI EnableMenuItem( _In_ HMENU hMenu, _In_ UINT uIDEnableItem, _In_ UINT uEnable )
|
用CheckMenuItem
设置或取消复选标志:
1 2 3 4 5
| DWORD WINAPI CheckMenuItem( _In_ HMENU hMenu, _In_ UINT uIDCheckItem, _In_ UINT uCheck )
|
几个菜单项之间模仿一组单选按钮:
1 2 3 4 5 6 7
| BOOL WINAPI CheckMenuRadioItem( _In_ HMENU hMenu, _In_ UINT idFirst, _In_ UINT idLast, _In_ UINT idCheck, _In_ UINT uFlags )
|
当uFlags为MF_BYCOMMAND时,组中ID在数值上是连续的;当uFlags为MF_BYPOSITION时,在菜单栏中位置是连续的。
右键弹出菜单
用CreatePopupMenu
创建一个空的弹出菜单:
1
| HMENU WINAPI CreatePopupMenu(VOID);
|
用GetMenuItemCount
获取指定菜单句柄下菜单项个数:
1 2 3
| INT GetMenuItemCount( _In_opt_ HMENU hMenu )
|
例如:
1 2
| hMenu = GetMenu(hwnd); n = GetMenuItemCount(GetSubMenu(hMenu, 1));
|
用GetMenuString
获取一个菜单项文本字符串:
1 2 3 4 5 6 7
| INT WINAPI GetMenuString( _In_ HMENU hMenu, _In_ UINT uIDItem, _Out_opt_ LPTSTR lpString, _In_ INT nMaxCount, _In_ UINT uFlag )
|
右键弹出菜单:
1 2 3 4 5 6 7 8 9
| BOOL WINAPI TrackPopupMenu( _In_ HMENU hMenu, _In_ UINT uFlags, _In_ INT x, _In_ INT y, _In_ INT nReserved, _In_ HWND hWnd, _In_opt_ CONST PRECT prcRect )
|
参数uFlags有些常用标志:
枚举值 |
含义 |
TPM_LEFTALIGN |
快捷菜单左侧与X参数指定坐标对齐 |
TPM_TOPALIGN |
快捷菜单顶部与Y参数指定坐标对齐 |
例如弹出“编辑”菜单项:
1 2 3 4 5 6 7
| case WM_RBUTTONDOWN: { pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); ClientToScreen(hwnd, &pt); TrackPopupMenu(GetSubMenu(GetMenu(hwnd), 1), TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hwnd, NULL); return 0; };
|
没法直接用TrackPopupMenu
显示整个主菜单,有别的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| HMENU hMenu, hMenuPopup; POINT pt; TCHAR szBuf[32] = { 0 }; case WM_RBUTTONDOWN: { hmenu = GetMenu(hwnd); hMenuPopup = CreatePopupMenu(); for (SIZE_T i = 0; i < GetMenuItemCount(hMenu); i++) { GetMenuString(hMenu, i, szBuf, _countof(szBuf), MF_BYPOSITION); AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)GetSubMenu(hMenu, i), szBuf); }; pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); ClientToScreen(hwnd, &pt); TrackPopupMenu(hMenuPopup, TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, py.y, 0, hwnd, NULL); return 0; };
|
扩展菜单项&菜单项自绘
挖坑不打算填。
图标
在资源文件.rc中导入图标.ico文件,然后这里设置:
1
| wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
|
动态更换窗口标题栏左侧图标和任务栏图标:
1
| SetClassLongPtr(hwnd, GCLP_HICON, (LONG)LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_EAGLE)));
|
动态更换可执行文件图标:
1
| SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_EAGLE)));
|
光标
在资源文件.rc中导入静态光标.cur文件,然后这里设置:
1
| wcex.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_POINT));
|
动态光标类型只能从外部文件加载,当然静态光标也可以这么加载:
1
| wcex.hCursor = LoadCursorFromFile(TEXT("ninjanormal.ani"));
|
修改指定系统光标:
1 2 3 4
| BOOL WINAPI SetSystemCursor( _In_ HCURSOR hcur, _In_ DWORD id )
|
系统光标ID以OCR_开头,但有一一对应的IDC_进行MAKEINTRESOURCE
封装,使用OCR_资源前,需要在Windows.h包含前:
例如这样两种方法,但重启计算机后又恢复:
1 2
| SetSytemCursor(CopyCursor(LoadCursor(g_hInstance, MAKEINTRESOURCE(IDC_POINT))), OCR_NORMAL); SetSytemCursor(CopyCursor(LoadCursorFromFile(TEXT("ninjanormal.ani"))), OCR_NORMAL);
|
动态更换光标同样两种方法:
1 2
| SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG)LoadCursor(g_hInstance, MAKEINTRESOURCE(IDC_POINT))); SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG)LoadCursorFromFile(TEXT("ninjanormal.ani")));
|
自定义资源
获取模块中指定类型和名称的资源:
1 2 3 4 5
| HRSRC WINAPI FindResource( _In_opt_ HMODULE hModule, _In_ LPCTSTR lpName, _In_ LPCTSTR lpType )
|
失败时用GetLastError
获取错误信息:
1
| DWORD GetLastError(VOID);
|
通过资源信息块句柄得到资源句柄:
1 2 3 4
| HGLOBAL WINAPI LoadResource( _In_opt_ HMODULE hModule, _In_ HRSRC hResInfo )
|
获取内存中资源数据指针:
1 2 3
| LPVOID WINAPI LockResource( _In_ HANDLE hResData )
|
获取资源大小:
1
| SizeofResource(g_hInstance, hResBlock);
|
例如在.rc文件中新建WAVE文件类型的文件夹,其中有IDR_WAVE1,则歌曲播放方法:
1 2 3 4 5
| #pragma comment(lib, "Winmm.lib") HRSRC hResBlock = FindResource(g_hInstance, MAKEINTRESOURCE(IDR_WAVE), TEXT("WAVE")); HANDLE hRes = LoadResource(g_hInstance, hResBlock); LPVOID lpMusic = LockResource(hRes); PlaySound((LPCTSTR)lpMusic, NULL, SND_MEMORY | SND_ASYNC | SND_LOOP);
|
资源不需要手动释放,不需要时系统自动释放。