嵌入式GUI FTK设计与实现-主循环
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli@gmail.com>
带图形用户界面(GUI)的应用程序和传统的批处理程序是不同的:
* 批处理程序是一步一步的执行,直到完成任务为止,完成任务后程序立即退出。
* 图形用户界面应用程序则是事件驱动的,它等待事件发生,然后处理事件,如此循环,直到用户要求退出为止。
两种执行模型如下图所示:
通常我们把等待事件/处理事件的循环称为主循环(MainLoop),主循环是GUI应用程序必要组件之一,FTK当然也离开不主循环 (MainLoop)。大多数嵌入式GUI都采用了Windows类似的主循环:
while(GetMessage(&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
我不太喜欢这种主循环,主要原因有两点:
* 它看似简洁,但内部实现并不简洁。它从当前线程的队列里获取消息,然后处理消息。这些消息从来哪里的?当然是由其它线程传递给当前线程的,这就意味着 GUI需要多线程的支持。而FTK不想引入多线程来折磨自己,至少在GUI部分是不想的。
* 这是面向过程的处理方式。消息是一个对象,按照面向对象的思想来说,对象的数据和行为应该绑定在一起。而这里的消息是纯粹的数据,所有消息都由目标窗口的消息处理函数来处理的。FTK希望每类消息都有自己的处理函数,而不是全部由窗口来处理。
FTK采用了GTK类似的主循环:
ftk_run();
它看起来更简洁,内部实现也不需要多线程的支持。这里采用了POSA(面向模式的软件架构)中的Reactor模式,它的主要好处有:
* 用单线程处理多个事件源。
* 增加新事件源时不会影响事件分发的框架。
整个主循环由下列组件构成:
FtkSource 是一个接口,是所有事件源的抽象,它要求事件源实现下列函数:
* ftk_source_get_fd 用来获取文件描述符,当然这个文件描述符不一定是真正的文件描述符,只要是MainLoop能挂在上面等待的句柄(Handle)即可。
* ftk_source_check 用来检查事件源要求等待的时间。-1表示不关心等待时间。0表示要马就有事件发生,正数表示在指定的时间内将有事件发生。
* ftk_source_dispatch 用来处理事件,每个事件源都有自己的处理函数,而不是全部耦合到窗口的处理函数中。
FtkSourcesManager负责管理所有事件源。主要提供下列函数:
* ftk_sources_manager_add 增加一个事件源。
* ftk_sources_manager_remove 删除一个事件源。
* ftk_sources_manager_get_count 获取事件源总数。
* ftk_sources_manager_get 获取指定索引的事件源。
FtkMainLoop负责循环的等待事件发生,然后调用事件源的处理函数去处理。主要提供下列函数:
* ftk_main_loop_run 启动主循环
* ftk_main_loop_quit 退出主循环
* ftk_main_loop_add_source 增加一个事件源
* ftk_main_loop_remove_source 删除一个事件源
FtkMainLoop提供了add_source和remove_source两个函数对 FtkSourcesManager相应函数进行包装,这里包装不是简单的调用FtkSourcesManager的函数,而是发送一个事件:
- Ret ftk_main_loop_add_source(FtkMainLoop* thiz, FtkSource* source)
- {
- FtkEvent event = {0};
- return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL);
- event.type = FTK_EVT_ADD_SOURCE;
- event.u.extra = source;
- return ftk_source_queue_event(ftk_primary_source(), &event);
- }
- Ret ftk_main_loop_remove_source(FtkMainLoop* thiz, FtkSource* source)
- {
- FtkEvent event = {0};
- return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL);
- event.type = FTK_EVT_REMOVE_SOURCE;
- event.u.extra = source;
- return ftk_source_queue_event(ftk_primary_source(), &event);
- }
- 这个事件由Primary Source进行处理:
- static Ret ftk_source_primary_dispatch(FtkSource* thiz)
- {
- ...
- switch(event.type)
- {
- case FTK_EVT_ADD_SOURCE:
- {
- ftk_sources_manager_add(ftk_default_sources_manager(), event.u.extra);
- break;
- }
- case FTK_EVT_REMOVE_SOURCE:
- {
- ftk_sources_manager_remove(ftk_default_sources_manager(), event.u.extra);
- break;
- }
- }
- ...
- }
Ret ftk_main_loop_add_source(FtkMainLoop* thiz, FtkSource* source){ FtkEvent event = {0}; return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL); event.type = FTK_EVT_ADD_SOURCE; event.u.extra = source; return ftk_source_queue_event(ftk_primary_source(), &event);} Ret ftk_main_loop_remove_source(FtkMainLoop* thiz, FtkSource* source){ FtkEvent event = {0}; return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL); event.type = FTK_EVT_REMOVE_SOURCE; event.u.extra = source; return ftk_source_queue_event(ftk_primary_source(), &event);}这个事件由Primary Source进行处理:static Ret ftk_source_primary_dispatch(FtkSource* thiz){ ... switch(event.type) { case FTK_EVT_ADD_SOURCE: { ftk_sources_manager_add(ftk_default_sources_manager(), event.u.extra); break; } case FTK_EVT_REMOVE_SOURCE: { ftk_sources_manager_remove(ftk_default_sources_manager(), event.u.extra); break; } } ...}
为什么要多此一举呢?原因这样的:FTK是单线程的,GUI线程只负责用户界面的管理,由后台工作的线程负责长时间的操作。但是后台工作的线程经常需要更新用户界面,比如下载网络数据的线程要更新下载进度界面。FTK需要提供一种机制,让后台线程来更新用户界面但又不需要引入互斥机制。这可以通过 idle来串行化对GUI的操作,后台线程要更新GUI时,就增加一个idle source,后台线程不能直接调用ftk_sources_manager_add,那样需要互斥机制,而且也不能唤醒主循环去处理这个idle。所以它通过Primary Source的管道发送一个事件,这个事件会唤醒睡眠中的主循环,然后调用Primary Source分发函数去处理事件。
现在我们来看ftk_main_loop_run的实现,ftk_main_loop_run的实现是平台相关的,对于支持select的平台,用 select是最好的方法。下面是基于select的实现:
1.找出最小等待时间和文件描述符
- for(i = 0; i < ftk_sources_manager_get_count(thiz->sources_manager); i++)
- {
- source = ftk_sources_manager_get(thiz->sources_manager, i);
- if((fd = ftk_source_get_fd(source)) >= 0)
- {
- FD_SET(fd, &thiz->fdset);
- if(mfd < fd) mfd = fd;
- n++;
- }
- source_wait_time = ftk_source_check(source);
- if(source_wait_time >= 0 && source_wait_time < wait_time)
- {
- wait_time = source_wait_time;
- }
- }
for(i = 0; i < ftk_sources_manager_get_count(thiz->sources_manager); i++) { source = ftk_sources_manager_get(thiz->sources_manager, i); if((fd = ftk_source_get_fd(source)) >= 0) { FD_SET(fd, &thiz->fdset); if(mfd < fd) mfd = fd; n++; } source_wait_time = ftk_source_check(source); if(source_wait_time >= 0 && source_wait_time < wait_time) { wait_time = source_wait_time; } }
这里遍历所有source,找出一个最小的等待时间和要等待的文件描述符。
2. 等待事件发生
- tv.tv_sec = wait_time/1000;
- tv.tv_usec = (wait_time%1000) * 1000;
- ret = select(mfd + 1, &thiz->fdset, NULL, NULL, &tv);
tv.tv_sec = wait_time/1000; tv.tv_usec = (wait_time%1000) * 1000; ret = select(mfd + 1, &thiz->fdset, NULL, NULL, &tv);
3.检查事件源并调用相应的事件处理函数
- if( (ret > 0) && (fd = ftk_source_get_fd(source)) >= 0 && FD_ISSET(fd, &thiz->fdset))
- {
- if(ftk_source_dispatch(source) != RET_OK)
- {
- /*as current is removed, the next will be move to current, so dont call i++*/
- ftk_sources_manager_remove(thiz->sources_manager, source);
- ftk_logd("%s:%d remove %p/n", __func__, __LINE__, source);
- }
- else
- {
- i++;
- }
- continue;
- }
- //这里处理timer和idle。
- if((source_wait_time = ftk_source_check(source)) == 0)
- {
- if(ftk_source_dispatch(source) != RET_OK)
- {
- /*as current is removed, the next will be move to current, so dont call i++*/
- ftk_sources_manager_remove(thiz->sources_manager, source);
- //ftk_logd("%s:%d remove %p/n", __func__, __LINE__, source);
- }
- else
- {
- i++;
- }
- continue;
- }
if( (ret > 0) && (fd = ftk_source_get_fd(source)) >= 0 && FD_ISSET(fd, &thiz->fdset)) { if(ftk_source_dispatch(source) != RET_OK) { /*as current is removed, the next will be move to current, so dont call i++*/ ftk_sources_manager_remove(thiz->sources_manager, source); ftk_logd("%s:%d remove %p/n", __func__, __LINE__, source); } else { i++; } continue; } //这里处理timer和idle。 if((source_wait_time = ftk_source_check(source)) == 0) { if(ftk_source_dispatch(source) != RET_OK) { /*as current is removed, the next will be move to current, so dont call i++*/ ftk_sources_manager_remove(thiz->sources_manager, source); //ftk_logd("%s:%d remove %p/n", __func__, __LINE__, source); } else { i++; } continue; }
如果事件源处理函数的返回值不是RET_OK的事件,我们认为出错了或者是事件要求自己被移除,那就把它移除掉。
GUI是事件驱动的,创建一个窗口后,函数马上就返回了,窗口中的控件对用户事件处理是在以后的事件循环中进行的。这对于大多数情况是正常的,但有时我们需要用户确认一些问题,根据确认的结果做相应的处理。比如,用户删除一个文件,我们要确认他是否真的想删除后才能去删除。也就是在创建对话框后,函数不是马上返回,而且等用户确认,关闭对话框后才返回。
为了做到这一点,我们要在一个事件处理函数中,创建另外一个主循环来分发事件。模态对话框就是这样实现的:
- int ftk_dialog_run(FtkWidget* thiz)
- {
- DECL_PRIV1(thiz, priv);
- return_val_if_fail(thiz != NULL, RET_FAIL);
- return_val_if_fail(ftk_widget_type(thiz) == FTK_DIALOG, RET_FAIL);
- ftk_widget_show_all(thiz, 1);
- priv->main_loop = ftk_main_loop_create(ftk_default_sources_manager());
- ftk_main_loop_run(priv->main_loop);
- ftk_main_loop_destroy(priv->main_loop);
- priv->main_loop = NULL;
- return ftk_widget_id(ftk_window_get_focus(thiz));
- }
int ftk_dialog_run(FtkWidget* thiz){ DECL_PRIV1(thiz, priv); return_val_if_fail(thiz != NULL, RET_FAIL); return_val_if_fail(ftk_widget_type(thiz) == FTK_DIALOG, RET_FAIL); ftk_widget_show_all(thiz, 1); priv->main_loop = ftk_main_loop_create(ftk_default_sources_manager()); ftk_main_loop_run(priv->main_loop); ftk_main_loop_destroy(priv->main_loop); priv->main_loop = NULL; return ftk_widget_id(ftk_window_get_focus(thiz));}
对话模型提供了一个用于退出该主循环的函数:
- Ret ftk_dialog_quit(FtkWidget* thiz)
- {
- DECL_PRIV1(thiz, priv);
- return_val_if_fail(ftk_dialog_is_modal(thiz), RET_FAIL);
- ftk_main_loop_quit(priv->main_loop);
- return RET_OK;
- }
Ret ftk_dialog_quit(FtkWidget* thiz){ DECL_PRIV1(thiz, priv); return_val_if_fail(ftk_dialog_is_modal(thiz), RET_FAIL); ftk_main_loop_quit(priv->main_loop); return RET_OK;}
给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
嵌入式GUI FTK设计与实现-主循环相关推荐
- 嵌入式GUI FTK设计与实现-事件源(FtkSource)
转载时请注明出处和作者联系方式 文章出处:http://www.limodev.cn/blog 作者联系方式:李先静 <xianjimli@gmail.com> 在<主循环>一 ...
- 嵌入式GUI FTK 界面设计器
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 嵌入式G ...
- 嵌入式GUI FTK支持输入法
From: http://blog.csdn.net/absurd/article/details/5318285 春节期间给FTK增加输入法支持,目前支持拼音输入法,五笔输入法和手写输入法.手写输入 ...
- 【蓝桥杯嵌入式】【STM32】2_KEYBOARD(主循环扫描+外部中断)
文章目录 1 原理图 2 部分源码 主循环扫描实现 外部中断实现 下载工程文件: https://gitee.com/Joseph_Cooper/blue-bridge-embedded 1 ...
- 嵌入式系统软件架构设计
嵌入式系统软件架构设计 目录 1. 前言 4 2. 决定架构的因素和架构的影响 4 2.1. 常见的误解 5 2.1.1. 小型的系统不需要架构 5 2.1.2. 敏捷开发不需要架构 7 3. 嵌入式 ...
- 【Qt设计开发】GUI界面设计开发
文章目录 一.Qt简介和下载安装 二.Qt入门 2.1 创建第一个项目 2.2 快捷键和命名规范 2.3 Qt项目和VS2022项目相互转换 三.Qt基础 3.1 Qt对象树和窗口坐标系概念 3.2 ...
- 嵌入式系统开发设计---嵌入式系统开发设计
嵌入式系统设计的主要任务是定义系统的功能.决定系统的架构,并将功能映射到系统实现架构上.这里,系统架构既包括软件系统架构也包括硬件系统架构.一种架构可以映射到各种不同的物理实现,每种实现表示不同的取舍 ...
- python framebuffer gui_基于Framebuffer的嵌入式GUI系统实现
摘要:本文研究了基于Framebuffer的嵌入式GUI的系统实现,包括其体系结构层次的建立.驱动机制的分析.微型客户端/服务器模式的实现,以及基于Framebuffer的GAL与GDI的设计等关键内 ...
- 嵌入式linux设计报告,嵌入式linux课程设计报告
嵌入式linux课程设计报告 重庆科技学院 课程设计成果 院(系):_电气与信息工程学院_ 班 级: 计科普0802 学生姓名: 庄桐泉 学 号: 2008441067 设计地点(单位)___ _I3 ...
最新文章
- 有界阻塞队列ArrayBlockingQueue和无界阻塞队列LinkedBlockingQueue
- UITextView高度根据内容变化
- 二次元少女生成器、会开车的神经网络...2019年最好的17个机器学习项目!
- Linux2.6内核--抢占
- 2020年 第11届 蓝桥杯 C/C++ B组 省赛真题详解及小结【第1场省赛2020.7.5】【Java版】
- HTTP请求消息数据格式分析以及request和response
- 复平面中的点集预备知识
- LaTeX设置长公式的跨页显示
- 数据分析师能用到mysql_浅谈数据分析师的必备技能SQL
- SAP License:CO相关知识点
- [转]VSS(Visual SourceSafe)使用入门
- nero burning rom 2021绿色版安装及使用教程
- 机器学习(十七):网格搜索(Grid Search)和SVM
- wordpress壁纸小程序开源版_图片小程序源码
- 城市综合杆道路智慧路灯多杆合一项目解决方案解析
- yzl的javascript学习笔记
- 人民币兑换菲律宾比索去哪些银行?
- Pytorch求向量的L1范数或L2范数
- 第21节 ACL——控制路由器上接口大门的进出规则
- No current assignment for partition 解决
热门文章
- 【unity shader】高级光照 --- 薄膜干涉
- [转载]windbg简明教程
- (烧脑)奇怪数 - C语言 - 回溯
- 一文搞懂AWS Region, VPC, VPC endpoint,AZ, Subnet 基础篇上
- 【Laravel5.3 笔记整理八】Laravel视图(二)逻辑控制、模板布局、文件包含
- 联想台式机 ubuntu 16.04 安装 Nvidia GTX 1060 3g 驱动
- 了解一下Roguelike游戏和Roguelite游戏
- win11无法输入中文解决办法
- 18、数据结构笔记之十八链表实现稀疏矩阵
- Synplify Pro的RTL视图与Technology视图以及优化分析