当前位置: 首页 > news >正文

emWin高级控件实战:LISTWHEEL与MENU的嵌入式GUI开发指南

1. 项目概述与核心价值

在嵌入式GUI开发领域,尤其是基于微控制器的资源受限环境中,构建一个既美观又响应灵敏的用户界面是一项不小的挑战。开发者常常需要在有限的RAM、Flash和CPU性能下,实现流畅的滚动、精准的触控反馈以及清晰的菜单导航。这正是emWin这类专业嵌入式GUI库大显身手的地方。它不仅仅是一个图形绘制引擎,更是一个封装了复杂交互逻辑的控件(Widget)工具箱,让开发者能够像搭积木一样快速构建出功能完整的界面。

今天,我们把焦点放在emWin V5.28中两个极具代表性且功能强大的高级控件上:LISTWHEEL(列表滚轮)MENU(菜单)。LISTWHEEL控件模拟了物理滚轮或触摸滑动的交互体验,特别适合用于时间选择、数值调节等需要“拨动”感的场景;而MENU控件则是构建应用导航和命令系统的骨架,无论是简单的设置菜单还是复杂的多级导航,都离不开它。

你手头可能只有一份零散的官方API手册片段,里面充斥着函数原型和参数列表,读起来枯燥且难以形成系统认知。这份资料就像一张张零散的零件图纸,你知道每个螺丝钉的规格,却不知道如何组装成一台能运转的机器。本文将扮演“装配手册”和“实战指南”的角色,我会基于自己多年在STM32、NXP等平台上使用emWin的经验,不仅为你逐条解读这些API,更会深入剖析其设计原理、分享实际项目中的配置技巧、避坑指南以及性能优化思路。我们的目标是将这些碎片化的信息,整合成一份你可以在下一个项目中直接参考、甚至“抄作业”的实战宝典。

2. LISTWHEEL控件:打造流畅的触控滚轮体验

2.1 控件原理与核心设计思路

LISTWHEEL,顾名思义,是一个“列表滚轮”控件。它与传统的LISTBOX(列表框)有本质区别。LISTBOX通常通过键盘方向键或附着的滚动条来浏览项目,交互是离散的、步进式的。而LISTWHEEL的设计灵感来源于物理滚轮或智能手机上的惯性滚动列表,其核心交互是连续的、模拟物理运动的

它的工作原理可以这样理解:当用户通过触摸屏或鼠标在控件上纵向拖拽时,整个列表内容会跟随手指移动,产生“滚动”效果。松开手指后,列表不会立即停止,而是会根据释放时的速度进行“惯性滑动”,并最终自动对齐到某个项目位置,这个过程称为“吸附”(Snap)。更巧妙的是,LISTWHEEL的列表是“循环”的,滚动到最后一项后会无缝衔接到第一项,就像真正的轮子一样,实现了无限滚动的视觉效果。

这种设计带来了两个核心优势:

  1. 交互自然:符合触控设备的操作直觉,用户体验更佳。
  2. 空间高效:在一个固定高度的区域内,可以浏览理论上无限多的项目(通过循环),非常适合在空间有限的嵌入式屏幕上做选择器。

2.2 关键API详解与实战配置

官方手册列出了数十个API,我们不必死记硬背,而是按功能模块来理解和运用。下面我将结合代码示例和配置心得,讲解最核心的几个函数群。

2.2.1 创建与初始化

创建LISTWHEEL是第一步,LISTWHEEL_CreateEx()是最常用的函数。

GUI_CONST_STORAGE char * apWeekdays[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", NULL // 必须以此结尾! }; LISTWHEEL_Handle hListWheel; hListWheel = LISTWHEEL_CreateEx(50, 100, // x, y 位置 200, 150, // 宽度,高度 hParent, // 父窗口句柄 WM_CF_SHOW, // 创建后立即显示 0, // ExFlags,保留 GUI_ID_LISTWHEEL0, // 控件ID apWeekdays); // 初始文本数组

注意apWeekdays数组的最后一个元素必须是NULL。这是emWin中许多以指针数组作为参数的函数的通用约定,用于标识数组结束。忘记添加NULL会导致内存越界访问,是常见的崩溃原因。

创建后,我们通常需要调整其视觉和交互参数,使其更符合我们的设计。

2.2.2 视觉样式定制
  • 字体与颜色:使用LISTWHEEL_SetFont()LISTWHEEL_SetTextColor()来改变字体和文本颜色。LISTWHEEL_SetBkColor()可以分别设置选中项和未选中项的背景色。

    LISTWHEEL_SetFont(hListWheel, &GUI_Font24B_ASCII); // 设置为24点阵粗体 LISTWHEEL_SetTextColor(hListWheel, LISTWHEEL_CI_SEL, GUI_RED); // 选中项红色 LISTWHEEL_SetTextColor(hListWheel, LISTWHEEL_CI_UNSEL, GUI_DARKGRAY); // 未选中项深灰 LISTWHEEL_SetBkColor(hListWheel, LISTWHEEL_CI_SEL, GUI_LIGHTBLUE); // 选中项浅蓝背景
  • 行高与边距:默认行高由字体决定。如果你希望项目之间有更大的间距,或者需要为自定义绘制留出空间,可以使用LISTWHEEL_SetLineHeight()

    // 设置固定行高为30像素,即使字体高度只有20像素,也会留出上下边距 LISTWHEEL_SetLineHeight(hListWheel, 30); // 设置文本距离控件左边缘10像素,右边缘5像素 LISTWHEEL_SetLBorder(hListWheel, 10); LISTWHEEL_SetRBorder(hListWheel, 5);
  • 文本对齐:通过LISTWHEEL_SetTextAlign()可以设置文本在项目区域内的对齐方式,如左对齐GUI_TA_LEFT、居中GUI_TA_HCENTER、右对齐GUI_TA_RIGHT

2.2.3 交互行为调优

这是让LISTWHEEL体验“跟手”的关键。

  • 吸附位置LISTWHEEL_SetSnapPosition()决定了列表停止滚动时,哪个位置会“吸附”到控件的固定点(默认为顶部,y=0)。假设你的控件高度是150,你希望当前选中项始终显示在控件垂直中心,那么吸附位置应设置为75。

    // 设置吸附位置为控件垂直中心 int snapPos = LISTWHEEL_GetYSize(hListWheel) / 2; LISTWHEEL_SetSnapPosition(hListWheel, snapPos);

    这个设置直接影响LISTWHEEL_GetPos()LISTWHEEL_GetSel()的返回值,它们返回的是位于吸附位置上的项目索引。

  • 减速度LISTWHEEL_SetDeceleration()控制手指松开后滚轮惯性滑动的“阻力”。值越大,停止得越快,感觉越“生硬”;值越小,滑动时间越长,感觉越“顺滑”。默认值是15,在实际的触屏设备上,我通常需要根据屏幕尺寸和项目数量进行微调,范围在10到30之间进行试验,以找到最符合物理直觉的值。

  • 定时器周期LISTWHEEL_SetTimerPeriod()设置控件内部动画更新的时间间隔,默认25ms(即40FPS)。在性能较低的MCU上,如果同时刷新多个控件感到吃力,可以适当增大此值(如40ms),但会降低动画的流畅度。

2.2.4 动态操作与数据获取
  • 编程控制滚动:除了用户触摸,你也可以用代码控制滚动。LISTWHEEL_SetPos()是直接“跳转”到指定索引项。而LISTWHEEL_MoveToPos()则会以动画方式滚动到目标项,并且会自动选择最短路径(因为列表是循环的)。LISTWHEEL_SetVelocity()则可以给滚轮一个初始速度让它自己滚动,常用于实现“快速滑动”后的惯性效果模拟。

    // 直接跳转到第3项(索引2) LISTWHEEL_SetPos(hListWheel, 2); // 或以动画方式滚动到第3项 LISTWHEEL_MoveToPos(hListWheel, 2); // 设置一个初始速度,正数向下,负数向上 LISTWHEEL_SetVelocity(hListWheel, 50);
  • 获取当前选择:最常用的就是LISTWHEEL_GetSel(),它返回当前位于吸附位置(即被选中)的项目索引。结合LISTWHEEL_GetItemText()可以获取其文本内容。

    int selIndex = LISTWHEEL_GetSel(hListWheel); char buffer[50]; LISTWHEEL_GetItemText(hListWheel, selIndex, buffer, sizeof(buffer)); GUI_DispStringAt(buffer, 10, 10); // 在屏幕其他地方显示选中项

2.3 高级应用:自定义绘制(Owner Draw)

LISTWHEEL的默认绘制是简单的文本。但在很多高级UI中,我们需要每个项目显示图标、不同颜色、甚至更复杂的图形。这时就需要用到Owner Draw(自绘)功能。

通过LISTWHEEL_SetOwnerDraw()注册一个自定义的绘制回调函数,你就能完全掌控每个项目的渲染过程。

static int _MyOwnerDraw(const WIDGET_ITEM_DRAW_INFO * pDrawItemInfo) { const char* pText; GUI_RECT Rect = *pDrawItemInfo->pRect; int Index = pDrawItemInfo->ItemIndex; int IsSelected = (pDrawItemInfo->SelState & WIDGET_ITEM_STATE_SELECTED) ? 1 : 0; switch (pDrawItemInfo->Cmd) { case WIDGET_ITEM_GET_YSIZE: // 告诉控件我们期望的项目高度是40像素 return 40; case WIDGET_ITEM_DRAW: // 绘制背景 if (IsSelected) { GUI_SetColor(GUI_BLUE); GUI_FillRectEx(&Rect); GUI_SetColor(GUI_WHITE); } else { GUI_SetColor(GUI_LIGHTGRAY); GUI_FillRectEx(&Rect); GUI_SetColor(GUI_BLACK); } // 获取项目文本 LISTWHEEL_GetItemText(pDrawItemInfo->hWin, Index, buffer, sizeof(buffer)); // 在矩形区域内绘制文本(居中) GUI_SetTextAlign(GUI_TA_HCENTER | GUI_TA_VCENTER); GUI_DispStringInRect(buffer, &Rect, 0); // 如果不是最后一项,画一条分隔线 if (Index < LISTWHEEL_GetNumItems(pDrawItemInfo->hWin) - 1) { GUI_SetColor(GUI_DARKGRAY); GUI_DrawHLine(Rect.y1, Rect.x0, Rect.x1); } break; default: // 对于不处理的消息,调用默认绘制函数以确保基础功能正常 return LISTWHEEL_OwnerDraw(pDrawItemInfo); } return 0; } // 在初始化时设置自绘函数 LISTWHEEL_SetOwnerDraw(hListWheel, _MyOwnerDraw);

实操心得:在Owner Draw函数中,务必处理好WIDGET_ITEM_GET_YSIZE命令,返回你自定义的项目高度。否则,控件会使用默认的字体高度进行计算,导致布局错乱。对于不打算处理的绘制命令(如WIDGET_DRAW_OVERLAY),最好像上面一样调用默认的LISTWHEEL_OwnerDraw(),这是一个好习惯,能避免一些潜在的显示问题。

2.4 常见问题与排查技巧实录

在实际项目中,使用LISTWHEEL可能会遇到一些“坑”,下面是我总结的常见问题及解决方法:

问题现象可能原因排查步骤与解决方案
触摸滚动无反应或卡顿1. 未启用触摸屏支持或触摸校准不准。
2. 控件未获得焦点。
3. 在WM_TOUCH消息处理中未调用WM_HandlePID()
1. 确认GUI_PID_StoreState()被正确调用,将触摸坐标传入emWin。
2. 使用WM_SetFocus()将焦点设置到LISTWHEEL控件或其父窗口。
3. 确保在主任务或触摸中断中正确传递了触摸事件。
惯性滚动不自然,停止太快或太慢LISTWHEEL_SetDeceleration()参数设置不当。这是一个需要反复调试的参数。建议创建一个测试界面,用滑块控件实时调整减速度值,观察效果。通常从默认值15开始,每次增减5进行测试。
文本显示不完整或被裁剪1. 控件宽度不足。
2. 文本长度超过了控件宽度,且未设置自动换行。
1. 增加控件创建时的xSize参数。
2. 使用LISTWHEEL_SetWrapMode(hObj, GUI_WRAPMODE_WORD)启用按单词换行,或GUI_WRAPMODE_CHAR按字符换行。注意,这需要控件有足够的高度。
选中项索引获取错误混淆了LISTWHEEL_GetPos()LISTWHEEL_GetSel()LISTWHEEL_GetPos()返回的是当前位于控件顶部(或自定义吸附点)的项目索引,而LISTWHEEL_GetSel()返回的是当前被选中(高亮)的项目索引。在未设置吸附位置时,两者通常相同。务必根据你的交互设计选择正确的函数。
动态修改列表内容后显示异常直接操作了内部字符串数组指针,未通知控件刷新。修改列表内容后,应调用WM_InvalidateWindow(hListWheel)强制重绘控件。如果内容变化较大,更稳妥的方法是先LISTWHEEL_DeleteAllItems()(如果API支持)或销毁重建,再重新添加项目。
Owner Draw模式下,项目高度不一致或闪烁Owner Draw函数对WIDGET_ITEM_GET_YSIZE命令的返回值不一致或计算错误。确保WIDGET_ITEM_GET_YSIZE命令返回固定的、准确的项目高度值。避免在此命令中进行复杂的动态计算。闪烁可能是由于重绘区域计算错误,尝试在WM_PAINT消息中只绘制无效区域。

3. MENU控件:构建层级导航与命令系统

3.1 控件原理与消息机制

MENU控件用于创建水平或垂直的菜单系统,支持多级子菜单、菜单项禁用/启用、分隔符等特性。与LISTWHEEL不同,MENU的核心在于消息驱动

当用户与菜单交互(点击、选择、打开子菜单)时,MENU控件会向其所有者窗口发送WM_MENU消息。应用程序需要在自己的窗口回调函数中处理此消息,以执行相应的命令。

理解“所有者窗口”是关键。默认情况下,菜单的所有者是其父窗口。但你可以通过MENU_SetOwner()指定任何一个窗口句柄来接收菜单消息。这为灵活设计UI架构提供了可能,例如让一个全局的命令处理器窗口来管理所有菜单动作。

WM_MENU消息的Data.p指针指向一个MENU_MSG_DATA结构,其中包含:

  • MsgType:消息类型,如MENU_ON_ITEMSELECT(项目被选中)、MENU_ON_INITMENU(菜单初始化前,可用于动态更新菜单状态)。
  • ItemId:触发事件的菜单项ID。

3.2 菜单创建、结构与动态管理

3.2.1 创建与附加

创建菜单有两种主要方式:

  1. 作为子窗口创建:使用MENU_CreateEx(),指定父窗口。菜单会作为该窗口的一个子控件存在。
  2. 作为弹出菜单创建:先创建一个无父窗口(或父窗口为WM_UNATTACHED)的菜单,然后在需要时使用MENU_Popup()将其附加到某个窗口的指定位置。弹出菜单在选择后会自动关闭。
// 方式1:创建水平主菜单栏 MENU_Handle hMainMenu; hMainMenu = MENU_CreateEx(0, 0, LCD_GetXSize(), 30, hMainFrame, WM_CF_SHOW, MENU_CF_HORIZONTAL, ID_MENU_MAIN); // 方式2:创建垂直弹出菜单(例如右键菜单) MENU_Handle hPopupMenu; hPopupMenu = MENU_CreateEx(0, 0, 0, 0, WM_UNATTACHED, WM_CF_SHOW, MENU_CF_VERTICAL, ID_MENU_POPUP); // ... 为hPopupMenu添加项目 ... // 在某个事件(如WM_TOUCH)中弹出 MENU_Popup(hPopupMenu, hDestWin, xPos, yPos, 120, 0, 0); // 宽度固定120,高度自动

注意MENU_CreateExxSizeySize参数。如果设为0,菜单会根据其内容自动调整尺寸。如果设为固定值,则菜单尺寸固定,添加/删除项目时尺寸不变,项目可能显示不全。对于水平主菜单栏,通常需要固定宽度为屏幕宽度;对于弹出菜单,通常固定宽度,高度自动。

3.2.2 构建菜单结构

菜单项通过MENU_ITEM_DATA结构来定义,并使用MENU_AddItem()MENU_InsertItem()添加。

// 定义菜单项数据 static const MENU_ITEM_DATA _aMenuItemData[] = { // 文本指针, ID, 标志, 子菜单句柄 { "File", ID_MENU_FILE, 0, hSubMenuFile }, // 有子菜单 { "Edit", ID_MENU_EDIT, 0, 0 }, { "", ID_MENU_SEP1, MENU_IF_SEPARATOR, 0 }, // 分隔符 { "Help", ID_MENU_HELP, 0, hSubMenuHelp }, { "Exit", ID_MENU_EXIT, 0, 0 }, }; // 添加项目到主菜单 for(i = 0; i < GUI_COUNTOF(_aMenuItemData); i++) { MENU_AddItem(hMainMenu, &_aMenuItemData[i]); } // 动态添加一个项目到指定位置(在ID_MENU_EDIT之前插入) MENU_ITEM_DATA newItem = {"New", ID_MENU_NEW, 0, 0}; MENU_InsertItem(hMainMenu, ID_MENU_EDIT, &newItem);

关键点

  • 子菜单:通过hSubmenu成员关联。你需要先创建子菜单(MENU_CreateEx,通常为垂直菜单),然后将其句柄赋给父菜单项的hSubmenu
  • 分隔符:设置MENU_IF_SEPARATOR标志,pText通常为空字符串。
  • 禁用项:设置MENU_IF_DISABLED标志,或后期使用MENU_DisableItem()/MENU_EnableItem()动态控制。
  • ID唯一性:官方建议,在整个菜单系统中,菜单项ID应保持唯一。这能简化消息处理逻辑。
3.2.3 消息处理示例

在所有者窗口的回调函数中处理WM_MENU消息:

static void _cbCallback(WM_MESSAGE * pMsg) { MENU_MSG_DATA * pMenuData; switch (pMsg->MsgId) { case WM_MENU: pMenuData = (MENU_MSG_DATA *)pMsg->Data.p; switch (pMenuData->MsgType) { case MENU_ON_INITMENU: // 菜单显示前调用,可用于根据程序状态更新菜单项(如禁用“粘贴”) if (!_HasDataInClipboard()) { MENU_DisableItem(pMsg->hWinSrc, ID_MENU_PASTE); } else { MENU_EnableItem(pMsg->hWinSrc, ID_MENU_PASTE); } break; case MENU_ON_ITEMSELECT: // 菜单项被选中(点击或按Enter) switch (pMenuData->ItemId) { case ID_MENU_FILE_NEW: _CreateNewFile(); break; case ID_MENU_FILE_OPEN: _OpenFileDialog(); break; case ID_MENU_EXIT: _CloseApplication(); break; // ... 处理其他ID ... } break; case MENU_ON_ITEMACTIVATE: // 菜单项被高亮(鼠标悬停或键盘导航),可用于更新状态栏提示 _UpdateStatusBarHint(pMenuData->ItemId); break; } break; default: // 其他消息传递给默认回调,这对MENU控件正常工作很重要 MENU_Callback(pMsg); break; } }

重要提示:在窗口回调中,对于非WM_MENU的消息,务必调用MENU_Callback(pMsg)。这个默认回调函数处理了菜单的绘制、触摸、键盘导航等所有基础逻辑。如果忘记调用,菜单将无法正常显示和交互。

3.3 视觉定制与皮肤效果

emWin的MENU控件支持“皮肤”(Skinning),这本质上是通过WIDGET_EFFECT结构体来定义菜单项的绘制效果。

  • 设置默认效果MENU_SetDefaultEffect()会影响之后创建的所有新菜单。
  • 设置特定菜单效果MENU_SetEffect()(注意:在提供的API片段中未列出,但通常存在)或通过WIDGET_SetEffect()来设置。 emWin内置了几种效果,如WIDGET_Effect_3D1L(默认的3D凸起效果)、WIDGET_Effect_Simple(简单的平面填充效果)。你也可以创建自定义的WIDGET_EFFECT结构体,实现完全个性化的绘制,比如圆角、渐变背景等。
// 为某个菜单设置简单的平面效果 MENU_SetEffect(hMyMenu, &WIDGET_Effect_Simple);
  • 颜色与字体定制:与LISTWHEEL类似,可以通过MENU_SetBkColor()MENU_SetTextColor()MENU_SetFont()等函数精细控制不同状态(启用、选中、禁用、激活子菜单)下的外观。
    // 设置主菜单选中项为蓝底白字 MENU_SetBkColor(hMainMenu, MENU_CI_SELECTED, GUI_BLUE); MENU_SetTextColor(hMainMenu, MENU_CI_SELECTED, GUI_WHITE); // 设置菜单项内边距 MENU_SetBorderSize(hMainMenu, MENU_BI_LEFT, 15); MENU_SetBorderSize(hMainMenu, MENU_BI_RIGHT, 15);

3.4 键盘导航支持

在嵌入式设备中,除了触摸,键盘(或编码器、五向按键)也是重要的输入方式。MENU控件内置了完善的键盘导航逻辑,如上文API表格所述:

  • GUI_KEY_RIGHT/GUI_KEY_LEFT:在水平菜单中左右移动选择;在垂直菜单中,用于进入/退出子菜单。
  • GUI_KEY_DOWN/GUI_KEY_UP:在垂直菜单中上下移动选择;在水平菜单中,用于打开当前选中项的子菜单。
  • GUI_KEY_ENTER:激活选中项(执行命令)或打开其子菜单。
  • GUI_KEY_ESCAPE:关闭当前子菜单或取消当前菜单的选择。

要让键盘生效,只需确保菜单窗口或其父窗口拥有输入焦点(通过WM_SetFocus()设置),并且键盘事件通过GUI_StoreKeyMsg()正确传递给了emWin系统。

3.5 常见问题与排查技巧实录

问题现象可能原因排查步骤与解决方案
菜单点击无反应,不发送WM_MENU消息1. 窗口回调函数未正确处理WM_MENU消息。
2. 菜单项被禁用(MENU_IF_DISABLED)。
3. 菜单的所有者窗口设置错误。
1. 在回调函数中添加WM_MENU消息处理,并检查MsgTypeItemId
2. 检查菜单项的标志位,或使用MENU_EnableItem()启用它。
3. 确认MENU_SetOwner()设置正确,或默认父窗口能收到消息。
子菜单无法弹出或位置错乱1. 子菜单句柄hSubmenu未正确关联或创建失败(返回0)。
2. 父菜单或子菜单的创建标志(ExFlags)不正确。
3. 屏幕坐标计算错误(对于MENU_Popup)。
1. 检查子菜单创建函数的返回值,确保句柄有效。
2. 水平主菜单用MENU_CF_HORIZONTAL,垂直子菜单用MENU_CF_VERTICAL
3.MENU_Popup的坐标是相对于hDestWin客户区的。确保计算正确,特别是使用多层窗口时。
菜单项文本显示为乱码或方框1. 字体不支持所显示的字符。
2. 字符串指针pText指向了非法或已释放的内存(对于动态字符串)。
3. 使用了非ASCII字符但未启用相应字体。
1. 使用MENU_SetFont()设置为包含所需字符的字体,如中文字体。
2. 对于动态字符串,确保其生命周期覆盖菜单显示期间。最好使用静态常量字符串。
3. 确认编译器和工程设置正确支持宽字符或UTF-8(取决于emWin配置)。
动态修改菜单项(如变灰)后显示未更新修改菜单项属性后,未通知窗口管理器进行重绘。在调用MENU_DisableItem()MENU_SetItem()等函数后,调用WM_InvalidateWindow(hMenu)强制重绘整个菜单控件。
键盘可以操作其他控件,但无法操作菜单1. 菜单控件或其父窗口未获得焦点。
2. 键盘消息未正确传递到拥有焦点的窗口。
1. 在显示菜单后,调用WM_SetFocus(hMenu)WM_SetFocus(hParent)
2. 确保你的键盘扫描代码调用了GUI_StoreKeyMsg(Key, Pressed),并将消息发送给当前焦点窗口。
内存泄漏(长时间运行后内存减少)频繁创建和销毁弹出菜单(MENU_Popup),但未正确销毁。MENU_Popup不会自动销毁菜单对象。在弹出菜单关闭后(例如在MENU_ON_ITEMSELECT处理完后),如果不再需要该菜单对象,应调用WM_DeleteWindow(hPopupMenu)进行销毁。或者,复用同一个菜单对象。

4. 综合应用案例:构建一个日期时间设置界面

理论说得再多,不如一个实际案例来得直观。假设我们要为一个智能家居面板设计一个日期时间设置界面,其中包含年、月、日的LISTWHEEL选择器和顶部的MENU栏。

4.1 界面布局与设计

  • 顶部:一个水平MENU,包含“设置”、“保存”、“返回”等选项。
  • 中部:三个并列的LISTWHEEL控件,分别用于选择年、月、日。它们循环滚动,并且具有自定义的视觉效果(如中间项高亮放大)。
  • 底部:一个文本标签,实时显示当前选择的日期。

4.2 关键实现步骤

  1. 创建主窗口和菜单

    WM_HWIN hMainWin; MENU_Handle hTopMenu; // 创建主窗口 hMainWin = WM_CreateWindow(...); // 创建顶部菜单栏 hTopMenu = MENU_CreateEx(0, 0, LCD_GET_XSIZE(), 35, hMainWin, WM_CF_SHOW, MENU_CF_HORIZONTAL, ID_MENU_TOP); // 添加菜单项 MENU_ITEM_DATA topItems[] = {{"Settings", ID_MENU_SETTINGS, 0, hSubMenuSettings}, ...}; // ... 添加项目 ... // 设置菜单回调,主窗口作为所有者 WM_SetCallback(hMainWin, _MainWinCallback);
  2. 创建并配置LISTWHEEL控件

    LISTWHEEL_Handle hWheelYear, hWheelMonth, hWheelDay; // 创建年份滚轮 (2020-2030) char* yearStr[11]; for(int i=0; i<11; i++) { yearStr[i] = _IntToStr(2020+i); } yearStr[11] = NULL; hWheelYear = LISTWHEEL_CreateEx(50, 80, 80, 200, hMainWin, WM_CF_SHOW, 0, ID_WHEEL_YEAR, yearStr); // 设置居中吸附,自定义减速度,OwnerDraw用于高亮中心项 LISTWHEEL_SetSnapPosition(hWheelYear, 100); // 控件高200,中心是100 LISTWHEEL_SetDeceleration(hWheelYear, 18); // 稍慢的减速,更顺滑 LISTWHEEL_SetOwnerDraw(hWheelYear, _DateWheelOwnerDraw); // 类似创建月和日滚轮...
  3. 实现自定义绘制(Owner Draw): 在_DateWheelOwnerDraw函数中,根据ItemIndexSelState判断当前绘制项是否是位于中心的选择项。如果是,则用更大的字体、不同的颜色绘制,模拟“放大镜”效果。

  4. 处理交互与同步

    • 在LISTWHEEL的WM_NOTIFY_PARENT消息中(通知码WM_NOTIFICATION_SEL_CHANGED),获取当前选择的年、月、日。
    • 根据选择的年月,动态更新“日”LISTWHEEL的内容(注意大小月、闰年)。
    • 将最终日期更新到底部的文本标签。
    • 在顶部MENU的WM_MENU消息处理中,响应“保存”按钮,将当前日期写入RTC或存储芯片。

4.3 性能优化要点

  • 内存管理:LISTWHEEL的字符串数组如果很大,考虑使用内存设备(Memory Device)或虚拟化技术,避免一次性加载所有项目。
  • 绘制优化:在Owner Draw函数中,避免复杂的计算和多次设置颜色/字体。使用GUI_SetColor()GUI_SetFont()后,尽量批量绘制。
  • 消息处理:确保窗口回调函数尽快返回,避免在WM_PAINTWM_MENU消息中进行耗时操作(如文件读写、复杂计算)。必要时,可以发送自定义用户消息,在后台任务中处理。

通过这个案例,你将LISTWHEEL的流畅交互、MENU的系统命令、Owner Draw的自定义能力以及消息驱动机制串联了起来,构成了一个完整、可用的嵌入式GUI模块。这远比孤立地学习每个API参数要有价值得多。记住,在嵌入式GUI开发中,理解控件背后的设计哲学和消息流,比记住所有函数原型更重要。当你遇到问题时,多从“消息是否传递”、“焦点是否正确”、“内存是否有效”这几个角度去排查,往往能更快地找到突破口。

http://www.gsyq.cn/news/1560488.html

相关文章:

  • 自然人身份确权元数据集合赋能医疗健康证照合规
  • 抛弃传统RAG:LLM Wiki才是Agent真正的知识大脑
  • 有哪些AI论文网站是真的坚守学术严谨,而不是通用套壳?
  • Gemini 3.1 Flash-Lite端侧推理实战指南
  • 数字拼写转换:从规则解析到多语言自动化实现
  • 十二层PCB打样难?看看他三个月如何搞定交付
  • 抖音无水印下载神器:3分钟学会批量保存高清视频的必备工具
  • 2026扬州本地正规瓷砖空鼓维修服务商盘点|无损免拆砖修复,全域上门售后有保障 - 宅安选房屋修缮
  • 2026年6月最新劳力士中国官方售后客户地址热线电话服务网点 - 劳力士服务中心
  • CTF逆向实战:位操作加密(左移4右移4)原理与破解
  • 2026年6月最新浪琴中国官方售后服务地址热线及客服网点电话 - 浪琴服务中心
  • llama.cpp中MoE模型卸载优化实战指南
  • 鸿蒙物理 108 篇 第十八篇 开合吞吐场域交互法则
  • 3分钟掌握OpenSpeedy:让单机游戏运行如飞的免费开源神器
  • Windows下Hugging Face模型下载实战:绕过Git LFS与HTTP/1.1瓶颈
  • AMD 780M核显Windows原生运行ComfyUI实战指南
  • 北京播音主持艺考培训机构盘点 聚焦班型与师资配置 - 互联网科技品牌测评
  • 2026年森屿文华深度解析:朝阳东坝板块置业场景配套兑现与价值疑虑 - 品牌推荐
  • 算法优化思维:从暴力解法到最优解的分析过程
  • 2026年6月最新天梭中国官方售后客户服务地址及联系电话 - 天梭服务中心
  • 2026年6月最新欧米茄中国官方售后客服地址电话及服务网点汇总 - 欧米茄服务中心
  • 鸿蒙全球局势推演:火星殖民时代英语词汇爆炸推演:千万级术语通胀、学习成本危机与鸿蒙大一统体系破局研究(四)
  • 帆软报表前台任意文件上传漏洞深度剖析与武器化实践
  • 2026青岛门窗选购权威推荐:五大本地实力派源头工厂年度榜单与深度实测 - GrowthUME
  • 沈阳刑事律师机构排行:基于专业维度的客观参考 - 互联网科技品牌测评
  • 北京播音主持艺考考前冲刺班盘点 适配不同备考需求 - 互联网科技品牌测评
  • 南通办理营业性演出许可证代办服务商推荐 - 速递信息
  • 2026年6月最新江诗丹顿中国官方售后电话网点服务热线地址客服 - 江诗丹顿服务中心
  • 数据说话!2026沈阳黄金回收高价、正规、速度综合排名 - 奢侈品交易观察员
  • 2026年现阶段西安企业甄选AI推广服务商:口碑、实力与落地能力缺一不可 - 速递信息