扩展菜单控件(SMenuEx)¶
基本信息¶
控件名称: 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);
}
事件处理¶
扩展菜单控件支持以下事件:
-
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; }
-
EventMenuHover: 当鼠标悬停在菜单项上时触发
bool OnMenuHover(EventMenuHover *pEvt) { SMenuExItem *pItem = sobj_cast<SMenuExItem>(pEvt->sender); if(pItem) { // 处理菜单项悬停 UpdateStatusText(pItem->GetText()); } return true; }
最佳实践¶
- 菜单结构
- 合理组织菜单层次
-
使用分隔线分组相关项
-
视觉设计
- 统一的图标样式
-
合适的间距和高度
-
交互体验
- 添加快捷键提示
-
禁用不可用的项
-
性能优化
- 避免过深的子菜单
- 合理使用动态菜单
常见问题¶
- 显示问题
- 检查皮肤资源加载
-
验证菜单项属性设置
-
弹出位置
- 注意屏幕边界处理
-
考虑多显示器情况
-
子菜单问题
- 确认子菜单创建正确
- 检查内存管理
代码示例解析¶
让我们看一个更复杂的菜单示例:
// 头文件包含
#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. 如何实现右键菜单