菜单的分类
窗口顶层菜单,弹出式菜单,系统菜单。句柄类型为HMENU,根据这三类还会细分,但是代码里是一样的。
窗口顶层菜单
HMENU hMain = CreateMenu();//创建菜单,成功则返回句柄
HMENU hFile = CreatePopupMenu();//弹出式菜单
AppendMenu(hFile,MF_STRING|MF_CHECKED,1003,"新建");//勾选
AppendMenu(hFile,MF_SEPARATOR,0,""); //分割线
AppendMenu(hFile,MF_STRING|MF_MENUBARBREAK,1004,"退出"); //换列
HMENU hHelp = CreatePopupMenu();
AppendMenu(hHelp,MF_STRING|MF_GRAYED,1005,"关于");//变灰
AppendMenu(hMain,MF_POPUP,(UINT)hFile,"文件");
AppendMenu(hMain,MF_POPUP,(UINT)hHelp,"帮助");
SetMenu(hwnd,hMain);//将菜单添加到窗口中
/**********************
BOOL AppendMenu(
HMENU hMenu, // handle to menu,菜单句柄
UINT uFlags, // menu-item options,菜单项风格
UINT_PTR uIDNewItem, // identifier, menu, or submenu,菜单ID
LPCTSTR lpNewItem // menu-item content,菜单项名称
);
菜单项常用风格:
弹出下拉菜单,MF_POPUP,就是点一下弹出一列菜单。
点击执行,MF_STRING,点击后执行具体操作。
分隔线,MF_SEPARATOR,注意算索引的时候它算一个。
勾选,MF_CHECKED,MF_UNCHECKED。
不可用,MF_DISABLED,MF_GRAYED,后一个还会表示为灰色,等于前面的加强版。
换列,MF_MENUBARBREAK,显示到另一列,和开始菜单一样。感觉和下拉菜单差不多,只是方向变了。
***********************/
添加消息:
case WM_COMMAND:
OnCommand(hwnd,wParam);
break;
//WM_COMMAND是个很复杂的消息,对于菜单来说,只需要处理wParam的低八位字节,即菜单项ID。
添加消息处理:
//CheckMenuItem更改勾选状态,EnableMenuItem更改是否可用。
BOOL bState = TRUE;
void OnCommand(HWND hwnd,WPARAM wParam)
{
switch(LOWORD(wParam))
{
case 1003:
{
if (bState)
{
CheckMenuItem(hFile,1003,MF_BYCOMMAND|MF_CHECKED);//MF_BYCOMMAND要跟ID
}
else
{
CheckMenuItem(hFile,0,MF_BYPOSITION|MF_UNCHECKED);//MF_BYPOSITION不用跟ID
}
bState = !bState;
}
break;
case 1004:
PostMessage(hwnd,WM_QUIT,0,0);
break;
case 1005:
MessageBox(NULL,"关于","Test",MB_OK);
break;
}
}
//MF_BYCOMMAND和MF_BYPOSITION在菜单里面很常见,一个是指定ID,一个是指定位置。自己定义的时候可能用ID多点,如果是系统菜单,不知道ID,则更多使用位置索引,从0开始。
窗口顶层菜单的初始化:
/**********************
WM_INITMENUPOPUP,菜单初始化,被激活但未显示的菜单。
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // WM_INITMENUPOPUP
WPARAM wParam, // handle to menu (HMENU),被激活菜单的句柄。
LPARAM lParam // item position and indicator,低八位:被激活菜单项的顶层菜单索引。高八位:窗口菜单标识。
);
窗口菜单:系统菜单和顶层菜单。弹出式菜单不属于。
***********************/
系统菜单
HMENU hSys = GetSystemMenu(hwnd,FALSE); //获取某窗口的系统菜单。
//第二个参数表示重置选项,TRUR表示删除旧菜单,恢复到默认的系统菜单;False表示返回当前的系统菜单句柄。
for (int i=0;i<8;i++)//有多少个删多少次
{
DeleteMenu(hSys,0,MF_BYPOSITION);//根据索引删除,参数也是MF_BYCOMMAND和MF_BYPOSITION.
}
通过AppendMenu添加菜单,通过WM_SYSCOMMAND响应,和窗口顶层菜单一样,wParam的低八位字节即菜单项ID。
弹出式菜单
用的比较多的比如右键菜单。通常鼠标右键弹起触发。
如果是鼠标右键弹起触发:WM_RBUTTONUP:
//鼠标传过来的光标位置是窗口坐标系的坐标。
/*************
BOOL ClientToScreen( //窗口坐标转换为屏幕坐标
HWND hWnd, // handle to window
LPPOINT lpPoint // screen coordinates,窗口点,输入和输出都是这个参数
);
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT;
//对应的还有个ScreenToClient
***************/
HMENU hPopup = CreatePopupMenu();//创建
AppendMenu(hPopup,MF_STRING,1003,"新建");
AppendMenu(hPopup,MF_SEPARATOR,0,"");
AppendMenu(hPopup,MF_STRING,1004,"退出");
POINT pt = {0};
pt.x = LOWORD(lParam);
pt.x = HIWORD(lParam);
ClientToScreen(hwnd,&pt);
TrackPopupMenu(hPopup,TPM_CENTERALIGN|TPM_VCENTERALIGN,pt.x,pt.x,0,hwnd,NULL);//显示
//阻塞函数,点击任何可用项返回
/********************
BOOL TrackPopupMenu(
HMENU hMenu, // handle to shortcut menu,菜单句柄
UINT uFlags, // options,显示方式
int x, // horizontal position,x坐标,屏幕坐标系
int y, // vertical position,y坐标
int nReserved, // reserved, must be zero,保留,必须为0
HWND hWnd, // handle to owner window,处理菜单消息的窗口句柄
CONST RECT *prcRect // ignored,忽略
);
显示方式:
TPM_CENTERALIGN 鼠标光标处于右键菜单居中
TPM_LEFTALIGN 左边,最常用
TPM_RIGHTALIGN 右边
TPM_BOTTOMALIGN 底部
TPM_TOPALIGN 顶部
TPM_VCENTERALIGN 垂直方向居中
TPM_NONOTIFY
TPM_RETURNCMD 返回的是用户选择的菜单项的ID,用于消息处理
TPM_LEFTBUTTON 用鼠标左键选择菜单项
TPM_RIGHTBUTTON 用左键右键都可以
***********************/
消息处理和前面的菜单处理一样,都是通过ID判断,但是如果加入了TPM_RETURNCMD显示方式,则直接返回ID,在创建的函数里面即可做处理。等于消息直接自己送了,不进消息队列了。
除了鼠标右键弹起,对于右键菜单还有个专门的消息:WM_CONTEXTMENU,WPARAM返回右键点击的窗口句柄,LPARAM返回屏幕坐标,不需要做转换。
WM_CONTEXTMENU在WM_RBUTTONUP之后产生。