万星星@豌豆荚 欢迎加入我们
一个吃软饭的男人!!!!!我只想写程序####
微博:http://weibo.com/wanlianwen
posts - 172,  comments - 1253,  trackbacks - 0

上一次,我们可以获取到图片动画帧之间的时间间隔,如果想让动画转起来,就必须有时钟。插入的图片动画数量可能会比较多,因此要想不影响性能,时钟必须很轻量级而且要很高效。

 

Windows平台上实现时钟的方式五花八门,你可以使用窗口相关的SetTimer来设置一个时钟,也可以自己开辟线程来做等待触发模拟时钟,而Chromium封装的要更加C++对象化一些:依托Windows窗口消息,抽象出延迟任务的概念。这种手法几年前我也曾经考虑过,只是对其中下次最短触发时间计算以及更新的算法和设计都有力不从心,最终得出的是误差很大的精简版:选择固定的最小时间片为最小触发单位,对很小的时间间隔误差很明显。

 

WindowsTimer Queues用来实现高效的异步时钟,比较奇怪的是这组API用的貌似并不多。我们知道每个进程都有一个默认的线程池,可以在其中执行一些Work Items,时钟队列和等待操作也都会用到这个线程池。timer-queue中的timers创建和销毁都很轻量高效,因此我选择了它。

 

每个OLE图片对象在设置图片之后,如果发现是多帧的,就需要启动动画,创建时钟:

 

ATLVERIFY(CreateTimerQueueTimer(&timer_, NULL,
                              WaitOrTimerCallback,
                              callback_parameter_.get(),
                              image_->GetFrameDelay(current_frame_),
                              0, WT_EXECUTEDEFAULT));

 

这里timer_是返回值,返回新建的时钟对象,可以在OLE对象销毁或者回调函数中进行删除,而删除操作会等待回调执行完毕才返回。传递TimerQueueNULL表示使用系统的队列。Period0表示只触发一次,触发时间为image_->GetFrameDelay(current_frame_)。由于回调函数WaitOrTimerCallback是在线程池的线程中执行,所以更新操作需要同步到动画图片的创建线程中。callback_parameter_包含有上一节提及的ThreadState对象以及动画OLE对象指针,ThreadState创建的时候会同时创建一个隐藏窗口用于工作者线程向UI线程同步操作:

 

VOID CALLBACK IMRichPicture::WaitOrTimerCallback(PVOID lpParameter,
                                            BOOLEAN TimerOrWaitFired) {
  ATLASSERT(TimerOrWaitFired == TRUE);
 
  IMRichPicture::CallbackParameter* parameter =
      reinterpret_cast<IMRichPicture::CallbackParameter*>(lpParameter);
  ATLASSERT(parameter);
  parameter->thread_state->UpdatePictureFrame(parameter->picture);
}

 

下面是UpdatePictureFrame的实现:

 

void IMThreadState::UpdatePictureFrame(IMRichPicture* picture) const {
  PostMessage(message_window_, kMessageUpdatePictureFrame,
              reinterpret_cast<WPARAM>(picture->richedit()),
              reinterpret_cast<LPARAM>(picture));
}

 

这样绕一大圈子,是为了利用Timer Queues的同时保证图片的更新操作是在UI线程中执行,因为图片被插入也是发生在UI线程,即动画控件创建于UI线程,为了避免加锁带来的麻烦以及死锁的可能性,不应该轻易去加锁,尽量利用操作系统提供的基础设施来实现。这里需要注意的是隐藏窗口接收到kMessageUpdatePictureFrame消息时,richedit窗口可能已不存在或者动画控件已经销毁,因此使用指针前,需要判断对象是否还存在:

 

case kMessageUpdatePictureFrame: {
      IMRichEditImpl* richedit = reinterpret_cast<IMRichEditImpl*>(wparam);
      IMRichPicture* picture = reinterpret_cast<IMRichPicture*>(lparam);
      if (IMThreadState::current()->HasRichEdit(richedit))
        richedit->OnUpdatePictureFrame(picture);
      return 0;
}

 

posted on 2012-06-24 15:51 万连文 阅读(2729) 评论(6)  编辑 收藏 引用 所属分类: richedit

FeedBack:
# re: richedit研究04 – 高效时钟
2012-08-23 22:02 | 路障
richedit->OnUpdatePictureFrame(picture);

问一下,你的OnUpdatePictureFrame里面是怎么刷新下一帧的?是通知richedit刷新,还是在OnUpdatePictureFrame里面获取位置直接画上去下一帧的图像内容?  回复  更多评论
  
# re: richedit研究04 – 高效时钟
2012-08-24 07:03 | 万连文
@路障
通知richedit刷新,这样更容易封装一些,richedit负责获取ole位置,并判断是否可见,不可见则做标记不再更新直至下次ole的Draw发生(richedit刷新机制负责)时设置true;如果可见则进行绘制。  回复  更多评论
  
# re: richedit研究04 – 高效时钟
2012-08-24 19:19 | 路障
能说一下你是怎么获取ole位置的吗?通过遍历ole对象,然后GetPoint?

另外,你是怎么通知richedit刷新的?

通过
if (m_pAdviseSink != NULL)
m_pAdviseSink->OnViewChange(DVASPECT_CONTENT, -1);

还是通过
::InvalidateRect(m_hRichEditWnd, &m_rcItem, FALSE);  回复  更多评论
  
# re: richedit研究04 – 高效时钟
2012-08-24 19:40 | 万连文
@路障
都不是,这方面的内容我会在下一篇博客里面提到。  回复  更多评论
  
# re: richedit研究04 – 高效时钟
2012-08-24 19:59 | 路障
万大侠,不知道你能不能把你的代码发我一份,最近在弄一个东西,时间比较赶。
如果可以,发一下到这个邮箱:847708268#qq.com。  回复  更多评论
  
# re: richedit研究04 – 高效时钟
2012-09-04 17:00 | 路障
@万连文
万大侠,大致简略地说一下,你是怎么获取OLE控件的位置和通知RichEdit刷新的?  回复  更多评论
  

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


简历下载
联系我

<2012年6月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

常用链接

留言簿(66)

随笔分类

随笔档案

相册

搜索

  •  

最新评论

阅读排行榜

评论排行榜