emWin菜单控件开发指南:从基础API到高级应用实战

发布时间:2026/6/20 15:56:03

emWin菜单控件开发指南:从基础API到高级应用实战 1. 菜单控件在嵌入式GUI中的核心地位在嵌入式系统开发里图形用户界面GUI是连接用户与设备功能的关键桥梁。而菜单作为这个桥梁上最经典、最直观的导航结构其重要性不言而喻。无论是工业触摸屏上的参数设置还是医疗设备中的功能选择亦或是智能家居控制面板的操作入口一个设计良好、响应迅速的菜单系统直接决定了产品的易用性和专业感。我接触过不少项目早期为了图省事用一堆按钮堆叠出所谓的“菜单”结果代码臃肿、逻辑混乱后期维护和功能扩展简直是噩梦。后来系统性地使用了emWin这类成熟GUI库的菜单控件才真正体会到什么叫“专业工具干专业活”。emWin的MENU控件本质上是一个高度封装、功能完备的窗口对象Widget。它不仅仅是在屏幕上画几个带文字的框其背后是一套完整的创建、管理、交互和销毁的生命周期机制。理解这套机制你就能从“会调用API”进阶到“能设计出稳健的菜单系统”。很多新手觉得菜单API调用简单但一旦涉及到动态菜单项、多级子菜单、状态同步、皮肤定制等实际需求就会遇到各种坑。这篇文章我就结合自己踩过的那些坑把emWin MENU控件的API掰开揉碎了讲清楚从创建一个最简单的菜单到实现复杂的交互逻辑让你不仅能看懂手册更能用得顺手。2. 菜单控件的整体设计与核心思路在深入代码之前我们必须先建立对emWin MENU控件的整体认知。它不是一个孤立的绘图函数而是一个继承了窗口管理器WM所有特性的“活”的对象。2.1 基于窗口对象模型的架构emWin的所有控件包括MENU都是窗口Window的一种特殊类型。这意味着MENU控件天然拥有窗口的所有属性一个唯一的句柄WM_HWIN/MENU_Handle、一个父窗口、一个位置和大小区域、以及接收和处理消息的能力。这种设计带来了巨大的灵活性。例如你可以通过窗口管理器APIWM_MoveWindow,WM_ResizeWindow来移动或调整菜单大小也可以通过WM_SetCallback来为菜单窗口单独设置回调函数处理一些自定义的绘制或消息。菜单的视觉呈现和交互逻辑被完美地封装在控件内部。作为开发者你只需要关注两件事数据菜单项的结构、文本、ID和事件用户点了哪个项。控件自己负责根据数据渲染出正确的视觉效果包括选中高亮、禁用灰度、子菜单箭头等并在用户操作时通过消息机制向你报告发生了什么。2.2 关键数据结构MENU_ITEM_DATA菜单的核心是菜单项。MENU_ITEM_DATA这个结构体就是你定义每一个菜单项的蓝图。我们来看看它的每个成员理解它们是如何协作的typedef struct { const char * pText; // 菜单项显示的文本 U16 Id; // 菜单项的唯一标识符 U16 Flags; // 菜单项的状态标志如禁用、分隔符 MENU_Handle hSubmenu; // 关联的子菜单句柄 } MENU_ITEM_DATA;pText: 指向菜单项文本字符串的指针。这里有个重要细节emWin内部并不复制这个字符串它只是保存了这个指针。这意味着你必须确保这个指针在整个菜单生命周期内都是有效的。通常的做法是使用静态字符串如“File”或者将字符串存放在全局或静态数组中。绝对不要使用局部变量地址否则一旦函数退出内存失效菜单显示将是乱码或导致程序崩溃。Id: 菜单项的唯一ID。这个ID是你后续在代码中识别“用户点了哪个项”的唯一依据。手册里特别强调在整个菜单树包括所有子菜单中每个菜单项的ID应该是唯一的。虽然在某些简单情况下不同子菜单使用相同ID可能不会立即出错但这会为消息处理埋下巨大的隐患强烈建议遵守此规则。Flags: 控制菜单项行为的位标志。目前主要支持两个MENU_IF_DISABLED: 将该菜单项置为灰色不可点击状态。用户可以看到它但无法选中。MENU_IF_SEPARATOR: 将该菜单项显示为一条分隔线。通常用于对菜单项进行视觉上的分组。设置为分隔线时pText和Id通常被忽略。hSubmenu: 这是实现多级菜单的关键。如果这个菜单项需要弹出子菜单你需要先创建另一个MENU控件作为子菜单然后将它的句柄赋值给这个成员。如果该项没有子菜单则将此成员设为0。2.3 消息传递机制WM_MENU菜单与应用程序的通信完全通过窗口消息Message进行。当用户与菜单交互时如选中一项菜单控件会向其“所有者窗口”Owner Window发送一条WM_MENU消息。这条消息的Data.p指针指向一个MENU_MSG_DATA结构体里面包含了事件类型MsgType和触发事件的菜单项IDItemId。typedef struct { U16 MsgType; // 消息类型如 MENU_ON_ITEMSELECT U16 ItemId; // 相关联的菜单项ID } MENU_MSG_DATA;主要的消息类型有MENU_ON_ITEMSELECT: 用户最终选择并释放了一个可用的菜单项。这是你最常处理的消息用于执行该菜单项对应的功能。MENU_ON_INITMENU: 在菜单即将显示之前发送。这是一个绝佳的“动态菜单”钩子。你可以在这里根据当前程序状态动态地启用、禁用或修改菜单项。例如在文本编辑器里当没有文本被选中时可以在菜单弹出前禁用“复制”和“剪切”项。MENU_ON_ITEMACTIVATE: 当用户高亮通过键盘或触摸移动到某个菜单项时发送。可用于实现更丰富的交互反馈比如在状态栏显示该菜单项的详细说明。MENU_ON_ITEMPRESSED: 当用户按下但尚未释放一个菜单项时发送。注意即使是禁用的菜单项也会触发此消息。关键技巧默认情况下WM_MENU消息会发送给菜单控件的父窗口。但你可以通过MENU_SetOwner()函数指定另一个窗口作为消息接收者。这在设计复杂的窗口关系时非常有用可以将所有菜单消息集中到一个专门的管理器窗口进行处理。3. 菜单创建与基础配置详解理解了整体架构我们就可以动手创建第一个菜单了。创建菜单不仅仅是调用一个函数它涉及到位置、大小、方向、父子关系等一系列决策。3.1 创建菜单MENU_CreateEx() 的深度解析MENU_CreateEx()是创建菜单最核心、最灵活的函数。我们逐一分析其参数MENU_Handle MENU_CreateEx(int x0, int y0, int xSize, int ySize, WM_HWIN hParent, int WinFlags, int ExFlags, int Id);x0, y0: 菜单控件在其父窗口坐标系中的左上角位置。对于主菜单栏y0通常为0x0则根据布局计算。对于弹出式子菜单这个坐标通常是触发它的父菜单项旁边。xSize, ySize: 这是最容易让人困惑的参数之一。它们控制菜单的尺寸行为。设为0菜单采用“自动尺寸”模式。菜单控件会根据你添加的菜单项内容文本长度、字体大小自动计算并调整自身大小。当你动态添加或删除菜单项时菜单窗口的大小会自动变化。这是最常用的方式特别适用于内容不确定的动态菜单。设为大于0的值菜单采用“固定尺寸”模式。菜单窗口的大小将被固定不会随内容改变。如果内容超出固定区域通常会被裁剪。这种模式常用于需要精确对齐的场景比如作为应用程序顶部的固定菜单栏MENU_CF_HORIZONTAL你需要将其xSize设置为父窗口的宽度以确保它横贯整个屏幕顶部。hParent: 父窗口句柄。菜单将作为这个窗口的子窗口被创建和管理。如果传入WM_HBKWIN桌面窗口句柄则菜单成为一个顶级窗口。一个特殊的参数值是WM_UNATTACHED它允许你创建一个“游离”的菜单稍后使用MENU_Attach()函数将其附加到某个窗口上。这在需要延迟绑定或复用菜单时很有用。WinFlags: 窗口创建标志。最常用的是WM_CF_SHOW让菜单创建后立即显示。其他标志如WM_CF_MEMDEV可用于内存设备支持以优化绘制性能、减少闪烁。ExFlags: 菜单特有的创建标志。目前主要是MENU_CF_HORIZONTAL水平菜单如菜单栏和MENU_CF_VERTICAL垂直菜单如下拉菜单或弹出菜单。这个选择决定了菜单项的排列方向。Id: 该菜单窗口的ID。在通过WM_GetDialogItem()等函数查找子窗口时使用。实操心得在嵌入式设备上我强烈建议在创建菜单前先使用GUI_ALLOC_GetNumUsedBytes()等函数监控一下内存使用情况。因为每个菜单窗口及其内部数据结构都会消耗内存。特别是在使用“自动尺寸”模式且动态增删菜单项时虽然方便但窗口大小变化会触发重绘和可能的内存重分配在性能敏感的场合需要留意。3.2 添加与组织菜单项创建了一个空的菜单窗口后下一步就是用MENU_AddItem()或MENU_InsertItem()为其填充内容。MENU_AddItem()是最简单的它总是将新项添加到菜单的末尾。而MENU_InsertItem()则可以在指定的现有菜单项通过ItemId指定之前插入新项这为你提供了动态调整菜单顺序的能力。构建一个典型“文件”下拉菜单的代码示例如下// 假设 hMainMenu 是主垂直菜单的句柄 MENU_ITEM_DATA ItemData; // 添加“新建”项 ItemData.pText 新建\tCtrlN; ItemData.Id ID_MENU_FILE_NEW; // 自定义的ID宏例如 0x1000 ItemData.Flags 0; ItemData.hSubmenu 0; MENU_AddItem(hMainMenu, ItemData); // 添加“打开”项 ItemData.pText 打开...\tCtrlO; ItemData.Id ID_MENU_FILE_OPEN; MENU_AddItem(hMainMenu, ItemData); // 添加一条分隔线 ItemData.pText NULL; // 分隔线文本通常被忽略 ItemData.Id 0; // 分隔线的ID通常也无意义但需唯一 ItemData.Flags MENU_IF_SEPARATOR; ItemData.hSubmenu 0; MENU_AddItem(hMainMenu, ItemData); // 添加“退出”项 ItemData.pText 退出; ItemData.Id ID_MENU_FILE_EXIT; ItemData.Flags 0; MENU_AddItem(hMainMenu, ItemData);注意事项文本中的制表符\temWin的菜单控件支持用制表符分隔主文本和快捷键文本如“CtrlN”。控件会自动将快捷键部分右对齐显示这是一个提升专业度的细节。ID的管理为所有菜单项定义有意义的ID宏如ID_MENU_FILE_NEW并集中在一个头文件中管理。这比直接使用数字更安全、更易维护。子菜单的创建如果要创建带子菜单的项你需要先创建子菜单本身。通常子菜单也是一个垂直菜单MENU_CF_VERTICAL。创建好后将其句柄赋值给父菜单项MENU_ITEM_DATA的hSubmenu成员。子菜单的父窗口hParent通常也设为父菜单的窗口这样消息和焦点才能正确传递。3.3 视觉样式定制默认的菜单样式可能不符合你的产品UI设计。emWin提供了丰富的API来定制菜单的外观。颜色设置通过MENU_SetBkColor()和MENU_SetTextColor()你可以为菜单项的不同状态设置颜色。颜色索引ColorIndex定义了状态MENU_CI_ENABLED: 普通启用状态。MENU_CI_SELECTED: 被选中高亮状态。MENU_CI_DISABLED: 禁用状态。MENU_CI_DISABLED_SEL: 禁用且被选中状态某些操作下可能触发。MENU_CI_ACTIVE_SUBMENU: 当前激活的子菜单项指示器颜色。例如设置高亮色为蓝色MENU_SetTextColor(hMenu, MENU_CI_SELECTED, GUI_BLUE);字体设置使用MENU_SetFont()可以改变整个菜单的字体。emWin支持多种内置字体如GUI_FONT_16B_1和用户自定义字体。重要提示更改字体后如果菜单是“自动尺寸”模式其大小会自动重新计算。但如果菜单是“固定尺寸”模式且新字体比原字体大可能会导致文本显示不全或被裁剪。边框调整MENU_SetBorderSize()允许你调整菜单项文本与项边界之间的空白内边距。你可以分别设置左、右、上、下四个方向的边框大小通过MENU_BI_LEFT等索引。适当增加边框可以让菜单看起来不那么拥挤提升可读性。默认值设置所有MENU_SetDefault...开头的函数如MENU_SetDefaultFont()用于设置后续新创建的菜单控件的默认属性。这在你需要统一整个应用程序的菜单风格时非常高效。它不会影响已经创建的菜单。4. 菜单的显示、交互与动态管理创建和配置好菜单只是第一步让它正确地显示出来并与用户交互才是核心。4.1 附着与弹出两种显示模式菜单有两种主要的显示方式对应两种不同的使用场景附着式菜单Attached Menu使用MENU_Attach()函数。这种菜单会作为目标窗口的一个持久子窗口存在通常用于主菜单栏或常驻侧边栏。它被创建后就固定在窗口的某个位置如顶部并一直显示。用户与应用程序的其他部分交互时它仍然可见。// 创建一个水平菜单栏并附着到主窗口的顶部 hMenuBar MENU_CreateEx(0, 0, 0, 25, hMainWindow, WM_CF_SHOW, MENU_CF_HORIZONTAL, ID_MENUBAR); // 这里添加“文件”、“编辑”等顶级菜单项... // 注意顶级菜单项通常关联着垂直的下拉子菜单hSubmenu // 将菜单栏附着到主窗口并设置其宽度与主窗口一致 MENU_Attach(hMenuBar, hMainWindow, 0, 0, MAIN_WINDOW_WIDTH, 25, 0);弹出式菜单Popup Menu使用MENU_Popup()函数。这是最常见的上下文菜单或下拉菜单的显示方式。菜单在指定的屏幕位置临时弹出当用户选择了一项或点击菜单外部区域时菜单会自动关闭。关键点MENU_Popup()函数是非阻塞的。它显示菜单后立即返回不会等待用户选择。用户的选择通过之前提到的WM_MENU消息异步通知。// 假设 hContextMenu 是一个预先创建好的垂直菜单 // 在鼠标右键点击位置 (x, y) 弹出菜单 MENU_Popup(hContextMenu, hDestWindow, x, y, 0, 0, 0); // 函数立即返回后续在 hDestWindow 的 WM_MENU 消息处理中响应用户选择重要提醒MENU_Popup()显示的菜单不会被自动删除。你需要自己管理它的生命周期。通常的做法是在窗口初始化时创建好弹出菜单并隐藏在需要时弹出在整个窗口生命周期内复用。窗口关闭时再统一销毁。4.2 消息处理与业务逻辑绑定菜单的“灵魂”在于对用户操作的响应。这一切都在窗口回调函数中对WM_MENU消息的处理中完成。下面是一个典型的主窗口消息回调函数中处理菜单消息的片段static void _cbMainWindow(WM_MESSAGE * pMsg) { switch (pMsg-MsgId) { case WM_MENU: { MENU_MSG_DATA * pMenuMsg (MENU_MSG_DATA *)pMsg-Data.p; switch (pMenuMsg-MsgType) { case MENU_ON_ITEMSELECT: // 用户最终选择了一个菜单项 switch (pMenuMsg-ItemId) { case ID_MENU_FILE_NEW: _OnFileNew(); // 执行“新建”功能 break; case ID_MENU_FILE_OPEN: _OnFileOpen(); break; case ID_MENU_FILE_EXIT: _OnFileExit(); break; // ... 处理其他菜单项ID } break; case MENU_ON_INITMENU: // 菜单即将显示进行动态更新 _UpdateMenuState(pMsg-hWinSrc); // pMsg-hWinSrc 是发送消息的菜单句柄 break; // 可以根据需要处理 MENU_ON_ITEMACTIVATE 等消息 default: break; } break; } // ... 处理其他消息 } }动态菜单更新技巧MENU_ON_INITMENU消息是实现动态菜单的关键。例如在文本编辑器的“编辑”菜单弹出前你需要检查当前是否有文本被选中从而决定“复制”和“剪切”项是否可用。static void _UpdateMenuState(MENU_Handle hMenu) { // 假设我们知道“复制”和“剪切”项的ID if (g_IsTextSelected) { MENU_EnableItem(hMenu, ID_MENU_EDIT_COPY); MENU_EnableItem(hMenu, ID_MENU_EDIT_CUT); } else { MENU_DisableItem(hMenu, ID_MENU_EDIT_COPY); MENU_DisableItem(hMenu, ID_MENU_EDIT_CUT); } // 还可以根据其他状态更新更多菜单项... }4.3 菜单项的动态操作除了在初始化时添加菜单项emWin允许你在运行时动态地修改菜单。MENU_DeleteItem(ItemId): 根据ID删除一个菜单项。如果菜单是自动尺寸模式删除后菜单大小会自动收缩。MENU_SetItem(): 修改一个已有菜单项的所有属性文本、ID、标志、子菜单。你可以用它来彻底改变一个菜单项。MENU_GetItem()/MENU_GetItemText(): 获取菜单项的属性。这在实现一些高级功能如保存/恢复菜单状态时有用。MENU_SetSel(): 以编程方式设置当前选中的菜单项通过索引。这在配合键盘导航时特别有用。一个常见的坑动态增删菜单项时特别是涉及子菜单一定要处理好菜单项ID的唯一性。如果删除一个带有子菜单的项最好也递归地销毁其子菜单使用WM_DeleteWindow()避免内存泄漏。5. 高级应用与性能优化实战掌握了基础API后我们来看看如何构建更复杂、更高效的菜单系统。5.1 构建多级嵌套菜单系统复杂的应用程序往往需要多级菜单。设计的关键在于清晰的层次结构和正确的句柄管理。自底向上创建先创建最底层的子菜单然后逐级向上。例如创建“编辑-查找-高级查找”这样的三级菜单应该先创建“高级查找”菜单然后将其句柄赋给“查找”菜单中的“高级”项最后将“查找”菜单句柄赋给主“编辑”菜单项。句柄管理为每一级菜单维护好句柄变量。通常使用一个结构体或数组来组织它们而不是散落一堆全局变量。消息传递子菜单的WM_MENU消息默认会向上传递给父菜单的所有者窗口。这意味着你通常只需要在最顶层窗口的一个地方处理所有菜单消息。通过ItemId就能区分来自哪一级菜单的哪个项。5.2 资源管理与内存优化在资源受限的嵌入式系统中菜单的内存使用需要精心规划。避免频繁创建销毁弹出式菜单应在程序初始化时创建好并隐藏起来WM_HideWindow()需要时显示MENU_Popup()用完后隐藏而不是每次都MENU_CreateEx()和WM_DeleteWindow()。创建和销毁窗口是比较耗时的操作。使用间接创建MENU_CreateIndirect()函数允许你通过一个资源表GUI_WIDGET_CREATE_INFO来创建菜单。这可以将菜单的结构定义位置、大小、样式与逻辑代码分离更适合大型项目或需要动态加载不同UI配置的场景。监控内存碎片如果你需要非常频繁地动态增删大量菜单项这在嵌入式系统中不常见可能会引起内存碎片。在这种情况下可以考虑预先分配一个足够大的菜单项池通过MENU_SetItem()来更新内容而不是增删。5.3 自定义绘制与皮肤emWin的菜单控件本身提供了不错的默认外观但对于品牌化要求高的产品可能需要完全自定义的皮肤。虽然MENU控件没有像BUTTON那样直接的“皮肤设置”API但你可以通过以下方式实现深度定制重写菜单项绘制这是最彻底的方式。你可以为菜单窗口设置一个自定义的回调函数WM_SetCallback()在WM_PAINT消息中完全接管每一个菜单项的绘制过程。你可以自己画背景、边框、文本、图标、选中状态等。这需要较强的emWin绘图功底。组合使用基础API利用MENU_SetBkColor,MENU_SetTextColor,MENU_SetFont,MENU_SetBorderSize等API已经可以组合出很多种风格。通过精心选择颜色和字体也能达到不错的视觉效果。使用透明背景和叠加将菜单背景设为透明需要支持然后在菜单后面放置一个自定义的背景图片窗口可以实现更复杂的视觉效果。5.4 与触摸屏和键盘的深度集成触摸屏优化emWin的菜单控件原生支持触摸。确保你的菜单项有足够的触摸区域通过MENU_SetBorderSize增加内边距。对于嵌套很深的菜单要测试触摸滑动操作是否流畅防止误触。键盘导航除了触摸完善的菜单系统必须支持键盘导航。你需要在你窗口的WM_KEY消息处理中捕获方向键、回车键、ESC键等并调用MENU_SetSel()来高亮不同的菜单项模拟触摸操作。例如按下右箭头键打开子菜单按下回车键确认选择。6. 常见问题排查与调试技巧即使理解了所有API实际开发中还是会遇到各种问题。这里记录一些我踩过的坑和解决方法。6.1 菜单不显示或显示异常问题现象可能原因排查步骤与解决方案菜单创建后完全看不见1. 未使用WM_CF_SHOW标志。2. 父窗口不可见或被遮挡。3. 菜单位置在屏幕外。1. 检查MENU_CreateEx的WinFlags参数是否包含WM_CF_SHOW。2. 确保父窗口已创建并显示。3. 检查x0,y0坐标是否在父窗口客户区内。菜单项为空白或乱码MENU_ITEM_DATA中的pText指针失效。绝对确保pText指向的字符串存在于全局/静态存储区而非函数内的局部变量。使用static const char字符串。菜单大小异常只显示一部分1. 使用了固定尺寸 (xSize/ySize0)但尺寸太小。2. 自动尺寸模式下字体设置过大。1. 检查固定尺寸值是否足以容纳最长的菜单项文本。2. 检查MENU_SetFont设置的字体大小或改用自动尺寸 (xSize/ySize0)。子菜单不弹出1. 父菜单项的hSubmenu句柄设置错误或为0。2. 子菜单创建失败句柄为0。3. 子菜单的父窗口设置不当。1. 打印或调试确认hSubmenu的值是否正确。2. 检查子菜单MENU_CreateEx的返回值是否非0。3. 子菜单的父窗口通常应设为父菜单的窗口句柄。6.2 消息无法接收或处理错误问题现象可能原因排查步骤与解决方案点击菜单项无反应1. 所有者窗口未正确设置或未处理WM_MENU消息。2. 菜单项被禁用 (MENU_IF_DISABLED)。3. 消息被其他回调函数截获未传递。1. 确认菜单的所有者窗口默认是父窗口的回调函数中实现了WM_MENU的case。2. 检查菜单项的Flags确保未设置MENU_IF_DISABLED。3. 在所有者窗口回调中对不处理的消息调用WM_DefaultProc。MENU_ON_INITMENU消息不触发此消息仅在菜单即将显示前发送。对于附着式常显菜单只在首次显示时发送一次。确保你的逻辑是针对弹出菜单或动态更新的场景。对于需要持续更新的附着菜单应在状态改变时主动调用MENU_EnableItem/MENU_DisableItem。收到的ItemId不是预期的值菜单项ID在整个菜单树中不唯一。严格遵守手册规定确保所有菜单项包括所有子菜单中的项的ID都是全局唯一的。重新规划ID分配方案。6.3 性能与内存问题问题现象可能原因排查步骤与解决方案弹出菜单有明显延迟1. 在MENU_ON_INITMENU消息中执行了耗时操作如文件读取。2. 菜单结构过于复杂项数太多。1. 将耗时的初始化工作提前到程序启动时进行MENU_ON_INITMENU中只做轻量的状态切换。2. 优化菜单结构减少单级菜单的项数必要时使用多级分组。动态增删菜单项后程序崩溃内存操作错误如访问了已释放的字符串指针。动态修改菜单项时特别是修改pText确保新字符串的生命周期覆盖菜单的使用期。考虑使用字符串池管理。长时间运行后内存不足频繁创建销毁菜单窗口导致内存碎片或泄漏。改为菜单对象复用模式。使用WM_HideWindow()和MENU_Popup()/WM_ShowWindow()来显示/隐藏而非创建/删除。6.4 调试与开发建议启用调试信息在emWin配置中启用GUI_DEBUG级别日志可以观察窗口创建、消息传递等过程帮助定位问题。使用模拟器SEGGER的emWin模拟器Windows版是极佳的开发调试工具。你可以先在PC上快速验证菜单的逻辑和外观大部分代码可以无缝移植到嵌入式目标板。简化起步不要一开始就试图构建复杂的多级菜单。从一个最简单的、只有两三个项的垂直菜单开始确保创建、显示、消息处理整个链路是通的。然后再逐步添加子菜单、动态特性、样式定制。封装与抽象当菜单逻辑变得复杂时不要把所有MENU_AddItem和消息处理case都堆在主窗口代码里。考虑将菜单的创建、配置、消息映射封装成独立的模块或函数提高代码可读性和可维护性。最后再分享一个小心得emWin的菜单控件虽然强大但对于一些特别定制化的交互比如环形菜单、图标菜单可能就需要你基于WIDGET或直接从WINDOW对象开始自绘了。但在90%的应用场景下熟练运用本文介绍的这些API足以构建出专业、流畅、易用的嵌入式菜单系统。关键在于理解其对象模型和消息驱动机制剩下的就是根据产品需求进行组合和优化了。

相关新闻