Skip to content

菜单控件 (SMenu)

Warning

The current page still doesn't have a translation for this language.

You can read it through google translate.

基本信息

  • 类名SMenu
  • 对应接口IMenuTObjRefImpl<IMenu>
  • XML 标签:无独立的布局标签。菜单通过 LoadMenu / LoadMenu2 从 XML 资源加载,本质是对 Win32 原生 HMENU 的封装,不是布局控件,没有 SOUI_ATTRS,也不参与 XML 布局树。
  • 功能描述:创建弹出式菜单,支持多级子菜单、图标、勾选/单选、禁用、快捷键提示等,是构建右键菜单与系统菜单的基础组件。

重要纠正:旧文档把 SMenu 当成普通布局控件并列出 itemWidth/textOffset/iconBarWidth/separatorHeight/minWidth/colorBkgnd 等“控件属性”,这些属性在源码中并不存在SMenu 自身没有任何 SOUI_ATTRS。菜单的样式由 SMenuAttrmenuattr)描述,菜单项由原生 HMENU 项构成,属性在 SMenu::BuildMenu 中解析。下文为对照源码(SOUI/include/helper/SMenu.hSOUI/src/helper/SMenu.cpp)后的正确属性表。

SMenu::LoadMenu2 在加载时会把 <menu> 根节点的属性交给 SMenuAttr 对象解析(pMenuAttr->InitFromXml(&xmlMenu)),因此下面这些属性写在 <menu> 根标签上。定义见 SOUI_ATTRS_BEGIN() of SMenuAttr

属性名 类型 默认值 说明
itemSkin skin 系统皮肤 SKIN_SYS_MENU_SKIN 菜单项背景皮肤(含正常/选中两态)。必填,若未设置 LoadMenu2 会报错并返回 FALSE
sepSkin skin 系统皮肤 SKIN_SYS_MENU_SEP 分隔线皮肤
checkSkin skin 系统皮肤 SKIN_SYS_MENU_CHECK 勾选 / 圈选标记皮肤
iconSkin skin 无(NULL 图标皮肤;也可在代码中调用 SMenu::SetIconSkin 设置
itemHeight layoutsize 25(dp) 菜单项高度。若未显式设置且无 itemSkinOnInitFinished 中回退为 30dp
iconMargin layoutsize 2(dp) 图标到菜单项边缘的内边距
textMargin layoutsize 5(dp) 文本左右内边距
maxWidth layoutsize 250(dp) 菜单项最大宽度;未设置时取 -1(自动)
iconSize layoutsize × 2 16 × 16(dp) 图标尺寸(宽、高)
font font 系统菜单字体 菜单文本字体
colorText color 系统 COLOR_MENUTEXT 正常文本颜色
colorTextSel color 系统 COLOR_HIGHLIGHTTEXT 选中项文本颜色
colorTextGray color 系统 COLOR_GRAYTEXT 禁用项(灰)文本颜色
trCtx string ""(空) 翻译上下文,配合 SOUI 翻译模块使用

纠正:源码中 没有 colorBkgnd / minWidth / textOffset / iconBarWidth / separatorHeight 这些属性;菜单背景完全由 itemSkin 决定。

菜单项属性 (<item>)

SMenu 的菜单项由原生 HMENU 项构成,属性在 SMenu::BuildMenuSOUI/src/helper/SMenu.cpp)中解析,不是 SOUI_ATTRS 宏声明的标准控件属性。支持的 XML 属性如下:

属性名 类型 默认值 说明
text string "" 菜单项文本;也可直接写在 <item> 标签体内
id int 0 菜单项命令 ID
icon int -1 图标在 iconSkin 中的索引
check bool false 是否带勾选标记(MF_CHECKED
radio bool false 是否单选圈选标记(MFT_RADIOCHECK + MF_CHECKED
disable bool false 是否禁用该项(MF_GRAYED
popup bool false 强制作为子菜单弹出(即使没有子节点)
userData uint 0 附加用户数据
contextHelpId uint 0 上下文帮助 ID

<sep> 标签表示分隔线,无属性。

纠正:旧文档中的 iconIndex / enable / hotkey 属性名无效。图标索引属性名为 icon(不是 iconIndex),禁用属性名为 disable(不是 enable);hotkey 通过 Win32 加速键设置,不会从 XML 解析。

使用示例

XML 资源示例

菜单通常定义在独立的 uires/xml 资源文件中,例如 menu_main.xml

<menu itemSkin="skin_menu_item" iconSkin="skin_menu_icon"
      itemHeight="26" iconMargin="4" textMargin="8">
    <item text="新建" icon="0">
        <item id="100" text="文件"/>
        <item id="101" text="文件夹"/>
    </item>
    <sep/>
    <item id="200" text="打开" icon="1"/>
    <item id="201" text="保存" icon="2"/>
    <sep/>
    <item id="300" text="自动保存" check="1"/>
    <item id="301" text="只读模式" radio="1"/>
    <item id="302" text="禁用项" disable="1"/>
    <item text="最近文件">
        <item id="400" text="文档1.txt"/>
        <item id="401" text="文档2.txt"/>
    </item>
</menu>

代码加载与弹出

#include <helper/SMenu.h>

// 成员变量
SMenu m_contextMenu;

void OnInit()
{
    // 从资源加载(resId 形如 "layout:menu_main")
    if (!m_contextMenu.LoadMenu(L"layout:menu_main"))
    {
        SASSERT_FMT(FALSE, "加载菜单失败");
        return;
    }
}

// 右键菜单:弹出并返回命令 ID
LRESULT OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    CPoint pt(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    if (pt.x == -1 && pt.y == -1)
    {
        CRect rc;
        GetWindowRect(&rc);
        pt.SetPoint(rc.left + 5, rc.top + 5);
    }

    int nCmd = m_contextMenu.TrackPopupMenu(
        TPM_RETURNCMD | TPM_RIGHTALIGN, pt.x, pt.y, m_hWnd);

    if (nCmd)
        OnCommand(nCmd);   // 处理菜单命令
    return 0;
}

代码动态操作

void MenuApiDemo()
{
    SMenu menu;
    menu.LoadMenu(L"layout:menu_demo");

    // 追加 / 插入菜单项(真实 API:AppendMenu / InsertMenu)
    menu.AppendMenu(MF_STRING, 1000, L"新菜单项", /*iIcon*/ -1);
    menu.InsertMenu(0, MF_STRING, 1001, L"插入的菜单项", /*iIcon*/ 0);

    // 启用 / 禁用、勾选
    menu.EnableMenuItem(1000, MF_BYCOMMAND | MF_GRAYED);
    menu.CheckMenuItem(300, MF_BYCOMMAND | MF_CHECKED);

    // 删除与销毁
    menu.DeleteMenu(1001, MF_BYCOMMAND);
}

事件处理

菜单项被选中后,TrackPopupMenu 返回对应的命令 ID(TPM_RETURNCMD 模式),由调用方统一处理;也可在宿主窗口中响应 EventCmd

bool OnMenuCommand(EventCmd* pEvt)
{
    int nID = pEvt->idFrom;
    switch (nID)
    {
    case 100: CreateNewFile(); break;
    case 200: OpenFile();      break;
    case 201: SaveFile();      break;
    }
    return true;
}

常见问题

  1. LoadMenu2 返回 FALSE:通常是 <menu> 根节点未设置 itemSkin
  2. 图标不显示:确认设置了 iconSkin,且 <item>icon 索引在皮肤范围内。
  3. 文本颜色不生效:普通文本用 colorText,选中项用 colorTextSel,禁用项用 colorTextGray,三者不要混用。
  4. 子菜单不弹出:除了包含子节点外,也可显式设置 popup="1" 强制子菜单。

相关主题