菜单控件 (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"/>
事件处理¶
菜单控件支持以下事件:
-
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; }
-
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);
}
}
最佳实践¶
- 菜单结构
- 合理组织菜单层次,避免过深嵌套
- 使用分隔线分组相关功能项
-
保持菜单项文本简洁明了
-
视觉设计
- 统一的图标样式和尺寸
- 合适的菜单项高度和间距
-
一致的颜色和字体设置
-
交互体验
- 提供清晰的快捷键提示
- 及时更新菜单项状态(启用/禁用)
-
合理使用选中状态表示开关功能
-
性能优化
- 避免在菜单中放置过多项目
- 延迟加载复杂的子菜单
- 合理使用动态菜单构建
常见问题¶
- 显示问题
- 检查皮肤资源是否正确加载
- 验证菜单项属性设置
-
确认菜单尺寸设置是否合适
-
弹出位置
- 注意屏幕边界处理
- 考虑多显示器情况
-
确保菜单在可见区域内显示
-
子菜单问题
- 确认子菜单创建正确
- 检查内存管理
-
验证菜单项ID唯一性
-
事件处理
- 确保正确订阅事件
- 检查事件处理函数返回值
- 验证菜单项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. 如何创建和显示动态菜单