Skip to content

扩展菜单控件(SMenuEx)

Warning

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

You can read it through google translate.

基本信息

控件名称: menuex
对应类名: SMenuEx
功能描述: SOUI中的扩展菜单控件,提供了更丰富的自定义功能和样式支持,可以创建现代化的菜单界面。支持图标、快捷键、子菜单等特性。

控件属性

属性名 类型 默认值 说明
iconSkin string "" 菜单项图标的皮肤名称
itemHeight int 26 菜单项的高度
iconMargin int 4 图标与文本的间距
textMargin int 8 文本的左右边距
separatorHeight int 3 分隔线的高度
maxWidth int 0 菜单最大宽度(0为自动)

使用示例

XML示例

<!-- 基础菜单定义 -->
<menuex itemHeight="26" iconSkin="menu_icons" 
        textMargin="8" iconMargin="4">
    <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>
</menuex>

<!-- 自定义样式菜单 -->
<menuex itemHeight="32" 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>
</menuex>

代码示例

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

// 动态添加菜单项
SMenuExItem *pItem = new SMenuExItem();
pItem->SetText(L"新菜单项");
pItem->SetID(1000);
menu.InsertMenu(0, TRUE, pItem);

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

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

事件处理

扩展菜单控件支持以下事件:

  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. EventMenuHover: 当鼠标悬停在菜单项上时触发

    bool OnMenuHover(EventMenuHover *pEvt)
    {
        SMenuExItem *pItem = sobj_cast<SMenuExItem>(pEvt->sender);
        if(pItem)
        {
            // 处理菜单项悬停
            UpdateStatusText(pItem->GetText());
        }
        return true;
    }
    

最佳实践

  1. 菜单结构
  2. 合理组织菜单层次
  3. 使用分隔线分组相关项

  4. 视觉设计

  5. 统一的图标样式
  6. 合适的间距和高度

  7. 交互体验

  8. 添加快捷键提示
  9. 禁用不可用的项

  10. 性能优化

  11. 避免过深的子菜单
  12. 合理使用动态菜单

常见问题

  1. 显示问题
  2. 检查皮肤资源加载
  3. 验证菜单项属性设置

  4. 弹出位置

  5. 注意屏幕边界处理
  6. 考虑多显示器情况

  7. 子菜单问题

  8. 确认子菜单创建正确
  9. 检查内存管理

代码示例解析

让我们看一个更复杂的菜单示例:

// 头文件包含
#include <core/SWindow.h>
#include <helper/SMenuEx.h>

class CMainDlg : public SHostWnd
{
protected:
    // 菜单相关变量
    SMenuEx m_mainMenu;

    void OnInit()
    {
        // 加载菜单布局
        if(!m_mainMenu.LoadMenu("layout:menu_main"))
        {
            SASSERT_FMT(FALSE, "加载菜单失败");
            return;
        }

        // 初始化菜单状态
        InitMenuState();
    }

    // 初始化菜单状态
    void InitMenuState()
    {
        // 获取最近文件菜单项
        SMenuExItem *pRecentMenu = 
            m_mainMenu.GetMenuItemByName(L"menu_recent");
        if(pRecentMenu)
        {
            // 清空现有子菜单
            pRecentMenu->DeleteAllItems();

            // 添加最近文件列表
            std::vector<SStringT> recentFiles = GetRecentFiles();
            for(size_t i = 0; i < recentFiles.size(); i++)
            {
                SMenuExItem *pItem = new SMenuExItem();
                pItem->SetText(recentFiles[i]);
                pItem->SetID(300 + i);
                pRecentMenu->InsertMenu(-1, TRUE, pItem);
            }
        }

        // 设置菜单项状态
        UpdateMenuState();
    }

    // 更新菜单状态
    void UpdateMenuState()
    {
        bool bHasFile = HasOpenedFile();

        // 更新保存菜单项状态
        SMenuExItem *pSave = 
            m_mainMenu.GetMenuItemByID(201);
        if(pSave)
        {
            pSave->EnableMenuItem(bHasFile);
        }

        // 更新其他依赖状态的菜单项
        UpdateDependentMenuItems();
    }

    // 处理菜单命令
    void OnCommand(UINT uID)
    {
        switch(uID)
        {
        case 100: // 新建文件
            OnNewFile();
            break;
        case 200: // 打开文件
            OnOpenFile();
            break;
        case 201: // 保存文件
            OnSaveFile();
            break;
        default:
            // 处理最近文件菜单项
            if(uID >= 300 && uID < 400)
            {
                OnOpenRecentFile(uID - 300);
            }
            break;
        }
    }

    // 显示右键菜单
    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;
        }

        // 创建上下文菜单
        SMenuEx contextMenu;
        contextMenu.LoadMenu("layout:menu_context");

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

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

        if(nCmd)
        {
            OnCommand(nCmd);
        }

        return 0;
    }
};

这个示例展示了: 1. 如何加载和初始化菜单 2. 如何动态更新菜单状态 3. 如何处理菜单命令 4. 如何实现右键菜单