Skip to content

菜单控件 (SMenu)

Warning

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

You can read it through google translate.

基本信息

控件名称: menu
对应类名: SMenu
功能描述: SOUI中的基础菜单控件,用于创建弹出式菜单。支持多级子菜单、图标、快捷键等功能,是构建应用程序上下文菜单和系统菜单的基础组件。

控件属性

属性名 类型 默认值 说明
iconSkin string "" 菜单项图标的皮肤名称
itemHeight int 24 菜单项的高度
textOffset int 20 文本偏移量
iconBarWidth int 20 图标栏宽度
separatorHeight int 3 分隔线高度
maxWidth int 0 菜单最大宽度(0为自动)
minWidth int 0 菜单最小宽度(0为自动)

使用示例

XML示例

<!-- 基础菜单定义 -->
<menu itemHeight="26" iconSkin="menu_icons">
    <item text="新建">
        <item id="100" text="文件"/>
        <item id="101" text="文件夹"/>
    </item>
    <sep/>
    <item text="打开" id="200"/>
    <item text="保存" id="201"/>
    <sep/>
    <item text="最近文件">
        <item id="300" text="文档1.txt"/>
        <item id="301" text="文档2.txt"/>
    </item>
</menu>

<!-- 自定义样式菜单 -->
<menu itemHeight="30" iconSkin="menu_custom" 
      colorBkgnd="#FFFFFF" colorText="#333333">
    <item text="编辑" iconIndex="0">
        <item id="400" text="复制" iconIndex="1"/>
        <item id="401" text="粘贴" iconIndex="2"/>
        <item id="402" text="剪切" iconIndex="3"/>
    </item>
</menu>

代码示例

// 创建菜单
SMenu menu;
menu.LoadMenu("layout:menu_main");

// 动态添加菜单项
pugi::xml_node newItem = menu.InsertItem(-1, L"新菜单项", 1000);

// 弹出菜单
CPoint pt;
GetCursorPos(&pt);
int nCmd = menu.TrackPopupMenu(TPM_RETURNCMD, pt.x, pt.y);

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

菜单项属性

基本属性

属性名 类型 说明
text string 菜单项显示文本
id int 菜单项ID
iconIndex int 图标索引
hotkey string 快捷键
check bool 是否选中
enable bool 是否启用

使用示例

<!-- 带快捷键的菜单项 -->
<item text="新建" hotkey="Ctrl+N" id="100"/>

<!-- 带图标的菜单项 -->
<item text="打开" iconIndex="1" id="101"/>

<!-- 禁用的菜单项 -->
<item text="保存" enable="0" id="102"/>

<!-- 选中的菜单项 -->
<item text="自动保存" check="1" id="103"/>

事件处理

菜单控件支持以下事件:

  1. 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;
    }
    

  2. EventMenuCmd: 菜单命令事件

    bool OnMenuCmd(EventMenuCmd *pEvt)
    {
        SWindow *pSender = sobj_cast<SWindow>(pEvt->sender);
        if(pSender)
        {
            // 处理菜单命令
            int nCmdID = pEvt->nCmdID;
            HandleMenuCommand(nCmdID);
        }
        return true;
    }
    

菜单操作

代码操作示例

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

    // 添加菜单项
    pugi::xml_node newItem = menu.InsertItem(0, L"新菜单", 1000);

    // 添加子菜单
    pugi::xml_node subMenu = menu.InsertSubMenu(newItem, L"子菜单", 1001);

    // 添加分隔线
    menu.InsertSeparator(subMenu);

    // 设置菜单项状态
    menu.EnableItem(0, FALSE);

    // 设置菜单项选中
    menu.CheckItem(1, TRUE);

    // 删除菜单项
    menu.DeleteItem(2);

    // 获取菜单项数量
    int nCount = menu.GetItemCount();
}

动态构建菜单

void BuildDynamicMenu()
{
    SMenu menu;

    // 构建文件菜单
    pugi::xml_node fileMenu = menu.InsertItem(-1, L"文件", 0);
    menu.InsertSubMenu(fileMenu, L"新建", 100, L"Ctrl+N");
    menu.InsertSubMenu(fileMenu, L"打开", 101, L"Ctrl+O");
    menu.InsertSeparator(fileMenu);
    menu.InsertSubMenu(fileMenu, L"退出", 102);

    // 构建编辑菜单
    pugi::xml_node editMenu = menu.InsertItem(-1, L"编辑", 1);
    menu.InsertSubMenu(editMenu, L"撤销", 200, L"Ctrl+Z");
    menu.InsertSubMenu(editMenu, L"重做", 201, L"Ctrl+Y");

    // 构建最近文件菜单
    pugi::xml_node recentMenu = menu.InsertSubMenu(fileMenu, L"最近文件", 300);
    std::vector<SStringT> recentFiles = GetRecentFiles();
    for(size_t i = 0; i < recentFiles.size(); i++)
    {
        menu.InsertSubMenu(recentMenu, recentFiles[i], 301 + i);
    }
}

最佳实践

  1. 菜单结构
  2. 合理组织菜单层次,避免过深嵌套
  3. 使用分隔线分组相关功能项
  4. 保持菜单项文本简洁明了

  5. 视觉设计

  6. 统一的图标样式和尺寸
  7. 合适的菜单项高度和间距
  8. 一致的颜色和字体设置

  9. 交互体验

  10. 提供清晰的快捷键提示
  11. 及时更新菜单项状态(启用/禁用)
  12. 合理使用选中状态表示开关功能

  13. 性能优化

  14. 避免在菜单中放置过多项目
  15. 延迟加载复杂的子菜单
  16. 合理使用动态菜单构建

常见问题

  1. 显示问题
  2. 检查皮肤资源是否正确加载
  3. 验证菜单项属性设置
  4. 确认菜单尺寸设置是否合适

  5. 弹出位置

  6. 注意屏幕边界处理
  7. 考虑多显示器情况
  8. 确保菜单在可见区域内显示

  9. 子菜单问题

  10. 确认子菜单创建正确
  11. 检查内存管理
  12. 验证菜单项ID唯一性

  13. 事件处理

  14. 确保正确订阅事件
  15. 检查事件处理函数返回值
  16. 验证菜单项ID映射关系

代码示例解析

让我们看一个完整的菜单使用示例:

#include <helper/SMenu.h>

class CMainDlg : public SHostWnd
{
protected:
    // 上下文菜单
    SMenu m_contextMenu;

    void OnInit()
    {
        // 加载上下文菜单
        if(!m_contextMenu.LoadMenu("layout:menu_context"))
        {
            SASSERT_FMT(FALSE, "加载上下文菜单失败");
            return;
        }
    }

    // 显示上下文菜单
    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.x = rc.left + 5;
            pt.y = rc.top + 5;
        }

        // 根据当前状态调整菜单项
        PrepareContextMenu();

        // 显示菜单
        int nCmd = m_contextMenu.TrackPopupMenu(
            TPM_RETURNCMD | TPM_RIGHTALIGN,
            pt.x, pt.y, m_hWnd);

        if(nCmd)
        {
            OnMenuCommand(nCmd);
        }

        return 0;
    }

    // 准备上下文菜单
    void PrepareContextMenu()
    {
        bool bHasSelection = HasTextSelection();
        bool bHasFile = HasOpenedFile();

        // 更新复制菜单项状态
        m_contextMenu.EnableItem(100, bHasSelection);

        // 更新保存菜单项状态
        m_contextMenu.EnableItem(200, bHasFile);
    }

    // 处理菜单命令
    void OnMenuCommand(UINT uID)
    {
        switch(uID)
        {
        case 100: // 复制
            OnCopy();
            break;
        case 101: // 粘贴
            OnPaste();
            break;
        case 200: // 保存
            OnSaveFile();
            break;
        case 300: // 退出
            OnExit();
            break;
        default:
            break;
        }
    }

    // 创建动态菜单
    void ShowDynamicMenu(CPoint pt)
    {
        SMenu dynamicMenu;

        // 添加动态项目
        std::vector<SStringT> actions = GetAvailableActions();
        for(size_t i = 0; i < actions.size(); i++)
        {
            dynamicMenu.InsertItem(-1, actions[i], 1000 + i);
        }

        // 显示菜单
        int nCmd = dynamicMenu.TrackPopupMenu(
            TPM_RETURNCMD, pt.x, pt.y, m_hWnd);

        if(nCmd >= 1000)
        {
            ExecuteAction(nCmd - 1000);
        }
    }
};

这个示例展示了: 1. 如何加载和使用上下文菜单 2. 如何根据应用程序状态动态调整菜单项 3. 如何处理菜单命令 4. 如何创建和显示动态菜单