转载时请注明出处和作者联系方式
文章出处: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的函数,而是发送一个事件:
为什么要多此一举呢?原因这样的: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.找出最小等待时间和文件描述符
这里遍历所有source,找出一个最小的等待时间和要等待的文件描述符。
2. 等待事件发生
3.检查事件源并调用相应的事件处理函数
如果事件源处理函数的返回值不是RET_OK的事件,我们认为出错了或者是事件要求自己被移除,那就把它移除掉。
GUI是事件驱动的,创建一个窗口后,函数马上就返回了,窗口中的控件对用户事件处理是在以后的事件循环中进行的。这对于大多数情况是正常的,但有时我们需要用户确认一些问题,根据确认的结果做相应的处理。比如,用户删除一个文件,我们要确认他是否真的想删除后才能去删除。也就是在创建对话框后,函数不是马上返回,而且等用户确认,关闭对话框后才返回。
为了做到这一点,我们要在一个事件处理函数中,创建另外一个主循环来分发事件。模态对话框就是这样实现的:
对话模型提供了一个用于退出该主循环的函数: