使用GUI Guider工具开发嵌入式GUI应用(6)-切换多screen换场景
本节将展示使用GUI Guider实现切换显示页面功能。
这里设计的用例是:
- 创建3张页面,
screen_0
,screen_1
和screen_2
。 - 分别在每个页面上中放置一个Label(最简单的显示组件),分别填入不同的字符串,例如
Welcome
,YOU
,Hello
等。 - 为每个页面创建触发事件,配置GUI Guider让3个页面循环切换。当上一个页面载入完成后,切换页面开始载入下一个页面,循环往复。
在GUI Guider编辑区的左上角,有页面管理窗口,鼠标单击对应的加号"+"按钮,就可以新建页面。如果要改新建页面的名字,不能在页面管理窗口中直接重命名,此时可在GUI Guider编辑区右侧的“属性设置”标签页改变“名字”中的内容。为了减少生成不必要的键盘组件,记得要把“是否显示键盘”的选项关闭。如图x所示。
然后,在“事件添加”标签页中,创建一个事件:
- 指定触发方式为“Loaded”,对应当载入本页面中所有的显示内容后,触发该事件
- 指定目标对象为“screen_1”,对应当该事件发生后,对页面screen_1进行操作
- 指定动作为“加载页面”,及加载screen_1的页面。这里还可以选择加载页面的速度为1000ms内启动加载,以及加载新的页面后停留多久1000ms,甚至还可以选择加载的特效为“move left”,即从右向左移入。
对新建的3个页面都是如此配置,即screen_0的载入完成后加载screen_1,screen_1加载完成后加载screen_2,screen_2加载完成后加载screen_0,形成循环。之后,就可以生成代码并仿真了。
在实际调试到这里的时候,发现了当前版本GUI Guider的一个Bug:生成C代码后使用模拟器运行,切换到screen_1就卡住了,不会继续切换screen_2。但是使用MicroPython的模拟器运行配置好的GUI Guider工程就可以正常切换。经过对比源码以及调试过程后发现,生成C代码中,确定最终能产生触发信号的一个判定条件未被合理使用,该排判定条件始终不能被满足,因此无法正确执行到预设的触发事件的语句。但在Python代码中,就避开了这个判断,从而能够正常切换页面。一个临时的解法,是人工改动GUI Guider生成events_init.c
文件源码,将其中的d->prev_src == NULL
这个判断条件屏蔽掉。见源码如下:
static void screen_0_event_handler(lv_event_t *e)
{lv_event_code_t code = lv_event_get_code(e);switch (code){case LV_EVENT_SCREEN_LOADED:{lv_obj_t * act_scr = lv_scr_act();lv_disp_t * d = lv_obj_get_disp(act_scr);//if (d->prev_scr == NULL && (d->scr_to_load == NULL || d->scr_to_load == act_scr))if (d->prev_scr == NULL || (d->scr_to_load == NULL || d->scr_to_load == act_scr)){if (guider_ui.screen_1_del == true)setup_scr_screen_1(&guider_ui);lv_scr_load_anim(guider_ui.screen_1, LV_SCR_LOAD_ANIM_MOVE_LEFT, 2500, 500, false);guider_ui.screen_0_del = false;}}break;default:break;}
}void events_init_screen_0(lv_ui *ui)
{lv_obj_add_event_cb(ui->screen_0, screen_0_event_handler, LV_EVENT_ALL, ui);
}static void screen_1_event_handler(lv_event_t *e)
{lv_event_code_t code = lv_event_get_code(e);switch (code){case LV_EVENT_SCREEN_LOADED:{lv_obj_t * act_scr = lv_scr_act();lv_disp_t * d = lv_obj_get_disp(act_scr);//if (d->prev_scr == NULL && (d->scr_to_load == NULL || d->scr_to_load == act_scr))if (d->prev_scr == NULL || (d->scr_to_load == NULL || d->scr_to_load == act_scr)){if (guider_ui.screen_2_del == true)setup_scr_screen_2(&guider_ui);lv_scr_load_anim(guider_ui.screen_2, LV_SCR_LOAD_ANIM_MOVE_LEFT, 2500, 500, false);guider_ui.screen_1_del = false;}}break;default:break;}
}void events_init_screen_1(lv_ui *ui)
{lv_obj_add_event_cb(ui->screen_1, screen_1_event_handler, LV_EVENT_ALL, ui);
}static void screen_2_event_handler(lv_event_t *e)
{lv_event_code_t code = lv_event_get_code(e);switch (code){case LV_EVENT_SCREEN_LOADED:{lv_obj_t * act_scr = lv_scr_act();lv_disp_t * d = lv_obj_get_disp(act_scr);//if (d->prev_scr == NULL && (d->scr_to_load == NULL || d->scr_to_load == act_scr))if (d->prev_scr == NULL || (d->scr_to_load == NULL || d->scr_to_load == act_scr)){if (guider_ui.screen_0_del == true)setup_scr_screen_0(&guider_ui);lv_scr_load_anim(guider_ui.screen_0, LV_SCR_LOAD_ANIM_MOVE_LEFT, 2500, 500, false);guider_ui.screen_2_del = false;}}break;default:break;}
}void events_init_screen_2(lv_ui *ui)
{lv_obj_add_event_cb(ui->screen_2, screen_2_event_handler, LV_EVENT_ALL, ui);
}
从源码中可以看到,在检测screen_0对象事件的处理函数screen_0_event_handler()
中,当检测到LV_EVENT_SCREEN_LOADED
事件后,才有机会执行载入下一个页面的操作函数lv_scr_load_anim()
。但在执行lv_scr_load_anim()
函数之前,需要满足3个条件:
d->prev_scr == NULL
表示前一页是否为没有d->scr_to_load == NULL
表示正在显示的页是否为没有d->scr_to_load == act_scr
表示正在显示的页是否为当前页
源码中已经注释掉的判定条件要求必须没有上一页,这个判定条件仅有首页screen_0才能满足,到screen_1页面载入完成后,不能满足前一页面为没有的条件,因此无法继续执行载入screen_2页面的功能。一种直接的解法,就是屏蔽掉这个没有上页的判定条件,经仿真和上机调试后,验证可行。
最初设计这个没有前页的判定条件是有意义的。根据原作者的解释,此处的设计是为了避免在前一个页面中的事件未执行完毕时提前载入下一个页面,这可能在多线程环境下发挥更大的作用,但这需要LVGL内核实现一个机制:为当前页面中的所有事件挂一个信号量,只有当所有事件都执行完毕,所有的信号量都被归还后,由LVGL将当前页记录的上一页清空,从而满足此处设计的判定条件。不过,当前版本的LVGL内核并未实现这样的机制。所以为了能确保让当前的程序能够工作,还需要人为调整GUI Guider生成后的代码。特别注意,event_init.c
这个文件是由GUI Guider自动生成的,每次生成代码都会覆盖,因此人为改动之后,如果再次执行自动生成代码的过程,仍需要再改一次。
调好代码之后,由于GUI Guider每次使用C程序的模拟器运行之前都会重新生成代码,覆盖人工调整源码,因此无法直接模拟正常程序的执行过程,但下载到单片机上,可以看到预期效果。如图x所示。
(未完待续。。。)