Chapter 155 · XUI 弹出层与反馈

Menu 菜单

Menu 弹出式菜单控件,支持菜单项、分隔线、子菜单、快捷键文本、勾选/禁用状态。

155

概念

Menu 基于 Popup 实现弹出菜单:
• MenuItem:文本 + 图标 + 快捷键 + 子菜单 + 状态(勾选/禁用)
• Separator:分隔线
• OpenAt:在指定坐标弹出(右键菜单)
• Select 回调:点击菜单项触发

API 速查

函数说明
int xuiMenuCreate(xui_context pCtx, xui_widget* ppW, const xui_menu_desc_t* pDesc)创建菜单
int xuiMenuSetItems(xui_widget pW, const xui_menu_item_t* pItems, int iCount)批量设置项
int xuiMenuAddItem(xui_widget pW, const xui_menu_item_t* pItem)添加项
int xuiMenuAddSeparator(xui_widget pW)添加分隔线
int xuiMenuOpenAt(xui_widget pW, xui_widget pOwner, float fX, float fY)在坐标弹出
int xuiMenuClose(xui_widget pW)关闭
int xuiMenuSetSelect(xui_widget pW, xui_menu_select_proc onSelect, void* pUser)选中回调
int xuiMenuSetItemState(xui_widget pW, int iIndex, uint32_t iMask, uint32_t iState)项状态
int xuiMenuMeasure(xui_widget pW, float* pW, float* pH)测量尺寸
xui_widget xuiMenuGetPopupWidget(xui_widget pW)获取内部 Popup

编译本章示例

gcc -O2 -I. -o ch155.exe ch155_main.c src\xui_*.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch155.exe

示例:右键菜单

xui_menu_desc_t desc = { 0 };

xui_widget pMenu;
xuiMenuCreate(pCtx, &pMenu, &desc);

// 构建菜单项
xui_menu_item_t items[] = {
    { "复制",  "Ctrl+C", 100, 0 },
    { "粘贴",  "Ctrl+V", 101, 0 },
    { "",      "",        0,   1 }, // 分隔线
    { "删除",  "Del",     102, 0 },
};
xuiMenuSetItems(pMenu, items, 4);
xuiMenuSetSelect(pMenu, OnMenuSelect, NULL);

// 右键时弹出
xuiMenuOpenAt(pMenu, pOwner, fMouseX, fMouseY);

完整代码

将以下代码保存为 ch155_main.c,编译后运行即可看到右键菜单效果:

#include "xge.h"
#include "xui.h"
#include <stdio.h>

static xui_context g_pCtx;
static xui_widget g_pMenu;

static void onMenuSel(xui_widget pW, int iVal, void* p) {
    (void)pW; (void)p;
    printf("菜单项 value=%d\n", iVal);
}

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x081114FF);
    float mx, my;
    xgeMouseGet(&mx, &my);
    xuiInputPointerMove(g_pCtx, mx, my);
    if (xgeMouseDown(XGE_MOUSE_RIGHT))
        xuiMenuOpenAt(g_pMenu, NULL, mx, my);
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        xuiInputPointerDown(g_pCtx, 0);
    else
        xuiInputPointerUp(g_pCtx, 0);
    xuiUpdate(g_pCtx, xgeGetDelta());
    xuiLayout(g_pCtx);
    xuiRender(g_pCtx, NULL, NULL, 0);
    return XGE_OK;
}

int main(void) {
    xge_desc_t d = { 0 };
    d.iWidth = 800; d.iHeight = 600;
    d.sTitle = "ch155 - Menu";
    d.iFlags = XGE_INIT_VSYNC;
    xgeInit(&d);

    xuiCreate(&g_pCtx);
    extern const xui_proxy_t tXgeProxy;
    xui_proxy_t tXgeProxy = xuiProxyXge();
xuiSetProxy(g_pCtx, &tXgeProxy);
    xuiSetViewportSize(g_pCtx, 800, 600);

    xui_widget pRoot;
    xuiWidgetCreate(g_pCtx, &pRoot);
xuiWidgetSetLayoutType(pRoot, XUI_LAYOUT_COLUMN);
    xuiWidgetSetRect(pRoot, (xui_rect_t){0, 0, 800, 600});
    xuiSetRootWidget(g_pCtx, pRoot);

    /* 创建右键菜单 */
    xui_menu_desc_t md = { 0 };
    xuiMenuCreate(g_pCtx, &g_pMenu, &md);
    xuiMenuSetSelect(g_pMenu, onMenuSel, NULL);

    xui_menu_item_t items[] = {
        { "复制", "Ctrl+C", 100, 0 },
        { "粘贴", "Ctrl+V", 101, 0 },
        { "",     "",        0,   1 },
        { "删除", "Del",     102, 0 },
    };
    xuiMenuSetItems(g_pMenu, items, 4);

    xgeRun(onFrame, NULL);
    xuiDestroy(g_pCtx);
    xgeUnit();
    return 0;
}

代码讲解

  • SetItems:批量设置菜单项,包含文本、快捷键、值、是否分隔线。
  • OpenAt:在鼠标位置弹出菜单,右键触发。
  • Select 回调:点击菜单项时触发,通过 value 识别操作。
  • 分隔线:文本为空 + separator=1 表示分隔线。

要点与陷阱

  • 子菜单:MenuItem 中设置 pSubMenu 指向另一个 Menu 控件。
  • State:位掩码控制 Checked / Disabled / Separator 状态。
  • Measure:打开前可测量菜单尺寸用于自定义定位。
  • OpenForOwner:自动定位到 Owner 控件下方。

进阶练习

  1. 多级子菜单:文件 → 新建 → 项目/文件/文件夹。
  2. 动态菜单:根据选中对象类型构建不同菜单项。
Menu 菜单效果
图:Menu 菜单渲染效果