一步一步实现自己的模拟控件(9)——消息处理
这次我们将要给Widget增加一些状态,并使其能够接受出消息处理扩展,测试工程中实现了一个按钮的消息处理扩展。
Widget状态:
之前的控件只是绘制了一个边框,并且总是会在窗口中显示。实际上我们往往会希望能够让某个控件显示或者隐藏、可用或者不可用等等,那么控件应该具有能够标识这些状态的属性,于是我们给Widget增加了状态概念。
void AddStates(size_t states);
void SubStates(size_t states);
bool CheckState(widget::StateBitField s);
上面是状态相关的几个接口,包括了增加状态组,减少状态组,检测状态。这里有个状态组的概念,是因为我将状态用位域来实现,那么他们就可以通过or运算来得到多个状态的集合,我就这个集合称为状态组。
STATE_VISIBLE = 1 << 0, // 控件可见?决定控件是否被绘制
STATE_ENABLE = 1 << 1, // 控件可用?决定控件是否响应鼠标键盘消息
STATE_TRANSPARENT = 1 << 2, // 控件透明?决定控件是否响应鼠标键盘消息以及是否显示tooltip
};
目前我给Widget定义了3个状态:可见、可用、透明。默认创建的Widget是不可见、不可用、不透明的,需要在创建成功后手动设置,例如:
auto pRootWidget = ghost::Widget::Create();
pRootWidget->AddStates(
消息处理扩展:
通常对于控件来说,没有消息处理就等于没有生命意义,那么为Widget添加消息处理扩展就意味着使Widget活起来,这次我们就来完成这个任务,期待Widget活起来的那激动人心的一刻。和以往添加扩展支持一样,为扩展编写一个抽象基类,在Widget的关联对象管理中添加这个扩展的关联处理,然后在Widget的SendMessage处理逻辑里增加对消息处理扩展的支持。那么我们的SendMessage实现就变成了这样:
{
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的人至少能够看到其运行效果。