嵌入式GUI FTK设计与实现-主循环

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               



转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <[email protected]>

带图形用户界面(GUI)的应用程序和传统的批处理程序是不同的:

* 批处理程序是一步一步的执行,直到完成任务为止,完成任务后程序立即退出。
* 图形用户界面应用程序则是事件驱动的,它等待事件发生,然后处理事件,如此循环,直到用户要求退出为止。

两种执行模型如下图所示:

cmp

通常我们把等待事件/处理事件的循环称为主循环(MainLoop),主循环是GUI应用程序必要组件之一,FTK当然也离开不主循环 (MainLoop)。大多数嵌入式GUI都采用了Windows类似的主循环:

    while(GetMessage(&msg))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

我不太喜欢这种主循环,主要原因有两点:

* 它看似简洁,但内部实现并不简洁。它从当前线程的队列里获取消息,然后处理消息。这些消息从来哪里的?当然是由其它线程传递给当前线程的,这就意味着 GUI需要多线程的支持。而FTK不想引入多线程来折磨自己,至少在GUI部分是不想的。

* 这是面向过程的处理方式。消息是一个对象,按照面向对象的思想来说,对象的数据和行为应该绑定在一起。而这里的消息是纯粹的数据,所有消息都由目标窗口的消息处理函数来处理的。FTK希望每类消息都有自己的处理函数,而不是全部由窗口来处理。

FTK采用了GTK类似的主循环:

    ftk_run();

它看起来更简洁,内部实现也不需要多线程的支持。这里采用了POSA(面向模式的软件架构)中的Reactor模式,它的主要好处有:

* 用单线程处理多个事件源。
* 增加新事件源时不会影响事件分发的框架。

整个主循环由下列组件构成:
mainloop
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 plain copy print ?
  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.    
  6.     event.type = FTK_EVT_ADD_SOURCE;  
  7.     event.u.extra = source;  
  8.    
  9.     return ftk_source_queue_event(ftk_primary_source(), &event);  
  10. }  
  11.    
  12. Ret ftk_main_loop_remove_source(FtkMainLoop* thiz, FtkSource* source)  
  13. {  
  14.     FtkEvent event = {0};  
  15.     return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL);  
  16.    
  17.     event.type = FTK_EVT_REMOVE_SOURCE;  
  18.     event.u.extra = source;  
  19.    
  20.     return ftk_source_queue_event(ftk_primary_source(), &event);  
  21. }  
  22. 这个事件由Primary Source进行处理:  
  23. static Ret ftk_source_primary_dispatch(FtkSource* thiz)  
  24. {  
  25.     ...  
  26.     switch(event.type)  
  27.     {  
  28.         case FTK_EVT_ADD_SOURCE:  
  29.         {  
  30.             ftk_sources_manager_add(ftk_default_sources_manager(), event.u.extra);  
  31.             break;  
  32.         }  
  33.         case FTK_EVT_REMOVE_SOURCE:  
  34.         {  
  35.             ftk_sources_manager_remove(ftk_default_sources_manager(), event.u.extra);  
  36.             break;  
  37.         }  
  38.     }  
  39.     ...  
  40. }  
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 plain copy print ?
  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.   
  11.     source_wait_time = ftk_source_check(source);  
  12.     if(source_wait_time >= 0 && source_wait_time < wait_time)  
  13.     {  
  14.         wait_time = source_wait_time;  
  15.     }  
  16. }  
        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 plain copy print ?
  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 plain copy print ?
  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 plain copy print ?
  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.    
  7.     ftk_widget_show_all(thiz, 1);  
  8.     priv->main_loop = ftk_main_loop_create(ftk_default_sources_manager());  
  9.     ftk_main_loop_run(priv->main_loop);  
  10.     ftk_main_loop_destroy(priv->main_loop);  
  11.     priv->main_loop = NULL;  
  12.    
  13.     return ftk_widget_id(ftk_window_get_focus(thiz));  
  14. }  
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 plain copy print ?
  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.    
  6.     ftk_main_loop_quit(priv->main_loop);  
  7.    
  8.     return RET_OK;  
  9. }  
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

这里写图片描述

猜你喜欢

转载自blog.csdn.net/jggyff/article/details/84194552