分享一下我老师大神的人工智能教程!零基础,通俗易懂!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的函数,而是发送一个事件:

[c-sharp] view plaincopyprint?
  1. Ret ftk_main_loop_add_source(FtkMainLoop* thiz, FtkSource* source)
  2. {
  3. FtkEvent event = {0};
  4. return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL);
  5. event.type = FTK_EVT_ADD_SOURCE;
  6. event.u.extra = source;
  7. return ftk_source_queue_event(ftk_primary_source(), &event);
  8. }
  9. Ret ftk_main_loop_remove_source(FtkMainLoop* thiz, FtkSource* source)
  10. {
  11. FtkEvent event = {0};
  12. return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL);
  13. event.type = FTK_EVT_REMOVE_SOURCE;
  14. event.u.extra = source;
  15. return ftk_source_queue_event(ftk_primary_source(), &event);
  16. }
  17. 这个事件由Primary Source进行处理:
  18. static Ret ftk_source_primary_dispatch(FtkSource* thiz)
  19. {
  20. ...
  21. switch(event.type)
  22. {
  23. case FTK_EVT_ADD_SOURCE:
  24. {
  25. ftk_sources_manager_add(ftk_default_sources_manager(), event.u.extra);
  26. break;
  27. }
  28. case FTK_EVT_REMOVE_SOURCE:
  29. {
  30. ftk_sources_manager_remove(ftk_default_sources_manager(), event.u.extra);
  31. break;
  32. }
  33. }
  34. ...
  35. }

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.找出最小等待时间和文件描述符

[c-sharp] view plaincopyprint?
  1. for(i = 0; i < ftk_sources_manager_get_count(thiz->sources_manager); i++)
  2. {
  3. source = ftk_sources_manager_get(thiz->sources_manager, i);
  4. if((fd = ftk_source_get_fd(source)) >= 0)
  5. {
  6. FD_SET(fd, &thiz->fdset);
  7. if(mfd < fd) mfd = fd;
  8. n++;
  9. }
  10. source_wait_time = ftk_source_check(source);
  11. if(source_wait_time >= 0 && source_wait_time < wait_time)
  12. {
  13. wait_time = source_wait_time;
  14. }
  15. }

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. 等待事件发生

[c-sharp] view plaincopyprint?
  1. tv.tv_sec = wait_time/1000;
  2. tv.tv_usec = (wait_time%1000) * 1000;
  3. 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.检查事件源并调用相应的事件处理函数

[c-sharp] view plaincopyprint?
  1. if( (ret > 0) && (fd = ftk_source_get_fd(source)) >= 0 && FD_ISSET(fd, &thiz->fdset))
  2. {
  3. if(ftk_source_dispatch(source) != RET_OK)
  4. {
  5. /*as current is removed, the next will be move to current, so dont call i++*/
  6. ftk_sources_manager_remove(thiz->sources_manager, source);
  7. ftk_logd("%s:%d remove %p/n", __func__, __LINE__, source);
  8. }
  9. else
  10. {
  11. i++;
  12. }
  13. continue;
  14. }
  15. //这里处理timer和idle。
  16. if((source_wait_time = ftk_source_check(source)) == 0)
  17. {
  18. if(ftk_source_dispatch(source) != RET_OK)
  19. {
  20. /*as current is removed, the next will be move to current, so dont call i++*/
  21. ftk_sources_manager_remove(thiz->sources_manager, source);
  22. //ftk_logd("%s:%d remove %p/n", __func__, __LINE__, source);
  23. }
  24. else
  25. {
  26. i++;
  27. }
  28. continue;
  29. }

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是事件驱动的,创建一个窗口后,函数马上就返回了,窗口中的控件对用户事件处理是在以后的事件循环中进行的。这对于大多数情况是正常的,但有时我们需要用户确认一些问题,根据确认的结果做相应的处理。比如,用户删除一个文件,我们要确认他是否真的想删除后才能去删除。也就是在创建对话框后,函数不是马上返回,而且等用户确认,关闭对话框后才返回。

为了做到这一点,我们要在一个事件处理函数中,创建另外一个主循环来分发事件。模态对话框就是这样实现的:

[c-sharp] view plaincopyprint?
  1. int ftk_dialog_run(FtkWidget* thiz)
  2. {
  3. DECL_PRIV1(thiz, priv);
  4. return_val_if_fail(thiz != NULL, RET_FAIL);
  5. return_val_if_fail(ftk_widget_type(thiz) == FTK_DIALOG, RET_FAIL);
  6. ftk_widget_show_all(thiz, 1);
  7. priv->main_loop = ftk_main_loop_create(ftk_default_sources_manager());
  8. ftk_main_loop_run(priv->main_loop);
  9. ftk_main_loop_destroy(priv->main_loop);
  10. priv->main_loop = NULL;
  11. return ftk_widget_id(ftk_window_get_focus(thiz));
  12. }

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));}

对话模型提供了一个用于退出该主循环的函数:

[c-sharp] view plaincopyprint?
  1. Ret ftk_dialog_quit(FtkWidget* thiz)
  2. {
  3. DECL_PRIV1(thiz, priv);
  4. return_val_if_fail(ftk_dialog_is_modal(thiz), RET_FAIL);
  5. ftk_main_loop_quit(priv->main_loop);
  6. return RET_OK;
  7. }

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设计与实现-主循环相关推荐

  1. 嵌入式GUI FTK设计与实现-事件源(FtkSource)

    转载时请注明出处和作者联系方式 文章出处:http://www.limodev.cn/blog 作者联系方式:李先静 <xianjimli@gmail.com> 在<主循环>一 ...

  2. 嵌入式GUI FTK 界面设计器

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 嵌入式G ...

  3. 嵌入式GUI FTK支持输入法

    From: http://blog.csdn.net/absurd/article/details/5318285 春节期间给FTK增加输入法支持,目前支持拼音输入法,五笔输入法和手写输入法.手写输入 ...

  4. 【蓝桥杯嵌入式】【STM32】2_KEYBOARD(主循环扫描+外部中断)

    文章目录 1 原理图 2 部分源码 主循环扫描实现 外部中断实现   下载工程文件:   https://gitee.com/Joseph_Cooper/blue-bridge-embedded 1 ...

  5. 嵌入式系统软件架构设计

    嵌入式系统软件架构设计 目录 1. 前言 4 2. 决定架构的因素和架构的影响 4 2.1. 常见的误解 5 2.1.1. 小型的系统不需要架构 5 2.1.2. 敏捷开发不需要架构 7 3. 嵌入式 ...

  6. 【Qt设计开发】GUI界面设计开发

    文章目录 一.Qt简介和下载安装 二.Qt入门 2.1 创建第一个项目 2.2 快捷键和命名规范 2.3 Qt项目和VS2022项目相互转换 三.Qt基础 3.1 Qt对象树和窗口坐标系概念 3.2 ...

  7. 嵌入式系统开发设计---嵌入式系统开发设计

    嵌入式系统设计的主要任务是定义系统的功能.决定系统的架构,并将功能映射到系统实现架构上.这里,系统架构既包括软件系统架构也包括硬件系统架构.一种架构可以映射到各种不同的物理实现,每种实现表示不同的取舍 ...

  8. python framebuffer gui_基于Framebuffer的嵌入式GUI系统实现

    摘要:本文研究了基于Framebuffer的嵌入式GUI的系统实现,包括其体系结构层次的建立.驱动机制的分析.微型客户端/服务器模式的实现,以及基于Framebuffer的GAL与GDI的设计等关键内 ...

  9. 嵌入式linux设计报告,嵌入式linux课程设计报告

    嵌入式linux课程设计报告 重庆科技学院 课程设计成果 院(系):_电气与信息工程学院_ 班 级: 计科普0802 学生姓名: 庄桐泉 学 号: 2008441067 设计地点(单位)___ _I3 ...

最新文章

  1. 有界阻塞队列ArrayBlockingQueue和无界阻塞队列LinkedBlockingQueue
  2. UITextView高度根据内容变化
  3. 二次元少女生成器、会开车的神经网络...2019年最好的17个机器学习项目!
  4. Linux2.6内核--抢占
  5. 2020年 第11届 蓝桥杯 C/C++ B组 省赛真题详解及小结【第1场省赛2020.7.5】【Java版】
  6. HTTP请求消息数据格式分析以及request和response
  7. 复平面中的点集预备知识
  8. LaTeX设置长公式的跨页显示
  9. 数据分析师能用到mysql_浅谈数据分析师的必备技能SQL
  10. SAP License:CO相关知识点
  11. [转]VSS(Visual SourceSafe)使用入门
  12. nero burning rom 2021绿色版安装及使用教程
  13. 机器学习(十七):网格搜索(Grid Search)和SVM
  14. wordpress壁纸小程序开源版_图片小程序源码
  15. 城市综合杆道路智慧路灯多杆合一项目解决方案解析
  16. yzl的javascript学习笔记
  17. 人民币兑换菲律宾比索去哪些银行?
  18. Pytorch求向量的L1范数或L2范数
  19. 第21节 ACL——控制路由器上接口大门的进出规则
  20. No current assignment for partition 解决

热门文章

  1. 【unity shader】高级光照 --- 薄膜干涉
  2. [转载]windbg简明教程
  3. (烧脑)奇怪数 - C语言 - 回溯
  4. 一文搞懂AWS Region, VPC, VPC endpoint,AZ, Subnet 基础篇上
  5. 【Laravel5.3 笔记整理八】Laravel视图(二)逻辑控制、模板布局、文件包含
  6. 联想台式机 ubuntu 16.04 安装 Nvidia GTX 1060 3g 驱动
  7. 了解一下Roguelike游戏和Roguelite游戏
  8. win11无法输入中文解决办法
  9. 18、数据结构笔记之十八链表实现稀疏矩阵
  10. Synplify Pro的RTL视图与Technology视图以及优化分析