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

emWin窗口管理器高级API:运动支持、工具提示与多缓冲实战解析

1. emWin窗口管理器:嵌入式GUI的“中枢神经”

在嵌入式图形界面开发这片战场上,emWin的窗口管理器(Window Manager,简称WM)扮演着绝对核心的“中枢神经”角色。它远不止是屏幕上那些方框的简单管理者,而是一个负责调度、协调、渲染和响应的复杂系统。如果你把每个窗口、按钮、滑块都看作一个独立的“演员”,那么WM就是那个掌控全局的“导演”,它决定了谁在何时、何地、以何种方式登场和表演。对于像我这样在一线摸爬滚打多年的嵌入式工程师来说,深刻理解WM的运作机制,是写出高效、稳定、流畅GUI应用的基本功。今天,我们就抛开官方手册的刻板描述,深入聊聊WM中几个高级但极其实用的API模块:运动支持、工具提示和多缓冲。这些功能,往往是区分一个“能用”的界面和一个“好用”的界面的关键。

2. 运动支持:让界面“活”起来

静态的界面早已无法满足现代用户的交互预期。一个能够平滑移动、带有惯性或吸附效果的窗口或控件,能极大提升产品的质感和用户体验。emWin的WM_MOTION系列API,正是为此而生。

2.1 运动支持的核心原理与启用

运动支持的本质,是在WM的消息循环和重绘机制之上,叠加了一套基于时间和物理参数(速度、加速度、减速度)的动画引擎。它并不是实时响应你的每一次坐标设置,而是根据你设定的初始状态(如速度),由WM在后台自动计算每一帧的位置并更新窗口。

启用运动支持是整个功能的基石,必须在程序初始化阶段,在创建任何需要运动的窗口之前调用:

WM_MOTION_Enable(1); // 启用全局运动支持

这个调用是一次性的。我见过不少新手在每次启动动画前都调用它,这完全没有必要,反而可能引入未知状态。记住,它像是一个总开关,打开后,WM才会在内部处理与运动相关的消息和计算。

2.2 关键运动API详解与应用场景

运动API看似繁多,但根据其控制维度,可以清晰地分为几类:

1. 直接设定运动状态这是最直接的控制方式,告诉窗口“以这个速度动起来”。

  • WM_MOTION_SetSpeed(WM_HWIN hWin, int Axis, I32 Speed)

    • 功能:让指定窗口沿指定轴开始匀速运动。
    • 参数Speed单位是像素/秒,支持正负值表示方向。
    • 应用场景:实现滑出式菜单、持续滚动的背景、简单的平移动画。例如,实现一个从屏幕右侧滑入的侧边栏:
      // 假设 hSidebar 是侧边栏窗口句柄,初始位置在屏幕右侧之外(x坐标大于屏幕宽度) WM_MOTION_SetSpeed(hSidebar, GUI_COORD_X, -300); // 以300像素/秒的速度向左移动
  • WM_MOTION_SetMotion(WM_HWIN hWin, int Axis, I32 Speed, I32 Deceleration)

    • 功能:在设定速度的同时,指定一个减速度。窗口会以初速启动,然后在减速度作用下逐渐停止。
    • 参数Deceleration单位是像素/(秒*秒)
    • 应用场景:模拟“投掷”效果。用户在列表上快速滑动后松手,列表会依靠惯性继续滚动一段距离并减速停止。这里的减速度决定了滚动停止的快慢。

2. 设定运动过程与终点这类API不仅控制如何动,还明确了动的结果。

  • WM_MOTION_SetMovement(WM_HWIN hWin, int Axis, I32 Speed, I32 Dist)
    • 功能:让窗口以指定速度移动一段精确距离后自动停止。
    • 参数Dist必须是正值,表示移动的像素距离。
    • 应用场景:实现精确的、非交互的位移动画。比如,点击一个按钮后,一个提示窗口从屏幕底部向上精确弹出200像素。你无需计算动画时间,WM会基于速度和距离自动算出运动时长并停止。

3. 动态控制与精细调节在运动过程中,我们可能需要对运动进行干预。

  • WM_MOTION_SetDeceleration(WM_HWIN hWin, int Axis, I32 Deceleration)

    • 功能:动态修改一个正在运动窗口的减速度。
    • 重要前提:窗口必须已经在运动(例如通过SetSpeedSetMotion启动)。对静止窗口调用此函数无效。
    • 应用场景:实现复杂的交互。例如,一个可拖拽的窗口,在用户拖拽时跟随手指移动(此时减速度可能为0或很小),当用户松手时,根据松手瞬间的速度和方向,动态设置一个较大的减速度来实现“甩动后减速停止”的效果。
  • WM_MOTION_SetDefaultPeriod(unsigned Period)

    • 功能:设置一个默认的“补间动画”周期(单位:毫秒)。
    • 影响:这个周期影响两个场景:
      1. 减速阶段时长:如果一个窗口正在运动并被设置了减速度,Period定义了从当前速度减速到0的理想时间窗口。WM会据此调整减速度的计算,使减速过程大致在这个周期内完成。
      2. 吸附动画时长:如果启用了吸附功能(snapping),窗口在寻找并移动到下一个栅格位置时,会在这个周期内完成移动动画。
    • 返回值:返回之前设置的周期值,便于临时修改后恢复。
    • 实操心得:这个值不宜设置过长,通常100ms到500ms是比较理想的区间,既能让人眼感知到动画,又不会觉得拖沓。在资源紧张的MCU上,过长的动画周期会占用过多的CPU时间进行无意义的中间帧计算。

4. 启用窗口的可移动属性这是所有运动控制的前置条件。一个窗口必须被标记为“可移动”,它才能响应运动API。

  • WM_MOTION_SetMoveable(WM_HWIN hWin, U32 Flags, int OnOff)
    • 功能:启用或禁用指定窗口在特定方向上的可移动性。
    • 参数Flags可以是WM_CF_MOTION_X(允许X轴移动)、WM_CF_MOTION_Y(允许Y轴移动)或两者的按位或(WM_CF_MOTION_X | WM_CF_MOTION_Y)。
    • 注意:这个属性也可以在创建窗口时通过WM_CF_MOTION_X/Y标志位直接设置,或者在窗口的回调函数中处理WM_MOTION消息时动态改变。WM_MOTION_SetMoveable提供了一种运行时动态控制的灵活方式。

避坑指南:运动控制的常见问题

  1. 动画卡顿或闪烁:根本原因通常是WM的刷新与运动计算不同步。确保在主循环中稳定调用GUI_Exec()WM_Exec()。如果使用了多缓冲或存储设备,请确认它们被正确启用和配置。
  2. 运动不停止:检查是否错误地多次调用了WM_MOTION_SetSpeed而没有合适的停止机制(如减速度、距离限制或手动停止)。记住,SetSpeed是“开始运动”,要停止需要依赖减速度、距离限制,或者通过WM_MOTION_SetSpeed(hWin, axis, 0)来将速度设为0。
  3. 性能开销:每个运动的窗口都会在每次WM_Exec循环中触发重绘。同时运动的窗口过多,会对CPU和显示总线造成压力。在低端MCU上,需谨慎使用,并考虑降低动画帧率或简化动画效果。

3. 工具提示:提升交互的“贴心助手”

工具提示(ToolTip)是当用户将指针(如鼠标、触摸焦点)悬停在某个控件上时,短暂出现的一个小型信息窗口。它对于解释图标含义、显示完整内容(如长文本被截断时)、提供额外说明至关重要。emWin的WM_TOOLTIP模块提供了完整的工具提示管理功能。

3.1 工具提示的创建与管理流程

工具提示的使用遵循一个清晰的“创建-配置-添加工具-自动管理”流程。

第一步:创建工具提示对象工具提示本身是一个独立的对象,但它服务于一个特定的父窗口(通常是一个对话框)。

WM_TOOLTIP_HANDLE hToolTip; TOOLTIP_INFO aToolInfo[2]; // 假设我们有两个控件需要提示 // 填充第一个工具的信息 aToolInfo[0].hWin = hButton; // 按钮的窗口句柄 aToolInfo[0].pText = "点击此按钮提交表单"; // 填充第二个工具的信息 aToolInfo[1].hWin = hSlider; // 滑动条的窗口句柄 aToolInfo[1].pText = "拖动滑块调整音量大小"; // 创建工具提示对象,并一次性添加两个工具 hToolTip = WM_TOOLTIP_Create(hDialog, aToolInfo, 2);

WM_TOOLTIP_Create是关键函数:

  • hDialog:工具提示所属的父对话框句柄。工具提示会监听这个对话框内所有已注册“工具”的指针悬停事件。
  • pInfo:指向TOOLTIP_INFO结构体数组的指针。该结构体至少包含hWin(工具窗口句柄)和pText(提示文本)两个成员。你可以在这里预定义所有工具的提示信息。
  • NumItems:数组中工具的数量。如果传入0,则创建一个空的工具提示对象,后续需要用WM_TOOLTIP_AddTool动态添加工具。

第二步:动态添加或删除工具在应用运行过程中,你可能需要动态管理哪些控件具有工具提示。

// 动态为一个新创建的编辑框添加工具提示 WM_TOOLTIP_AddTool(hToolTip, hNewEditBox, "在此输入您的用户名");

WM_TOOLTIP_AddTool函数内部会复制传入的字符串pText到emWin管理的动态内存中。这意味着,你传入的字符串指针可以是局部变量或临时字符串,函数调用后其内存可以被释放,这大大方便了编程。

第三步:全局样式配置在创建工具提示对象前后,你都可以配置其默认外观和行为,这些配置是全局的,影响所有工具提示。

  • 设置颜色

    // 设置工具提示的背景色、边框色和文字颜色 WM_TOOLTIP_SetDefaultColor(WM_TOOLTIP_CI_BK, GUI_WHITE); // 背景白色 WM_TOOLTIP_SetDefaultColor(WM_TOOLTIP_CI_FRAME, GUI_BLUE); // 边框蓝色 WM_TOOLTIP_SetDefaultColor(WM_TOOLTIP_CI_TEXT, GUI_BLACK); // 文字黑色
  • 设置字体

    // 设置工具提示使用的字体 WM_TOOLTIP_SetDefaultFont(&GUI_Font13B_ASCII); // 使用13点阵粗体字体
  • 设置时间参数(核心行为控制)

    // 设置首次悬停后,提示出现的延迟时间(单位:毫秒) WM_TOOLTIP_SetDefaultPeriod(WM_TOOLTIP_PI_FIRST, 800); // 设置提示显示后,保持可见的持续时间 WM_TOOLTIP_SetDefaultPeriod(WM_TOOLTIP_PI_SHOW, 4000); // 设置在同一父窗口下,从一个工具移动到另一个工具时,新提示出现的延迟时间 WM_TOOLTIP_SetDefaultPeriod(WM_TOOLTIP_PI_NEXT, 100);

    这三个时间参数是调节工具提示“响应手感”的关键。PI_FIRST太长会让人觉得反应迟钝,太短则容易在鼠标无意掠过时误触发。PI_SHOW需要给用户足够时间阅读。PI_NEXT设置一个较短的延迟,可以在浏览一系列控件时获得流畅的提示切换体验。

第四步:销毁当父对话框被销毁,或者不再需要工具提示时,应手动删除工具提示对象以释放资源。

WM_TOOLTIP_Delete(hToolTip);

3.2 工具提示的底层机制与优化

工具提示的实现依赖于WM对WM_TOUCHWM_MOUSEOVER等消息的捕获和转发。当指针在注册了工具提示的窗口上停留超过PI_FIRST时间且未移动,WM就会创建一个透明的、顶层的子窗口来显示提示文本,并在指针移开或超过PI_SHOW时间后自动销毁它。

实操心得与性能考量

  1. 内存管理WM_TOOLTIP_AddTool会复制字符串。对于固定提示,使用静态字符串常量;对于动态生成的提示(如显示数值),要注意避免在频繁调用的函数中动态添加工具,以免引起内存碎片。更好的做法是初始化时一次性添加所有工具,通过WM_TOOLTIP_AddTool的返回值(索引)来动态更新某个工具的文本(虽然标准API不直接支持更新,但你可以通过删除旧工具再添加新工具,或更高级的自定义回调实现)。
  2. Z序与焦点:工具提示窗口通常被创建为最顶层窗口。确保你的UI设计中没有其他永久性顶层窗口会遮挡它。同时,工具提示不应获取焦点,以免干扰主交互流程。
  3. 触摸屏适配:在纯触摸屏设备上,没有“悬停”概念。通常需要将工具提示与“长按”事件绑定。这需要你自定义处理WM_TOUCH消息,在检测到长按时,手动触发类似工具提示的显示逻辑,emWin的标准工具提示模块对此支持有限。

4. 多缓冲与存储设备:解决闪烁与撕裂的利器

在动态图形界面中,“闪烁”和“撕裂”是两大视觉顽疾。闪烁是由于直接在屏幕上逐元素绘制,用户能看到中间过程;撕裂则发生在显示刷新和图形绘制不同步时,屏幕上同时显示两帧不同的内容。emWin提供了多缓冲和存储设备两种机制来根治这些问题。

4.1 多缓冲技术原理与启用

多缓冲的原理是准备多个(通常是2个或3个)完整的显示缓冲区(Frame Buffer)。一个作为“前台缓冲区”正在被LCD控制器扫描显示,另一个作为“后台缓冲区”用于应用程序绘制下一帧图像。当后台缓冲区绘制完成,通过一个原子操作(通常是切换指针或DMA传输)将其内容交换到前台,立即呈现完整的新帧,从而避免撕裂和闪烁。

在emWin中,启用多缓冲非常简单:

WM_MULTIBUF_Enable(1); // 启用WM自动多缓冲支持

这一行代码告诉WM:“请使用多缓冲机制来管理窗口的绘制”。此后,WM在组织窗口重绘时,会自动利用底层配置好的多缓冲硬件或软件机制。

关键理解WM_MULTIBUF_Enable只是启用WM层面对多缓冲的感知和利用。真正的多缓冲硬件配置(如分配多个帧缓冲区内存、设置LCD控制器)需要在底层驱动中完成,通常是在LCD_X_Config()函数中指定多个缓冲区的地址。emWin的WM模块会与底层驱动协作,在合适的时机(通常是所有窗口绘制完成后)触发缓冲区交换。

4.2 存储设备:软件实现的“离屏渲染”

对于不支持硬件多缓冲的显示控制器,或者在某些只需要局部无闪烁更新的场景下,存储设备(Memory Device)是完美的解决方案。

存储设备是什么?你可以把它想象成一块“离屏画布”。当为一个窗口启用存储设备后,所有对该窗口的绘制操作(GUI_DrawLine,GUI_FillRect, 文本显示等)都不会直接画在屏幕上,而是先画在这块“离屏画布”上。只有当所有绘制命令执行完毕,WM才会将整块画布的内容一次性、快速地拷贝到屏幕对应的窗口区域。由于这个拷贝操作通常很快,用户看到的是窗口内容的瞬间整体更新,从而消除了绘制过程中的闪烁。

API使用:

WM_HWIN hMyWindow; // 创建窗口... hMyWindow = WM_CreateWindow(...); // 为该窗口启用存储设备 WM_EnableMemdev(hMyWindow); // ...后续所有在该窗口客户区的绘制,都将自动使用存储设备 // 如果需要临时禁用(极少数情况,如性能调试) // WM_DisableMemdev(hMyWindow);

存储设备 vs 多缓冲:如何选择?

特性多缓冲 (Multi-buffering)存储设备 (Memory Device)
原理硬件层面多个完整帧缓冲区切换软件层面为单个窗口创建离屏画布
消除主要消除撕裂,配合WM也可消除闪烁主要消除闪烁
内存开销大 (N倍屏幕分辨率所需显存)较小 (与窗口面积成正比)
性能影响交换缓冲区开销极低,性能最佳需要内存拷贝,窗口越大拷贝开销越大
适用场景全屏动画、频繁全局更新的场景局部窗口更新、复杂控件绘制、不支持硬件多缓冲的系统
启用粒度全局启用(WM级别)可按窗口单独启用

实际项目中的策略:

  1. 首选硬件多缓冲:如果你的显示控制器和内存带宽允许,总是优先启用硬件多缓冲(WM_MULTIBUF_Enable(1))。它能提供最流畅的整体体验。
  2. 混合使用:在多缓冲已启用的全局环境下,你仍然可以为某个特别复杂、更新频繁的子窗口额外启用存储设备(WM_EnableMemdev)。这相当于为该窗口的绘制又加了一层“保险”,确保其内部更新绝对无闪烁。WM会智能处理,先绘制到存储设备,再整合到后台缓冲区。
  3. 资源紧张时:如果内存非常紧张,无法负担双帧缓冲区,那么针对性的使用存储设备是更经济的选择。只为那些确实需要无闪烁更新的窗口(如进度条、动态图表)启用它。

深度避坑:存储设备的“陷阱”

  1. 内存消耗计算:存储设备消耗的内存 = 窗口宽度 * 窗口高度 * 每个像素的字节数。对于真彩色(16位或24位)的大窗口,这块内存不容小觑。务必在系统设计阶段评估内存占用。
  2. 无效化与重绘:启用存储设备后,WM_InvalidateWindowWM_Paint的行为依然有效,但重绘的产出是先在存储设备上完成,再拷贝到屏幕。这意味着,频繁无效化小区域可能导致频繁的全窗口存储设备重绘和拷贝,反而降低性能。应尽量合并无效化区域。
  3. 与透明窗口的兼容性:如果窗口有透明效果,存储设备的处理会复杂化,因为需要正确混合背景。确保测试透明场景下的显示是否正确。

5. 定时器与滚动条:WM的辅助利器

除了上述三大主题,WM API中还有两组函数在构建动态界面时不可或缺:定时器和滚动条相关函数。

5.1 定时器:精准的“时间触发器”

GUI应用经常需要定时执行某些任务,如刷新数据、播放动画帧、处理超时。WM提供了基于消息的定时器机制,与窗口生命周期绑定,使用起来非常清晰。

创建与使用定时器:

static WM_HTIMER hTimer; // 定时器句柄 // 在窗口回调函数中 static void _cbMyWindow(WM_MESSAGE * pMsg) { switch (pMsg->MsgId) { case WM_CREATE: // 窗口创建时,启动一个1秒的单次定时器 hTimer = WM_CreateTimer(pMsg->hWin, // 接收消息的窗口 0, // 用户ID,用于区分多个定时器 1000, // 周期,1000毫秒 0); // 模式,保留为0 break; case WM_TIMER: // 定时器到期,收到WM_TIMER消息 if (pMsg->Data.v == hTimer) { // 通过Data.v传递定时器句柄 // 执行定时任务,例如更新一个计数器显示 UpdateCounter(); // 如果需要循环定时,可以重启定时器 WM_RestartTimer(hTimer, 1000); } break; case WM_DELETE: // 窗口删除前,务必删除定时器(虽然WM会自动清理,但显式删除是好习惯) WM_DeleteTimer(hTimer); break; default: WM_DefaultProc(pMsg); } }

关键点解析:

  • 关联性:定时器通过WM_CreateTimer与一个窗口句柄绑定。当该窗口被删除时,WM会自动删除所有与之关联的定时器,防止内存泄漏和野指针。这是一个非常重要的安全特性。
  • 消息驱动:定时器到期不会调用一个回调函数,而是向关联窗口发送一个WM_TIMER消息。你需要在窗口回调中处理此消息。pMsg->Data.v中包含了触发消息的定时器句柄,这在有多个定时器时用于区分。
  • 单次与循环WM_CreateTimer创建的是单次定时器。到期后,定时器对象依然存在但处于停止状态。要实现循环定时,必须在WM_TIMER消息处理中调用WM_RestartTimer
  • WM_RestartTimervsWM_CreateTimer:重启定时器比先删除再创建效率更高,因为它复用已有的定时器对象。

5.2 滚动条控制:获取与设置状态

对于可滚动的窗口(如LISTBOX,MULTIEDIT或自定义的滚动视图),WM提供了直接操作其关联滚动条的API。

获取滚动条句柄和状态:

WM_HWIN hListBox; // 假设这是一个LISTBOX窗口句柄 WM_HWIN hVScroll; int scrollPos; WM_SCROLL_STATE scrollState; // 获取垂直滚动条句柄 hVScroll = WM_GetScrollbarV(hListBox); if (hVScroll) { // 获取当前的滚动位置(滚动条拇指的偏移量) scrollPos = WM_GetScrollPosV(hListBox); printf("当前垂直滚动位置: %d\n", scrollPos); // 获取完整的滚动条状态(包含总项数、当前值、页大小) WM_GetScrollState(hVScroll, &scrollState); printf("总项数: %d, 当前值: %d, 页大小: %d\n", scrollState.NumItems, scrollState.v, scrollState.PageSize); }

设置滚动位置:

// 将列表滚动到第50项的位置 WM_SetScrollPosV(hListBox, 50); // 或者通过状态结构体设置 scrollState.v = 50; // 设置当前值 WM_SetScrollState(hVScroll, &scrollState);

应用场景:

  • 实现“回到顶部”功能WM_SetScrollPosV(hListBox, 0)
  • 同步多个视图的滚动:在一个视图中滚动时,获取其scrollPos,然后设置给另一个关联视图。
  • 保存和恢复用户界面状态:在应用退出前,保存关键滚动窗口的scrollPos;下次启动时恢复,提升用户体验。

注意事项

  1. WM_GetScrollbarH/VWM_GetScrollPosH/V的参数是窗口句柄(如hListBox),这个窗口是拥有滚动条的容器窗口
  2. WM_GetScrollStateWM_SetScrollState的参数是滚动条小部件本身的句柄(如hVScroll),需要通过WM_GetScrollbarV获取。
  3. 直接通过API设置滚动位置会触发窗口的WM_VSCROLLWM_HSCROLL消息,从而引发内容重绘。确保你的窗口回调能正确处理这些消息。

6. 综合实战:构建一个平滑的仪表盘界面

理论最终要服务于实践。让我们设想一个工业仪表盘应用,它需要:

  1. 一个可拖拽调整位置的实时数据卡片(使用运动支持)。
  2. 鼠标悬停在仪表刻度上时显示精确数值(使用工具提示)。
  3. 整个界面以60fps流畅刷新,无任何撕裂(使用多缓冲)。
  4. 一个每秒更新一次的历史趋势图(使用定时器)。
  5. 一个可滚动查看的报警日志列表(涉及滚动条)。

核心代码框架示意:

// 1. 初始化与全局设置 void MainTask(void) { GUI_Init(); WM_MULTIBUF_Enable(1); // 启用多缓冲以获得流畅体验 WM_MOTION_Enable(1); // 启用运动支持 WM_TOOLTIP_SetDefaultPeriod(WM_TOOLTIP_PI_FIRST, 500); WM_TOOLTIP_SetDefaultPeriod(WM_TOOLTIP_PI_SHOW, 3000); // 配置工具提示 // 2. 创建主窗口和控件 WM_HWIN hMainWin = CreateMainWindow(); WM_HWIN hDataCard = CreateDataCard(hMainWin); // 数据卡片窗口 WM_HWIN hTrendGraph = CreateTrendGraph(hMainWin); // 趋势图窗口 WM_HWIN hLogList = CreateLogList(hMainWin); // 日志列表窗口 WM_TOOLTIP_HANDLE hToolTip = CreateToolTipsForDials(hMainWin); // 为仪表创建工具提示 // 3. 为数据卡片启用拖拽运动 WM_MOTION_SetMoveable(hDataCard, WM_CF_MOTION_X | WM_CF_MOTION_Y, 1); // 注意:实际的拖拽开始/结束逻辑需要在 hDataCard 的 WM_TOUCH 消息回调中实现, // 通过 WM_MOTION_SetSpeed 来响应拖拽速度。 // 4. 为趋势图窗口启用存储设备,确保曲线绘制无闪烁 WM_EnableMemdev(hTrendGraph); // 5. 创建定时器,用于每秒更新趋势图 WM_HTIMER hUpdateTimer = WM_CreateTimer(hMainWin, 0, 1000, 0); // 6. 主循环 while(1) { GUI_Exec(); // 处理所有消息、重绘、动画 // 其他后台任务... } } // 主窗口回调函数 static void _cbMainWin(WM_MESSAGE * pMsg) { switch (pMsg->MsgId) { case WM_TIMER: if (pMsg->Data.v == hUpdateTimer) { UpdateTrendGraphData(); // 更新趋势图数据 WM_InvalidateWindow(hTrendGraph); // 使趋势图无效,触发重绘 WM_RestartTimer(hUpdateTimer, 1000); // 重启定时器 } break; case WM_NOTIFY_PARENT: // 处理来自子控件(如列表)的通知 if (pMsg->Data.v == WM_NOTIFICATION_RELEASED) { // 例如,处理列表项点击 int id = WM_GetId(pMsg->hWinSrc); if (id == ID_LIST_LOG) { int sel = LISTBOX_GetSel(pMsg->hWinSrc); // ... 处理选中的日志项 } } break; // ... 其他消息处理 default: WM_DefaultProc(pMsg); } }

这个例子展示了如何将WM的多个高级功能有机结合起来,构建一个响应迅速、视觉平滑、交互丰富的嵌入式GUI应用。每个模块各司其职,又通过WM的消息系统协同工作。

http://www.jsqmd.com/news/1049420/

相关文章:

  • WPScan 爆破 WordPress 后台实验报告(含Vulhub环境搭建)
  • 一城科创一城烟火,佳天下带你解锁完整广东 - 佳天下国旅
  • 无套路现款秒结,2026哈尔滨回收黄金口碑商家优选榜单 - 名奢变现站
  • 昆明旧金回收完整指南,2026 榜单盘点与线下门店实测汇总 - 讯息早知道
  • 西安GEO公司真实表现,一线观察揭示行业细节 - 速递信息
  • 筑影编辑器 房屋设计 建筑可视化设计工具 个人开发
  • 小众但惊喜:一位卡罗拉车主换用戴文润滑油8000公里的真实记录 - 技术实力派
  • 瑞祥商联卡回收平台闲置卡券这样处理省心又划算 - 京顺回收
  • Kimi 生成的数学试卷如何导出,AI 导出鸭针对性优化数理公式渲染,实测多类转换工具选出稳定导出方案
  • 深圳老旧小区窗户漏风漏音隔音改造 | 静华轩隔音窗 | 老推拉窗胶条老化、窗框缝隙、窗边渗水同步降噪密封整改,老城住宅低成本改造 - 维小达科技
  • 孟加拉语社交称谓系统与文化感知型语言模型
  • 2026昆明黄金回收S级严选店:奢响佳等三家获双资质+实时金价屏公示 - 商业快讯早知道
  • 2026年潍坊网站建设新趋势:企业如何抓住网络红利 - 速递信息
  • 2026 西安奢包回收 添价收鉴定不拆五金不损包身 全程在视线内操作 - 薛定谔的梨花猫
  • 西安正规黄金回收店推荐|2026年6月南稍门商圈七家门店实测 - 薛定谔的梨花猫
  • 2026深圳黄金回收流程详解逸程带你认识正规交易 - 逸程
  • 2026 年老牌木工厂 vs 新兴全案品牌:徐州整木定制谁更靠谱? - 速递信息
  • 2026 年厦门市厨卫屋顶防水修缮三家对比测评:吉修匠 99.8 分 - 吉修匠
  • 专业的深圳泰语企业培训 - 速递信息
  • 2026年国内知名防爆墙厂家排行 助力选靠谱供应商 - 速递信息
  • 20260618
  • 湛江全屋定制品牌口碑实测 本地业主真实评价排行 - 速递信息
  • HSTracker终极指南:macOS炉石传说玩家的完整卡组管理与对战辅助工具
  • TQVaultAE终极指南:如何轻松管理泰坦之旅无限装备仓库
  • 2026昆明黄金变现深度档案:对比15家门店,选出零隐形费用的5家口碑王 - 商业信息快查
  • 2026广州大额离婚维权:专精家事律师推荐,高胜诉律师事务所实测盘点 - 速递信息
  • 2026 年 6 月下旬最新官方正式辟谣|亨得利官方全网不实维保资讯澄清 + 正规网点公示 + 用户咨询答疑合集 - 亨得利官方维修中心
  • 2026成都局改装修新模式:闭口合同如何解决增项痛点 - 优家闲谈
  • 2026年新疆吐鲁番短线旅游导游安排和费用边界说明 - 盛世西域旅行
  • 2026 年亳州市厨卫屋顶防水修缮三家横向测评:吉修匠 99.8 分稳居榜首 - 吉修匠