最重要的用户界面
从编程的角度来分类:
- 静态菜单:在菜单资源编译器中预先编辑好的
- 动态菜单:在程序运行过程中通过代码生成
- 快捷菜单:前两种菜单的组合,在菜单编译器中预先编辑好,然后在程序运行过程中动态显示
对于菜单而言,可以理解为一个二维数组,每一个二维数组的元素理解为一个可以嵌套的子菜单。
菜单里面的每一个菜单项都有两个最基本的要素:菜单项名字,该菜单项唯一的标识ID
VS中菜单在资源管理器的Menu的资源项下
选中项目可以通过添加资源来添加资源
如果自己新建了菜单,可以通过注册窗口的时候添加自己的菜单
//如果想在window里面去绘制一个窗口,必须先注册一个窗口类
ATOM MyRegisterClass(HINSTANCE hInstance)
{
//WNDCLASS的扩展,如果尾部出现EX,表示此种类型的扩展
WNDCLASSEXW wcex; //结构变量
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTWIN32));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
//改成自己设计的菜单
wcex.lpszMenuName = MAKEINTRESOURCEW(MY_MENU); //窗口的菜单是哪个资源
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
菜单的响应
在WinProc中响应菜单
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择: 在此处响应自己制作的菜单
switch (wmId)
{
//ID_FILE_OPEN是自己创建的菜单ID
case ID_FILE_OPEN:
MessageBox(hWnd,_"打开了自己的菜单",0,0);
break;
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
静态菜单
菜单静态编辑通过VS中资源编辑器可以自己编辑,然后通过相关代码来实现静态菜单的功能。
可以通过 &+F (F为对应的按键)来实现快捷键alt+F 键。
通过代码实现菜单的加载
//加载菜单
LoadMenu(
_In_opt_ HINSTANCE hInstance, //加载菜单的窗口句柄
_In_ LPCWSTR lpMenuName); //已经建好的静态菜单ID
//通过创建窗口的时候加载菜单
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // 将实例句柄存储在全局变量中
hWnd = FindWindow(szWindowClass,nullptr); //查找窗口函数
//如果窗口已经有了,就不再创建,返回false
if(hWnd)
{
return false;
}
//MY_MENU为自己在VS中已经编辑好的菜单资源ID
HMENU my_menu =LoadMenu(hInst,MAKEINTRESOURCEW(MY_MENU));
hWnd = CreateWindowW(szWindowClass,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,的
nullptr,
my_menu, //这里加入自己编辑好的菜单
hInstance,
nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow); //显示窗口
UpdateWindow(hWnd); //更新窗口
return TRUE;
}
动态菜单
通过代码来创建全新的菜单,并加载到窗口中
菜单相关函数
//创建菜单
CreateMenu(
VOID);
//尾部追加菜单
AppendMenu(
_In_ HMENU hMenu, //追加的菜单句柄
_In_ UINT uFlags, //标识,菜单的风格
_In_ UINT_PTR uIDNewItem, //添加新菜单项的ID号
_In_opt_ LPCWSTR lpNewItem);//新菜单项的名字(字符串)
//对应位置插入菜单,第二个参数将受到第三个参数的影响
InsertMenu(
_In_ HMENU hMenu, //追加的菜单句柄
_In_ UINT uPosition, //插入的位置
_In_ UINT uFlags, //标识,菜单的风格
_In_ UINT_PTR uIDNewItem, //添加新菜单项的ID号
_In_opt_ LPCWSTR lpNewItem); //新菜单项的名字(字符串)
//删除菜单,很少删除菜单项,因为只是单纯删除菜单项,菜单的ID号还存在
DeleteMenu(
_In_ HMENU hMenu, //删除的菜单句柄
_In_ UINT uPosition, //删除的菜单的位置
_In_ UINT uFlags); //基于菜单的风格删除
//激活菜单
EnableMenuItem(
_In_ HMENU hMenu, //激活的菜单的句柄
_In_ UINT uIDEnableItem, //菜单的ID
_In_ UINT uEnable); //
// MF_GRAYED 变灰 MF_ENABLED 激活
//设置更新窗口
SetMenu(
_In_ HWND hWnd, //菜单所属的窗口句柄
_In_opt_ HMENU hMenu); //对应的菜单句柄
//菜单风格 MF开头
MF_BYCOMMAND 基于菜单ID
MF_BYPOSITION 基于下标位置
MF_CHECKED 选中
MF_ENABLED 激活
MF_UNENABLED 取消激活
MF_HILITE
MF_POPUP 弹出式菜单
菜单实现,代码如下
菜单的新建不能放在UpdateWindow(hWnd); //更新窗口之后,会出现BUG
如果在UpdateWindow(hWnd); 之后,需要更新一下菜单SetMenu(hWnd,my_menu);
//自己新建菜单
HMENU my_menu = NULL; //菜单句柄
//创建菜单
HMENU my_menu_1 = CreateMenu(); //创建一个空菜单,即空白菜
my_menu = CreateMenu(); //再次创建一个空白菜单单
//空白菜单添加菜单项
//my_menu_1添加
AppendMenu(my_menu_1,0,5001,_T("新建(&N)"));
AppendMenu(my_menu_1,0,5002,_T("打开(&F)"));
AppendMenu(my_menu_1,0,5003,_T("保存(&S)"));
AppendMenu(my_menu_1,0,5004,_T("关闭"));
//把my_menu理解为二维数组,my_menu_1理解为其中的一个一维数组
//my_menu_1当成一个菜单嵌套进my_menu中
//注意:在弹出式菜单中,不需要id,把一个菜单句柄当作ID来使用,那么这个弹出式菜单弹出之后就是该菜单句柄所指的菜单
//尾部追加菜单,行追加
AppendMenu(my_menu, //菜单句柄
MF_POPUP, //菜单风格,弹出式菜单
(UINT)my_menu_1,//菜单ID,把my_menu_1做一个UINT强转,当成菜单ID
_T("文件(&F)")); //菜单的名字,&F 响应快捷键
HMENU my_menu_2 = CreateMenu(); //创建一个空菜单,即空白菜
//my_menu_2添加
AppendMenu(my_menu_2,MF_CHECKED,6001,_T("撤销(&Z)"));
AppendMenu(my_menu_2,0,6002,_T("剪切(&X)"));
AppendMenu(my_menu_2,0,6003,_T("复制(&C)"));
AppendMenu(my_menu_2,0,6004,_T("删除"));
//插入菜单,一般习惯基于下标来插入
InsertMenu(my_menu, //菜单句柄
0, //插入的位置
MF_BYPOSITION | MF_POPUP, //菜单风格,弹出式菜单
(UINT)my_menu_2, //菜单ID,把my_menu_1做一个UINT强转,当成菜单ID
_T("编辑(&E)")); //菜单的名字,&F 响应快捷键
//一般情况下不删除菜单,让菜单变灰即可MF_BYCOMMAND基于ID MF_GRAYED变灰
EnableMenuItem(my_menu_2,6001,MF_BYCOMMAND | MF_GRAYED); //菜单变灰,非激活
EnableMenuItem(my_menu_2,6001,MF_BYCOMMAND | MF_ENABLED); //菜单再次激活
//在hWnd这个句柄所在的窗口里面重新更新一下菜单
SetMenu(hWnd,my_menu);
hWnd = CreateWindowW(szWindowClass,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,的
nullptr,
my_menu, //这里加入自己编辑好的菜单
hInstance,
nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow); //显示窗口
UpdateWindow(hWnd); //更新窗口
return TRUE;
快捷菜单
首先在VS中新建一个菜单,或者自己通过代码实现一个菜单,菜单暂时不显示,通过鼠标右键点击弹出菜单。
动态菜单弹出函数
TrackPopupMenu(
_In_ HMENU hMenu, //要显示的菜单的句柄
_In_ UINT uFlags, //菜单风格,对齐的方式
_In_ int x, //鼠标点击的x坐标
_In_ int y, //鼠标点击的y坐标
_Reserved_ int nReserved, //默认给0
_In_ HWND hWnd, //显示菜单的窗口句柄
_Reserved_ CONST RECT *prcRect);//显示在哪个矩形里面
//参数uFlags
TPM_CENTERALIGN //居中
TPM_LEFTALIGN //左对齐
TPM_RUGHTALIGN //右对齐
TPM_TOPALIGN //右对齐
//得到子菜单
GetSubMenu(
_In_ HMENU hMenu, //得到的是哪个菜单的子菜单的菜单句柄
_In_ int nPos); //
//把窗口区的坐标转换成到屏幕的坐标
ClientToScreen(
_In_ HWND hWnd, //对应的窗口句柄
_Inout_ LPPOINT lpPoint);//Point结构体,用来存放x,y坐标
代码逻辑如下
//全局菜单句柄
HMENU g_popMenu;
//WndProc消息处理函数中
switch (message)
{
//创建窗口时候加载菜单
case WM_CREATE:
{
//MY_MENU为已经创建好的静态菜单,或者是自己写好的菜单
g_popMenu = LoadMenu(hInst,MAKEINTRESOURCEW(MY_MENU))
}
break;
//鼠标的左键按下,显示菜单
case WM_RBUTTONDOWN:
{
Point pt;
pt.x = LOWORD(lParam);
//y取高两位2字节
pt.y = HIWORD(lParam);
//把窗口区的坐标转换成到屏幕的坐标
ClientToScreen(hWnd,&pt);
//得到子菜单
HMENU popMenu1 = GetSubMenu(g_popMenu,0);
//显示弹出式菜单,要注意相对坐标,绝对坐标
TrackPopupMenu(popMenu1, //要显示的菜单的句柄
TPM_LEFTALIGN | TPM_TOPALIGN
pt.x, //鼠标点击的坐标,相对于屏幕的位置
pt.y,
0,
hWnd, //显示的窗口的句柄
nullptr);
}
break;
}