这次我们将要给Widget增加一些状态,并使其能够接受出消息处理扩展,测试工程中实现了一个按钮的消息处理扩展。
Widget状态:
之前的控件只是绘制了一个边框,并且总是会在窗口中显示。实际上我们往往会希望能够让某个控件显示或者隐藏、可用或者不可用等等,那么控件应该具有能够标识这些状态的属性,于是我们给Widget增加了状态概念。
// 状态相关 void AddStates(size_t states); void SubStates(size_t states); bool CheckState(widget::StateBitField s);上面是状态相关的几个接口,包括了增加状态组,减少状态组,检测状态。这里有个状态组的概念,是因为我将状态用位域来实现,那么他们就可以通过or运算来得到多个状态的集合,我就这个集合称为状态组。
enum StateBitField{ STATE_VISIBLE = 1 << 0 , // 控件可见?决定控件是否被绘制 STATE_ENABLE = 1 << 1 , // 控件可用?决定控件是否响应鼠标键盘消息 STATE_TRANSPARENT = 1 << 2 , // 控件透明?决定控件是否响应鼠标键盘消息以及是否显示tooltip };目前我给Widget定义了3个状态:可见、可用、透明。默认创建的Widget是不可见、不可用、不透明的,需要在创建成功后手动设置,例如:
// 建立根控件 auto pRootWidget = ghost::Widget::Create();pRootWidget -> AddStates( ghost::widget::STATE_VISIBLE | ghost::widget::STATE_ENABLE | ghost::widget::STATE_TRANSPARENT);消息处理扩展:
通常对于控件来说,没有消息处理就等于没有生命意义,那么为Widget添加消息处理扩展就意味着使Widget活起来,这次我们就来完成这个任务,期待Widget活起来的那激动人心的一刻。和以往添加扩展支持一样,为扩展编写一个抽象基类,在Widget的关联对象管理中添加这个扩展的关联处理,然后在Widget的SendMessage处理逻辑里增加对消息处理扩展的支持。那么我们的SendMessage实现就变成了这样:
int Widget::SendMessage(widget::Message message, void * param1 /* = 0 */ , void * param2 /* = 0 */ ){ if ( ! CheckState(widget::STATE_ENABLE)) { // 不响应鼠标键盘消息 if ((widget::MSG_MOUSE_FIRST <= message && widget::MSG_MOUSE_LAST >= message) || (widget::MSG_KEY_FIRST <= message && widget::MSG_KEY_LAST >= message)) { return widget::MSG_RESULT_NONE; } } if (CheckState(widget::STATE_TRANSPARENT)) { // 不响应鼠标键盘消息,不显示tooltip if ((widget::MSG_MOUSE_FIRST <= message && widget::MSG_MOUSE_LAST >= message) || (widget::MSG_KEY_FIRST <= message && widget::MSG_KEY_LAST >= message)) { return widget::MSG_RESULT_NONE; } } auto pRelatedObject = GetRelatedObject(); if (pRelatedObject) { auto pMessageHandle = pRelatedObject -> GetMessageHandle(); if (pMessageHandle) { bool handled = false ; int res = pMessageHandle -> OnMessage( this , message, param1, param2, handled); if (handled) { return res; } } } switch (message) {#ifdef _DEBUG case widget::MSG_DRAW: { HBRUSH hBrush = ::CreateSolidBrush(pImpl_ -> testFrameColor_); ::FrameRect((HDC)param1, & pImpl_ -> absoluteRect_, hBrush); ::DeleteObject(hBrush); } break ; #endif // _DEBUG case widget::MSG_HIT_TEST: if (::PtInRect( & pImpl_ -> absoluteRect_, * ( const POINT * )param1)) { return ~ widget::MSG_RESULT_NONE; } break ; } return widget::MSG_RESULT_NONE;}我多次提到了tooltip,但是我们这次并没有为Widget增加其支持,它将在后面被加入到Widget中来。可以看到这里处理有对消息处理扩展的支持,还有状态对于消息的影响。这里出现了一个MSG_HIT_TEST,这是一个新增的消息。这次为Widget添加了很多的消息,包括了鼠标、键盘等,要将鼠标消息准确的发送给正确的控件,那么点击测试是必不可少的,这个MSG_HIT_TEST消息则是用于控件处理点击测试的。
点击测试:
当容器产生了鼠标事件的时候,我们能够得到鼠标热点在容器中的坐标。前面我也多次提到,模拟控件是容器中的某个区域,那么当鼠标热点位于某个控件所处的区域的时候,那么这个鼠标事件我们就应当交由这个控件进行处理(这是通常情况,也有可能某个控件作为透明控件,不接受任何点击测试)。于是我们便通过点击测试(HitTest)这个访问接口来找到应该处理鼠标事件的控件。在找到控件之后,我们还将坐标映射到了控件的相对坐标系,这样控件就可以以自身的相对位置来处理鼠标事件了。
当然,这次的内容非常多,包括坐标映射、区域映射,捕获鼠标的控件、活动控件、焦点控件等概念都未提到,但在代码中还是能够看到这些概念的。如果一一介绍,那文章就会非常冗长,也会使Widget实现进展缓慢,因此我通常都会省略一些内容,这些内容也就只能通过代码阅读来得到了。
按钮:
为了测试我们这次实现的内容,我们编写了一个按钮的消息处理扩展。简单起见,我们使其不产生命令、不绘制文本,仅仅只是展示对鼠标消息的处理和状态的变化而已。
我们将原先测试代码中的中间那个控件关联了按钮的消息处理,那么它就称为了一个按钮控件了。我们可以将鼠标移到它上面点击看看会发生什么。
下载测试工程代码 因为我一直都在使用VC10来编写Widget,也用到了一些新的特性,所以子啊这次的测试工程我生成了一份release下的程序,没有VC10的人至少能够看到其运行效果。
转载于:https://www.cnblogs.com/EvilGhost/archive/2011/04/24/Abstract_Widget_9.html
相关资源:数据结构—成绩单生成器