1. 项目概述:为什么嵌入式GUI的对话框开发值得深究?
在嵌入式系统里做界面开发,和你在PC或者手机上写应用完全是两码事。资源受限、实时性要求高、没有操作系统或者只有轻量级的RTOS,这些限制让每一行代码、每一个像素都显得格外珍贵。对话框,作为用户交互的核心载体,其实现方式直接决定了应用的响应速度、内存占用和开发效率。很多新手一上来就照着例程堆控件,结果界面卡顿、逻辑混乱,后期维护更是噩梦。问题的根源往往在于没有理解对话框背后的运行机制。
emWin作为一款成熟且广泛应用的嵌入式GUI库,其对话框架构设计得非常精妙。它没有采用某些重量级框架那种“拖拽生成代码”的黑盒模式,而是提供了一套清晰、可控的底层API。这套API的核心思想是**“描述+回调”**:你用资源表静态描述界面长什么样(有哪些控件、放在哪),用回调函数动态定义界面怎么动(用户点了按钮该怎么办)。这种分离使得界面布局调整和业务逻辑修改可以相对独立,极大地提升了代码的模块化程度。
这次,我们就聚焦两个看似基础但至关重要的部分:作为对话框容器的 WINDOW控件 ,和开箱即用的 CALENDAR通用对话框 。我会带你从原理层拆解,再到一行行代码的实操,最后分享那些官方手册里不会写的“踩坑”经验。无论你是刚接触emWin,还是想优化现有的对话框代码,相信都能找到直接的参考。
2. 对话框的核心原理:消息、资源表与回调函数
在深入控件之前,必须把emWin对话框的“三驾马车”搞清楚:消息循环机制、资源表和回调函数。这是理解一切的基础。
2.1 消息循环:GUI如何知道用户做了什么?
emWin内部有一个窗口管理器(WM),它负责管理所有窗口(包括对话框和控件)的创建、销毁、绘制和用户输入。其核心是一个 消息驱动 模型。
你可以把它想象成一个邮局系统:
- 事件是信件 :触摸屏按下、定时器到期、控件状态改变等,都会被封装成“消息”(Message)。
- 窗口是收件人 :每个窗口(对话框、按钮、文本框)都有一个唯一的“句柄”(Handle),就像门牌号。
- 回调函数是收件人的处理流程 :当邮局(WM)把信件(消息)投递到对应地址(窗口句柄)后,就会调用该窗口注册的“回调函数”(Callback)来处理这封信。
对于对话框,最重要的消息有两个:
-
WM_INIT_DIALOG:对话框创建后、显示前发出的“初始化”消息。这是你设置控件初始状态的黄金时间,比如给编辑框设置默认文本、设置列表框的选项、禁用某个按钮等。 -
WM_NOTIFY_PARENT:子控件(比如按钮、列表框)在发生某些事件(如被按下、选择改变)时,向父窗口(对话框)发出的“通知”消息。这是你实现业务逻辑的主要入口。
2.2 资源表:静态的界面蓝图
资源表是一个
GUI_WIDGET_CREATE_INFO
类型的结构体数组。它不包含任何逻辑,只定义界面的静态结构:有什么控件、ID是什么、位置大小、初始风格等。
static const GUI_WIDGET_CREATE_INFO _aDialogCreate[] = {
// 类型 文本 ID X Y 宽 高 标志 扩展数据
{ FRAMEWIN_CreateIndirect, "设置", 0, 10, 10, 200, 250, FRAMEWIN_CF_MOVEABLE, 0 },
{ WINDOW_CreateIndirect, NULL, GUI_ID_WINDOW0, 5, 30, 190, 180, 0, 0 },
{ BUTTON_CreateIndirect, "确定", GUI_ID_OK, 50, 210, 60, 25, 0, 0 },
{ BUTTON_CreateIndirect, "取消", GUI_ID_CANCEL, 130, 210, 60, 25, 0, 0 },
{ TEXT_CreateIndirect, "日期:", 0, 20, 60, 40, 20, TEXT_CF_LEFT, 0 },
// ... 更多控件
};
关键字段解读:
-
创建函数
:如
WINDOW_CreateIndirect,决定了控件的类型。 -
ID
:控件的唯一标识符(
GUI_ID_OK是系统预定义的),在回调函数中通过WM_GetDialogItem获取控件句柄全靠它。 -
坐标与尺寸
:相对于其
父窗口
客户区的坐标。注意嵌套关系,比如上例中
TEXT控件的父窗口是WINDOW控件,所以它的Y坐标60是相对于WINDOW顶部计算的。 -
标志(Flags)
:控件的初始行为属性,比如
FRAMEWIN_CF_MOVEABLE让框架窗口可拖动。
实操心得:资源表的布局技巧 手动计算每个控件的坐标非常繁琐且难以维护。在实际项目中,我强烈建议使用SEGGER提供的 AppWizard 工具进行可视化布局设计,它可以直接生成资源表代码。如果必须手写,可以先在纸上或绘图软件里画个草图,定义好间距和控件标准尺寸(如按钮宽60高25),然后用宏或常量来计算坐标,这样后期调整布局会轻松很多。
2.3 回调函数:动态的业务逻辑中枢
回调函数是对话框的灵魂。它是一个
switch-case
结构,根据收到的不同消息ID来执行相应的操作。
static void _cbDialog(WM_MESSAGE * pMsg) {
WM_HWIN hItem;
WM_HWIN hDlg = pMsg->hWin; // 获取当前对话框的句柄
int Id, NCode;
switch (pMsg->MsgId) {
case WM_INIT_DIALOG:
// 初始化所有控件
hItem = WM_GetDialogItem(hDlg, GUI_ID_WINDOW0);
WINDOW_SetBkColor(hItem, GUI_DARKGRAY); // 设置WINDOW控件背景色
// ... 其他控件初始化
break;
case WM_NOTIFY_PARENT:
Id = WM_GetId(pMsg->hWinSrc); // 获取触发事件的控件ID
NCode = pMsg->Data.v; // 获取通知代码
switch (NCode) {
case WM_NOTIFICATION_RELEASED: // 按钮释放事件
if (Id == GUI_ID_OK) {
// 点击“确定”后的逻辑
GUI_EndDialog(hDlg, 0); // 关闭对话框,返回0
} else if (Id == GUI_ID_CANCEL) {
// 点击“取消”后的逻辑
GUI_EndDialog(hDlg, 1); // 关闭对话框,返回1
}
break;
// 处理其他通知,如WM_NOTIFICATION_VALUE_CHANGED(滑动条值改变)
}
break;
default:
// 其他未处理的消息交给默认窗口过程
WM_DefaultProc(pMsg);
}
}
关键点解析:
-
WM_DefaultProc:这是 必须调用 的。它保证了那些你不关心的消息(如重绘、触摸屏按下)能得到默认处理,否则界面会无法正常响应。 -
GUI_EndDialog:这是关闭对话框并使其返回的唯一正确方式。第二个参数是返回值,会被GUI_ExecDialogBox函数接收,常用于判断用户点击了“确定”还是“取消”。
3. WINDOW控件:被低估的对话框容器
很多开发者习惯直接用
FRAMEWIN
作为对话框的根窗口,这没错。但
WINDOW
控件在特定场景下是更优的选择。
3.1 WINDOW控件的本质与适用场景
WINDOW
控件在emWin中被描述为“像一个没有边框和标题栏的框架窗口”。这句话点明了它的核心:
一个纯粹的容器
。
-
与FRAMEWIN对比
:
特性 FRAMEWIN WINDOW 外观 有标题栏、边框、可关闭按钮 无边框、无标题栏,纯背景 用途 作为应用程序主窗口或独立对话框 作为对话框内部的背景面板或子容器 内存开销 稍高(需要维护标题栏等结构) 更低 输入焦点 可以拥有(通过标题栏) 不能获得输入焦点
什么时候用WINDOW?
-
嵌入式设备弹窗
:很多设备需要弹出一些没有标题栏的提示框、菜单或设置面板,
WINDOW非常适合。 -
复杂对话框的子区域划分
:在一个大的
FRAMEWIN对话框内,用多个WINDOW控件划分出不同的功能区域(如“网络设置”、“显示设置”),每个区域设置不同的背景色,逻辑清晰。 -
全屏应用界面
:在一些简单的全屏应用中,直接用一个全屏的
WINDOW作为根容器,上面放置其他控件,比用FRAMEWIN更节省资源。
3.2 WINDOW控件的创建与配置详解
创建
WINDOW
控件主要使用
WINDOW_CreateEx
函数,其参数需要仔细理解:
WM_HWIN hWin;
hWin = WINDOW_CreateEx(50, // x0: 在父窗口中的X坐标
50, // y0: 在父窗口中的Y坐标
180, // xSize: 宽度
120, // ySize: 高度
hParent, // hParent: 父窗口句柄,0表示桌面
WM_CF_SHOW | WM_CF_HASTRANS, // WinFlags: 窗口标志
0, // ExFlags: 保留,填0
GUI_ID_WINDOW0, // Id: 控件ID
_cbWindowCallback); // cb: 回调函数(可为NULL)
关键参数解析:
-
WinFlags: 这是核心。-
WM_CF_SHOW: 创建后立即显示。 绝大多数情况都要加 ,否则你得自己调用WM_ShowWindow。 -
WM_CF_HASTRANS: 启用透明效果。如果WINDOW控件有透明部分,需要设置此标志,并确保其父窗口能正确绘制被遮挡的内容。 注意 :启用透明会显著增加绘制开销。
-
-
cb:WINDOW控件可以有自己的回调函数。这允许它处理自己的消息(比如在它上面直接绘制),但对于单纯作为容器的情况,通常设为NULL,消息会传递给父对话框处理。
配置背景色:
默认背景色是浅灰色(
0xC0C0C0
)。修改它有两个层面:
-
全局默认值
:
WINDOW_SetDefaultBkColor(GUI_BLUE)。这之后创建的所有WINDOW控件默认都是蓝色。 -
单个控件
:
WINDOW_SetBkColor(hWin, GUI_GREEN)。这只影响指定的控件。
踩坑记录:WINDOW的绘制顺序问题
WINDOW作为容器,其 绘制顺序在子控件之前 。这意味着,如果你在WINDOW的回调函数里处理WM_PAINT消息并绘制一些背景图案,这些图案会被其子控件覆盖。正确的做法是,如果背景需要复杂绘制,要么在WM_INIT_DIALOG里画好,要么确保子控件有透明背景。更常见的做法是直接设置纯色背景,简单高效。
3.3 实战:构建一个基于WINDOW的简易设置对话框
假设我们要做一个设备亮度调节对话框,它没有标题栏,内部有一个文本标签、一个滑动条和一个确定按钮。
// 1. 定义资源表
static const GUI_WIDGET_CREATE_INFO _aBrightnessDialog[] = {
{ WINDOW_CreateIndirect, NULL, GUI_ID_WINDOW0, 0, 0, 240, 135, 0, 0 }, // 全屏WINDOW作为背景
{ TEXT_CreateIndirect, "亮度调节", 0, 90, 10, 60, 20, TEXT_CF_HCENTER, 0 },
{ SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER0, 40, 50, 160, 30, 0, 0 },
{ BUTTON_CreateIndirect, "应用", GUI_ID_OK, 85, 95, 70, 25, 0, 0 },
};
// 2. 对话框回调函数
static void _cbBrightnessDialog(WM_MESSAGE * pMsg) {
WM_HWIN hItem, hDlg = pMsg->hWin;
static int Brightness = 50; // 静态变量保存亮度值
switch (pMsg->MsgId) {
case WM_INIT_DIALOG:
// 初始化WINDOW背景色
hItem = WM_GetDialogItem(hDlg, GUI_ID_WINDOW0);
WINDOW_SetBkColor(hItem, GUI_DARKGRAY);
// 初始化滑动条
hItem = WM_GetDialogItem(hDlg, GUI_ID_SLIDER0);
SLIDER_SetRange(hItem, 0, 100); // 范围0-100
SLIDER_SetValue(hItem, Brightness); // 设为当前亮度
SLIDER_SetNumTicks(hItem, 10); // 设置10个刻度
break;
case WM_NOTIFY_PARENT:
switch (pMsg->Data.v) {
case WM_NOTIFICATION_RELEASED:
if (WM_GetId(pMsg->hWinSrc) == GUI_ID_OK) {
// 获取当前滑动条值并保存
hItem = WM_GetDialogItem(hDlg, GUI_ID_SLIDER0);
Brightness = SLIDER_GetValue(hItem);
// 这里可以调用硬件API设置实际亮度
// SetBacklight(Brightness);
GUI_EndDialog(hDlg, 0);
}
break;
case WM_NOTIFICATION_VALUE_CHANGED:
// 滑动条值实时改变时,可以在这里做预览更新(如改变一个模拟亮度条)
// 注意:频繁操作避免耗时过长
break;
}
break;
default:
WM_DefaultProc(pMsg);
}
}
// 3. 创建并执行对话框(阻塞式)
void ShowBrightnessDialog(void) {
GUI_ExecDialogBox(_aBrightnessDialog,
GUI_COUNTOF(_aBrightnessDialog),
_cbBrightnessDialog,
0, 0, 0); // 父窗口为0(桌面),坐标(0,0)
}
这个例子展示了
WINDOW
作为无边框对话框容器的典型用法。它简洁、直接,适合嵌入式设备的系统级设置。
4. CALENDAR通用对话框:开箱即用与深度定制
通用对话框是emWin提供的“轮子”,
CALENDAR
是其中最常用的之一。它封装了一个完整的日期选择器,但绝不是黑盒,提供了丰富的API供你定制。
4.1 快速集成:三步创建一个日历对话框
使用
CALENDAR
通用对话框最简单,因为它不需要你定义资源表和完整的回调函数。
#include "DIALOG.h"
void OpenCalendarDialog(void) {
CALENDAR_DATE SelectedDate;
int Result;
// 步骤1: 创建日历对话框(非阻塞式)
WM_HWIN hCalendar;
hCalendar = CALENDAR_Create(0, // 父窗口为桌面
50, 50, // 屏幕坐标(50,50)
2023, 10, 27, // 初始日期:2023年10月27日
1, // 每周第一天为周日 (0=周六, 1=周日, ... 6=周五)
GUI_ID_CALENDAR0, // 对话框ID
0); // 标志位
// 步骤2: 执行对话框(阻塞式,等待用户操作)
Result = GUI_ExecCreatedDialog(hCalendar);
// 步骤3: 获取用户选择(如果用户不是取消操作)
if (Result == 0) { // 通常0表示确认操作(如点击了对话框自带的OK按钮,但CALENDAR自带对话框需自定义)
// 更通用的做法是在对话框关闭前,通过消息或全局变量获取日期
// 这里演示直接获取当前选中日期
CALENDAR_GetSel(hCalendar, &SelectedDate);
printf("Selected Date: %04d-%02d-%02d\n",
SelectedDate.Year,
SelectedDate.Month,
SelectedDate.Day);
}
// 注意:GUI_ExecCreatedDialog返回后,对话框已被删除,hCalendar句柄失效。
}
关键点:
-
CALENDAR_Create创建的是 非阻塞 对话框,它立即返回句柄,但对话框可能还未显示(需要WM_Exec)。 -
GUI_ExecCreatedDialog是 阻塞 函数,它会接管消息循环,直到对话框被GUI_EndDialog关闭。 -
重要限制
:基础的
CALENDAR_Create创建的对话框没有“确定/取消”按钮!它只是一个日历视图。用户选择日期后,你需要通过其他方式(如监听WM_NOTIFY_PARENT消息)来触发关闭和获取数据。
4.2 深度定制:打造符合产品风格的日历
默认的
CALENDAR
样式可能很简陋。emWin提供了一系列
CALENDAR_SetDefault...
函数,用于在创建
之前
设置全局默认样式。
// 在程序初始化阶段调用,影响之后创建的所有CALENDAR对话框
void InitCalendarStyle(void) {
// 1. 设置颜色
CALENDAR_SetDefaultColor(CALENDAR_CI_WEEKEND, GUI_RED); // 周末文字红色
CALENDAR_SetDefaultColor(CALENDAR_CI_SEL, GUI_BLUE); // 选中项背景蓝色
CALENDAR_SetDefaultColor(CALENDAR_CI_FRAME, GUI_DARKGRAY); // 当前日期框灰色
CALENDAR_SetDefaultBkColor(CALENDAR_CI_HEADER, GUI_LIGHTGRAY); // 标题区背景浅灰
// 2. 设置字体
CALENDAR_SetDefaultFont(CALENDAR_FI_HEADER, &GUI_Font20B_ASCII); // 月份/年份用大字体
CALENDAR_SetDefaultFont(CALENDAR_FI_CONTENT, &GUI_Font16_ASCII); // 日期用小字体
// 3. 设置尺寸
CALENDAR_SetDefaultSize(CALENDAR_SI_HEADER, 30); // 标题栏高度30像素
CALENDAR_SetDefaultSize(CALENDAR_SI_CELL_X, 30); // 日期单元格宽30
CALENDAR_SetDefaultSize(CALENDAR_SI_CELL_Y, 25); // 日期单元格高25
// 据此计算整个对话框大小:宽=7*30=210,高=7*25+30=205
// 4. 自定义星期和月份文本(支持本地化)
static const char * _apDays[] = {"日", "一", "二", "三", "四", "五", "六"};
static const char * _apMonths[] = {"一月","二月","三月","四月","五月","六月",
"七月","八月","九月","十月","十一月","十二月"};
CALENDAR_SetDefaultDays(_apDays);
CALENDAR_SetDefaultMonths(_apMonths);
}
样式定制原理
:这些
SetDefault
函数修改的是emWin库内部的静态变量。因此,
必须在创建任何
CALENDAR
对话框之前调用
,否则不生效。通常放在
GUI_Init()
之后、主循环之前。
4.3 实战:封装一个带确认功能的日历选择器
单纯一个日历视图不实用。我们需要将其嵌入到一个标准的对话框中,并加上操作按钮。
// 资源表:一个FRAMEWIN包含CALENDAR和两个按钮
static const GUI_WIDGET_CREATE_INFO _aDatePickerDialog[] = {
{ FRAMEWIN_CreateIndirect, "选择日期", 0, 10, 10, 230, 250, FRAMEWIN_CF_MOVEABLE, 0 },
{ CALENDAR_CreateIndirect, NULL, GUI_ID_CALENDAR0, 5, 30, 220, 180, 0, 0 }, // CALENDAR作为控件嵌入
{ BUTTON_CreateIndirect, "确定", GUI_ID_OK, 40, 215, 70, 25, 0, 0 },
{ BUTTON_CreateIndirect, "取消", GUI_ID_CANCEL, 120, 215, 70, 25, 0, 0 },
};
static CALENDAR_DATE g_SelectedDate; // 全局或静态变量存储最终选择
static void _cbDatePickerDialog(WM_MESSAGE * pMsg) {
WM_HWIN hItem, hDlg = pMsg->hWin;
int Id, NCode;
switch (pMsg->MsgId) {
case WM_INIT_DIALOG:
// 初始化日历为今天
{
CALENDAR_DATE Today = {2023, 10, 27}; // 应替换为获取系统时间的函数
hItem = WM_GetDialogItem(hDlg, GUI_ID_CALENDAR0);
CALENDAR_SetDate(hItem, &Today);
CALENDAR_SetSel(hItem, &Today);
g_SelectedDate = Today; // 初始化选择
}
break;
case WM_NOTIFY_PARENT:
Id = WM_GetId(pMsg->hWinSrc);
NCode = pMsg->Data.v;
switch (NCode) {
case WM_NOTIFICATION_RELEASED:
if (Id == GUI_ID_OK) {
// 点击确定,获取当前选中日期
hItem = WM_GetDialogItem(hDlg, GUI_ID_CALENDAR0);
CALENDAR_GetSel(hItem, &g_SelectedDate);
GUI_EndDialog(hDlg, 0); // 返回0表示确认
} else if (Id == GUI_ID_CANCEL) {
GUI_EndDialog(hDlg, 1); // 返回1表示取消
}
break;
case WM_NOTIFICATION_SEL_CHANGED:
// 日历选择发生变化时,可以实时更新g_SelectedDate或做其他反馈
if (Id == GUI_ID_CALENDAR0) {
hItem = WM_GetDialogItem(hDlg, GUI_ID_CALENDAR0);
CALENDAR_GetSel(hItem, &g_SelectedDate);
// 例如:在对话框标题显示当前选中日期
// char buf[32];
// sprintf(buf, "选择日期: %04d-%02d-%02d", g_SelectedDate.Year, g_SelectedDate.Month, g_SelectedDate.Day);
// FRAMEWIN_SetText(hDlg, buf);
}
break;
}
break;
default:
WM_DefaultProc(pMsg);
}
}
// 对外接口函数
int PickDate(CALENDAR_DATE * pDate) {
int result;
result = GUI_ExecDialogBox(_aDatePickerDialog,
GUI_COUNTOF(_aDatePickerDialog),
_cbDatePickerDialog,
0, 0, 0);
if (result == 0) { // 用户点击了确定
*pDate = g_SelectedDate;
return 0; // 成功
}
return -1; // 用户取消
}
这个实现才是一个真正可用的日期选择器。它把
CALENDAR
当作一个普通控件嵌入到标准对话框中,逻辑完整,体验良好。
5. 常见问题与排查技巧实录
在实际开发中,你会遇到各种各样的问题。下面是我总结的一些典型坑点和解决方法。
5.1 对话框不显示或显示异常
-
现象 :调用
GUI_ExecDialogBox后什么都没出现。-
检查1:内存分配
。确保在调用
GUI_Init()时分配了足够的存储设备(Memory Device)和窗口管理器内存。对话框创建失败往往是因为内存不足。 - 检查2:坐标和尺寸 。确认对话框的坐标和尺寸没有超出物理显示屏范围,或者父窗口的客户区范围。
-
检查3:
WM_CF_SHOW标志 。在资源表中创建控件或使用CreateEx时,确保包含了WM_CF_SHOW标志,或者后续手动调用WM_ShowWindow。 -
检查4:消息循环
。对于非阻塞对话框(
GUI_CreateDialogBox),创建后必须保证WM_Exec()或GUI_Exec()被定期调用,否则界面无法刷新。
-
检查1:内存分配
。确保在调用
-
现象 :对话框背景是黑色或花屏。
-
原因
:父窗口或
WINDOW控件没有正确绘制背景。 -
解决
:对于
WINDOW控件,确保设置了背景色(WINDOW_SetBkColor)。对于自定义的回调函数,如果在WM_PAINT中绘制,必须调用WM_DefaultProc,或者自己处理完绘制后调用WM_DefaultProc。
-
原因
:父窗口或
5.2 控件无法操作或消息无响应
-
现象
:点击按钮没反应,键盘操作无效。
-
检查1:输入焦点
。
WINDOW控件本身不能获得焦点。确保可操作的控件(如BUTTON,EDIT)被正确创建且未被禁用(WM_DisableWindow)。使用GUI_KEY_TAB可以在对话框内切换焦点。 -
检查2:回调函数中的
WM_DefaultProc。这是最常见的原因。你的对话框回调函数必须在default分支中调用WM_DefaultProc(pMsg),否则基础的消息(如触摸屏坐标转换、焦点切换)将无法处理。 -
检查3:消息传递路径
。子控件的
WM_NOTIFY_PARENT消息是发送给其父窗口的。如果你的对话框结构复杂(例如FRAMEWIN->WINDOW->BUTTON),那么按钮的通知是发给WINDOW的。你需要确保WINDOW的回调函数能正确处理或转发这些消息到顶层的对话框回调函数。
-
检查1:输入焦点
。
5.3 性能优化与内存管理
-
问题 :对话框弹出/关闭时感觉卡顿。
-
优化1:避免在
WM_INIT_DIALOG中执行耗时操作 。这里只做必要的控件状态初始化。如果需要从Flash或外部设备加载大量数据,考虑使用状态机,在对话框显示后异步加载。 -
优化2:使用存储设备
。对于复杂的对话框,可以在创建时使用
WM_CF_MEMDEV标志,为整个窗口启用内存设备。这会将窗口绘制到内存中再一次性刷屏,有效消除闪烁,但会消耗更多RAM。 -
优化3:精简资源表
。移除不可见的控件。如果控件很多,考虑使用
WIDGET_SetEffect减少绘制效果。
-
优化1:避免在
-
问题 :关闭对话框后,内存似乎没有释放。
-
原理
:
GUI_EndDialog会删除对话框及其所有子窗口,并释放emWin为它们分配的内存(主要是窗口对象本身)。但是,你在回调函数中通过malloc或类似方式分配的 应用层数据 不会被自动释放。 -
解决
:在对话框回调函数中处理
WM_DELETE消息(注意不是WM_DESTROY),在这里释放你申请的资源。
case WM_DELETE: if (pMyData) { GUI_ALLOC_Free(pMyData); // 使用emWin的内存池或你自己的free pMyData = NULL; } break; -
原理
:
5.4 CALENDAR控件的特定问题
-
现象 :
CALENDAR显示乱码或英文。- 原因 :没有设置中文字体或自定义的星期/月份字符串。
-
解决
:调用
CALENDAR_SetDefaultFont设置支持中文的字体(如&GUI_Font16_1)。调用CALENDAR_SetDefaultDays和CALENDAR_SetDefaultMonths设置中文字符串数组。
-
现象 :通过
CALENDAR_GetSel获取的日期不对。-
检查
:
CALENDAR内部维护两个日期:“当前日期”和“选中日期”。CALENDAR_SetDate设置的是日历中间显示的那个日期(通常是今天),CALENDAR_SetSel设置的是高亮选中的日期。初始化时,如果你只调用了CALENDAR_SetDate,那么选中日期可能还是默认值(如1号)。 最佳实践是初始化时同时调用CALENDAR_SetDate和CALENDAR_SetSel,传入同一个日期。
-
检查
:
-
现象 :
CALENDAR对话框尺寸计算不对,显示不全。-
计算
:对话框总宽度 =
7 * CALENDAR_SI_CELL_X。总高度 =7 * CALENDAR_SI_CELL_Y + CALENDAR_SI_HEADER。在创建CALENDAR控件(无论是通过CALENDAR_Create还是资源表)时,必须确保分配给它的窗口尺寸大于或等于这个计算值,否则内容会被裁剪。
-
计算
:对话框总宽度 =

368


被折叠的 条评论
为什么被折叠?



