- 1.消息流转流程
- 2.作者答疑
上一部分,将消息的预处理进行了讲解,这一部分重点分析win32消息怎样转成事件,再如何分发给各个子控件。首先查看事件的源代码,如下所示:
// Structure for notifications from the system
// to the control implementation.
typedef struct UILIB_API tagTEventUI
{
int Type;
CControlUI* pSender;
DWORD dwTimestamp;
POINT ptMouse;
TCHAR chKey;
WORD wKeyState;
WPARAM wParam;
LPARAM lParam;
} TEventUI;
先查看在消息函数下关键的一个消息,鼠标左键按下,在这个消息里定位子控件,并响应相应的函数,源代码如下所示:
case WM_LBUTTONDOWN:
{
// We alway set focus back to our app (this helps
// when Win32 child windows are placed on the dialog
// and we need to remove them on focus change).
if (!m_bNoActivate) ::SetFocus(m_hWndPaint);
if( m_pRoot == NULL ) break;
// 根据点位置查找子控件
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
CControlUI* pControl = FindControl(pt);
if( pControl == NULL ) break;
if( pControl->GetManager() != this ) break;
// 击中控件绘制
if(pControl->IsDragEnabled()) {
m_bDragMode = true;
if( m_hDragBitmap != NULL ) {
::DeleteObject(m_hDragBitmap);
m_hDragBitmap = NULL;
}
m_hDragBitmap = CRenderEngine::GenerateBitmap(this, pControl, pControl->GetPos());
}
SetCapture();
m_pEventClick = pControl;
pControl->SetFocus();
//消息转事件
TEventUI event = { 0 };
event.Type = UIEVENT_BUTTONDOWN;
event.pSender = pControl;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
pControl->Event(event);//转入事件处理
}
break;
这段代码非常清晰明了的展示了,win32消息转为事件,再调用事件处理函数。首先从win32消息里获取鼠标点位置,然后调用FindControl函数查找点所击中的子控件,源代码如下所示:
CControlUI* CPaintManagerUI::FindControl(POINT pt) const
{
ASSERT(m_pRoot);
return m_pRoot->FindControl(__FindControlFromPoint, &pt, UIFIND_VISIBLE | UIFIND_HITTEST | UIFIND_TOP_FIRST);
}
CControlUI* CPaintManagerUI::FindControl(LPCTSTR pstrName) const
{
ASSERT(m_pRoot);
return static_cast(m_mNameHash.Find(pstrName));
}
从根节点依次往下查找子节点是否包含该点,找到最下的那个子控件作为目标控件。填充参数,在pControl->Event(event);中转入子控件的事件处理。代码如下所示:
void CControlUI::Event(TEventUI& event)
{
if( OnEvent(&event) ) DoEvent(event);
}
OnEvent是个CEventSource成员变量,在委托那一小节里有比较清晰的讲解。先调用注册的委托对象数组,对该事件数据进行处理。接着调用DoEvent函数,作者假设击中的是一个按钮,则DoEvent源代码如下:
void CButtonUI::DoEvent(TEventUI& event)
{
if( !IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type DoEvent(event);
else CLabelUI::DoEvent(event);
return;
}
if( event.Type == UIEVENT_SETFOCUS )
{
Invalidate();
}
if( event.Type == UIEVENT_KILLFOCUS )
{
Invalidate();
}
if( event.Type == UIEVENT_KEYDOWN )
{
if (IsKeyboardEnabled() && IsEnabled()) {
if( event.chKey == VK_SPACE || event.chKey == VK_RETURN ) {
Activate();
return;
}
}
}
if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK )
{
if( ::PtInRect(&m_rcItem, event.ptMouse) && IsEnabled() ) {
m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED;
Invalidate();
}
return;
}
if( event.Type == UIEVENT_MOUSEMOVE )
{
if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
if( ::PtInRect(&m_rcItem, event.ptMouse) ) m_uButtonState |= UISTATE_PUSHED;
else m_uButtonState &= ~UISTATE_PUSHED;
Invalidate();
}
return;
}
if( event.Type == UIEVENT_BUTTONUP )
{
if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
if( ::PtInRect(&m_rcItem, event.ptMouse) && IsEnabled()) Activate();
m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
Invalidate();
}
return;
}
if( event.Type == UIEVENT_CONTEXTMENU )
{
if( IsContextMenuUsed() && IsEnabled()) {
m_pManager->SendNotify(this, DUI_MSGTYPE_MENU, event.wParam, event.lParam);//发送通知
}
return;
}
if( event.Type == UIEVENT_MOUSEENTER )
{
if( ::PtInRect(&m_rcItem, event.ptMouse ) ) {
if( IsEnabled() ) {
if( (m_uButtonState & UISTATE_HOT) == 0 ) {
m_uButtonState |= UISTATE_HOT;
Invalidate();
}
}
}
if ( GetFadeAlphaDelta() > 0 ) {
m_pManager->SetTimer(this, FADE_TIMERID, FADE_ELLAPSE);
}
}
if( event.Type == UIEVENT_MOUSELEAVE )
{
if( !::PtInRect(&m_rcItem, event.ptMouse ) ) {
if( IsEnabled() ) {
if( (m_uButtonState & UISTATE_HOT) != 0 ) {
m_uButtonState &= ~UISTATE_HOT;
Invalidate();
}
}
if (m_pManager) m_pManager->RemoveMouseLeaveNeeded(this);
if ( GetFadeAlphaDelta() > 0 ) {
m_pManager->SetTimer(this, FADE_TIMERID, FADE_ELLAPSE);
}
}
else {
if (m_pManager) m_pManager->AddMouseLeaveNeeded(this);
return;
}
}
if( event.Type == UIEVENT_SETCURSOR )
{
::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_HAND)));
return;
}
if( event.Type == UIEVENT_TIMER && event.wParam == FADE_TIMERID )
{
if( (m_uButtonState & UISTATE_HOT) != 0 ) {
if( m_uFadeAlpha > m_uFadeAlphaDelta ) m_uFadeAlpha -= m_uFadeAlphaDelta;
else {
m_uFadeAlpha = 0;
m_pManager->KillTimer(this, FADE_TIMERID);
}
}
else {
if( m_uFadeAlpha KillTimer(this, FADE_TIMERID);
}
}
Invalidate();
return;
}
CLabelUI::DoEvent(event);
}
如果击中的控件中的左键击中代码进行相应,如果没有处理函数,再传给父类进行响应,依次到最后。 【小节】仔细的读者阅读到这,对duilib的消息传递,一定会有一个清晰的认识,在duilib中,有两个路线传递消息,一条是从win32操作系统通过系统消息转事件传递信息,原理是通过点位查找当前子控件,再调用自动控件的DoEvent函数处理系统消息。另一条是duilib的自定义消息路线,子控件通过SendNotify函数,将消息进行转发,调用注册在子控件成员CPaintManagerUI中转发通知消息,所以这条路线是不能在真正的win32窗口间传递消息的。SendNotify函数源代码如下:
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)
{
Msg.ptMouse = m_ptLastMousePos;
Msg.dwTimestamp = ::GetTickCount();
if( m_bUsedVirtualWnd )
{
Msg.sVirtualWnd = Msg.pSender->GetVirtualWnd();
}
if( !bAsync ) {//同步处理
// Send to all listeners
if( Msg.pSender != NULL ) {
if( Msg.pSender->OnNotify ) Msg.pSender->OnNotify(&Msg);
}
for( int i = 0; i Notify(Msg);
}
}
else {//异步处理
TNotifyUI *pMsg = new TNotifyUI;
pMsg->pSender = Msg.pSender;
pMsg->sType = Msg.sType;
pMsg->wParam = Msg.wParam;
pMsg->lParam = Msg.lParam;
pMsg->ptMouse = Msg.ptMouse;
pMsg->dwTimestamp = Msg.dwTimestamp;
m_aAsyncNotify.Add(pMsg);
PostAsyncNotify();
}
}
读者阅读到这,思路上可能还有些模糊的地方,有个关系可能在头脑里没有清晰的确立起来,CPaintManagerUI是WindowImplBase的一个成员。而子控件是CPaintManagerUI的一个树形结构成员。这样关系就清楚起来,对于一个用WindowImplBase创建的窗口而言,它只有一个CPaintManagerUI,它处理绘制和窗口消息。而窗口内子控件间如果要通信就通过Notify的通讯路线进行交互。如此整个duilib内的消息处理流程就已经讲述完毕。
2.作者答疑如有疑问,请留言。