世上本无窗口,窗口只是人的眼睛和电脑屏幕及鼠标键盘相互操作后的视觉效果!

  

  下面我们来看看我们之前讲过的代码:

class CDuiFrameWnd : public CWindowWnd, public INotifyUI
{
public:
virtual LPCTSTR GetWindowClassName() const
{
return _T("DUIMainFrame");
} virtual void Notify(TNotifyUI& msg)
{
if (msg.sType == _T("click"))
{
if (msg.pSender->GetName() == _T("btnClick"))
{
::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL);
}
}
} virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0; if( uMsg == WM_CREATE )
{
CControlUI *pWnd = new CButtonUI;
pWnd->SetName(_T("btnClick"));
pWnd->SetText(_T("My First DUI")); // 设置文字
pWnd->SetBkColor(0xFF808080); // 设置背景色 m_PaintManager.Init(m_hWnd); //主窗口句柄
m_PaintManager.AttachDialog(pWnd);
m_PaintManager.AddNotifier(this);
return lRes;
} if( m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes) )
{
return lRes;
} return __super::HandleMessage(uMsg, wParam, lParam);
} protected:
CPaintManagerUI m_PaintManager;
};

  先看红色标记部分

  首先拦截WM_CREATE消息(如果你不知道是如何拦截的,请回去看领悟2),我们可以看到new出了一个CButtonUI,看名字知道这是一个按钮类,查duilib源码可知它从CControlUI派生,至于CControlUI是什么以后再讲,这里先要它是所有控件的基类.然后为这个按钮设置了控件ID::SetName(),设置显示名称::SetText(),并设置背景颜色

  然后将主窗口句柄m_hWnd传递给了对象m_PaintManager,干嘛用的呢?当然是用来绘图的!

  接着将按钮控件设置为m_PaintManger的控件树的根元素对应 --> m_PaintManger.AttachDialog(pWnd); 那么为什么要设置这么一个控件根元素?因为以后我们根据鼠标点击的位置来查找当前点击的是哪一个控件就是根据这个传递进来的根控件元素为起点开始遍历查找的!知道了吧?

  再接着就是最重要的消息相关的了:m_PaintManager.AddNotifier(this); 有同学问了,就这么一句就可以实现消息路由了?我们看看AddNotifier()到底做了什么!

bool CPaintManagerUI::AddNotifier(INotifyUI* pNotifier)
{
ASSERT(m_aNotifiers.Find(pNotifier)<);
return m_aNotifiers.Add(pNotifier);
}
private:
CStdPtrArray m_aNotifiers; //Notify消息接收的指针队列 SendNotify()会遍历此链表给所有相关的消息接收者发送NOTIFY消息,自然会有纯虚函数Notify来接收并处理此消息 比如按钮点击等

看看m_aNotifiers在哪里引用过:

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 < m_aNotifiers.GetSize(); i++ ) {
static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
}
......

}

红色部分循环遍历监听者,Notify()是个纯虚函数,根据C++多态性,肯定会调用用户类的Notify();那么这个SendNofity()是在哪里被调用的呢?

void CPaintManagerUI::SendNotify(CControlUI* pControl, LPCTSTR pstrMessage, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/, bool bAsync /*= false*/)
{
TNotifyUI Msg;
Msg.pSender = pControl;
Msg.sType = pstrMessage;
Msg.wParam = wParam;
Msg.lParam = lParam;
SendNotify(Msg, bAsync);
}

这是SendNotify()的重载函数,又是哪位在调用它呢?

    void CButtonUI::DoEvent(TEventUI& event)
{ if( event.Type == UIEVENT_BUTTONUP )
{
if( (m_uButtonState & UISTATE_CAPTURED) != ) {
if( ::PtInRect(&m_rcItem, event.ptMouse) ) Activate();
m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
Invalidate();
}
return;
}
}
    bool CButtonUI::Activate()
{
if( !CControlUI::Activate() ) return false;
if( m_pManager != NULL ) m_pManager->SendNotify(this, DUI_MSGTYPE_CLICK);
return true;
}

我们可以看到m_pManager,这是什么变量,查看可知,它是我们在用户类声明的CPaintManager对象,它是如何到达CButtonUI类成为它的成员变量的呢?

protected:
CPaintManagerUI* m_pManager; //此变量保存的就是主窗口的那个m_PaintManager

上面代码解释了m_pManager,这是CControlUI的成员,而CButtonUI派生自CControlUI,所以该变量被CButtonUI所拥有,不足为怪!至于它是在哪里被赋值的,可以看下duilib源码,这里不再贴代码了!

  这里就引出来一个问题,就是鼠标左键点击是如何到达CButtonUI的呢?????这是一个比较大的疑问! 请看代码:

    case WM_LBUTTONUP:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
if( m_pEventClick == NULL ) break;
ReleaseCapture();
TEventUI event = { };
event.Type = UIEVENT_BUTTONUP;
event.pSender = m_pEventClick;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
m_pEventClick->Event(event);
m_pEventClick = NULL;
}
break;

  之前说过,CPaintManager成为所有消息的路由中心,由它决定消息该如何路由,上面的代码段中红色标记部分,封装了event,并传递给Event(),至于 m_pEventClick是如何来的,如何赋值的,请看case WM_LBUTTONDOWN分支,

    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).
::SetFocus(m_hWndPaint);
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;
m_pEventClick = pControl;
pControl->SetFocus();
SetCapture();
TEventUI event = { };
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);
}

  请看我用红色标记的部分,首先调用FindControl,根据pt查找所属控件:

CControlUI* CPaintManagerUI::FindControl(POINT pt) const
{
ASSERT(m_pRoot);
return m_pRoot->FindControl(__FindControlFromPoint, &pt, UIFIND_VISIBLE | UIFIND_HITTEST | UIFIND_TOP_FIRST);
}

  其中

__FindControlFromPoint

是回调函数,

这个m_pRoot就是我们之前保存的CButtonUI的对象地址,调用的FindControl()当然是CControlUI的FindControl():

CControlUI* CControlUI::FindControl(FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags)
{
if( (uFlags & UIFIND_VISIBLE) != && !IsVisible() ) return NULL;
if( (uFlags & UIFIND_ENABLED) != && !IsEnabled() ) return NULL;
if( (uFlags & UIFIND_HITTEST) != && (!m_bMouseEnabled || !::PtInRect(&m_rcItem, * static_cast<LPPOINT>(pData))) ) return NULL;
return Proc(this, pData); // 根结点开始查找
}

  红色标记部分的Proc()就是我们传递的回调函数地址__FindControlFromPoint(),为了证明我说的是对的下面给出调试信息:

  

  可以清楚的看到Proc地址就是CPaintManagerUI::__FindControlFromPoint() 这是个静态函数,回调函数必须是静态或者全局;

   逗乐个圈,讲了下回调函数调用过程!

   目前我们的主窗口没有使用到CContainerUI,如果用到了,路径就不是这么走的了,根据继承性,会调用CContainerUI的FindControl(),不过最终还会回到基类CControlUI的FindControl(),呵呵

   这样我们便得到了m_pEventClick指针指向的控件,接着调用pControl->Event(event):

void CControlUI::Event(TEventUI& event)
{
if( OnEvent(&event) ) DoEvent(event);
}
    virtual void Event(TEventUI& event);
virtual void DoEvent(TEventUI& event);

  这两个函数被声明为虚函数!

  根据多态性,我想你知道我想说什么!呵呵!当然是派生类的DoEvent()啦!这样我们就可以处理鼠标点击左键的消息了,我们看看CButtonUI的DoEvent()做了什么?

  代码有点长,做好准备哦!

  

    void CButtonUI::DoEvent(TEventUI& event)
{
if( !IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND ) {
if( m_pParent != NULL ) m_pParent->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()) {
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) != ) {
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) != ) {
if( ::PtInRect(&m_rcItem, event.ptMouse) ) Activate();
m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
Invalidate();
}
return;
}
if( event.Type == UIEVENT_CONTEXTMENU )
{
if( IsContextMenuUsed() ) {
m_pManager->SendNotify(this, DUI_MSGTYPE_MENU, event.wParam, event.lParam);
}
return;
}
if( event.Type == UIEVENT_MOUSEENTER )
{
if( IsEnabled() ) {
m_uButtonState |= UISTATE_HOT;
Invalidate();
}
// return;
}
if( event.Type == UIEVENT_MOUSELEAVE )
{
if( IsEnabled() ) {
m_uButtonState &= ~UISTATE_HOT;
Invalidate();
}
// return;
}
if( event.Type == UIEVENT_SETCURSOR ) {
::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_HAND)));
return;
}
CLabelUI::DoEvent(event);
}

  同MFC一样,最后也是返回给基类处理必要的操作或者其他没有被处理的消息;我用红色标记了一个Activate()函数:

    bool CButtonUI::Activate()
{
if( !CControlUI::Activate() ) return false;
if( m_pManager != NULL ) m_pManager->SendNotify(this, DUI_MSGTYPE_CLICK);
return true;
}

  重点来了,就是这句红色的标记,就是它实现了消息发送给CPaintManager的对象来处理,而SendNotify()函数循环遍历m_aNotifiers消息接收者指针队列,调用Notify()虚函数,将消息发送给用户类CDuiFrameWnd类来告诉我们鼠标点击了按钮:

  

    if( !bAsync ) {
// Send to all listeners
if( Msg.pSender != NULL ) {
if( Msg.pSender->OnNotify ) Msg.pSender->OnNotify(&Msg);
}
for( int i = ; i < m_aNotifiers.GetSize(); i++ ) {
static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
}
}
    virtual void    Notify(TNotifyUI& msg)
{
if (msg.sType == _T("click"))
{
if (msg.pSender->GetName() == _T("btnClick"))
{
::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL);
}
}
}

  好了,控件的消息路由,就到这里吧!相信同学们认真看完应该没多大问题!

  

  

  

duilib学习领悟(3)的更多相关文章

  1. duilib学习领悟(1)

    学习duilib已经有一段时间,一直没时间写总结,今天得出空来,写写心得体会! 由于本人知识有限,若有错误地方,望批评指正.多谢.! 初识duilib 刚开始接触duilib的时候,觉的他好神奇,整个 ...

  2. duilib学习领悟(4)

    使用duilib创建的主窗口绘制工作全都发生在一个 真实存在的主窗口句柄当中,这个绘制过程稍稍有些复杂,但再复杂也逃不过WM_PAINT消息,所有的绘制工作也都由这个WM_PAINT消息完成.在上几篇 ...

  3. duilib学习领悟(2)

    再次强调,duilib只不过是一种思想! 在上一节中,我剖析了duilib中窗口类的注册,其中遗留两个小问题没有细说的? 第一个问题:过程函数中__WndProc()中有这么一小段代码: pThis ...

  4. DuiLib学习笔记(二) 扩展CScrollbar属性

    DuiLib学习笔记(二) 扩展CScrollbar属性 Duilib的滚动条滑块默认最小值为滚动条的高度(HScrollbar)或者宽度(VScrollbar).并且这个值默认为16.当采用系统样式 ...

  5. Duilib学习笔记《06》— 窗体基类WindowImpBase

    在前面的例子中我们发现,窗口都是继承CWindowWnd.INotifyUI,然后重载相关函数去实现.显然,我们发现窗口的创建流程实际上都是差不多的,主要只是在OnCreate加载的配置文件不同等等… ...

  6. Duilib学习笔记《05》— 消息响应处理

    在Duilib学习笔记<04>中已经知道了如何将窗体显示出来,而如何处理窗体上的事件.消息呢? 一. 系统消息 窗体显示的时候我们就已经说了,窗体是继承CWindowWnd类的,对于窗体的 ...

  7. Duilib学习笔记《04》— 窗体显示

    在前面已经了解了duilib控件以及界面布局相关内容,接下来就要考虑该如何将xml中描述的布局通过界面展现出来.实际上在 Duilib学习笔记<01> 中我们已经简单提到过基本的流程及元素 ...

  8. Duilib学习笔记《03》— 控件使用

    在前面已经对duilib有个一个基本的了解,并且创建了简单的空白窗体.这仅仅只是一个开始,如何去创建一个绚丽多彩的界面呢?这就需要一些控件元素(按钮.文本框.列表框等等)来完善. 一. Duilib控 ...

  9. duilib学习 --- 360demo 学习

    我想通过360demo的学习,大概就能把握duilib的一般用法,同时引申出一些普遍问题,和普遍解决方法.并在此分享一些链接和更多内容的深入学习..... 原谅我是一个菜鸟,什么都想知道得清清楚楚.. ...

随机推荐

  1. extentreports报告插件与testng集成

    前段时间在群里有人说了下用这个插件来生成测试报告,发现生成的报告非常不错.就下来学习了一下,并集成到了testng上,下面来分享一下: ExtentReports (by Anshoo Arora) ...

  2. sql中能使用charindex 不要用 in 。charindex比in快很多

    写SQL语句我们经常需要判断一个字符串中是否包含另一个字符串,但是SQL SERVER中并没有像C#提供了Contains函数,不过SQL SERVER中提供了一个叫CHAEINDX的函数,顾名思义就 ...

  3. logback的xml配置文件模板(超详细)

    <?xml version="1.0" encoding="UTF-8" ?> <!-- 在此未说明属性为非必须的,那就表示属性必须设置 -- ...

  4. Postman和jmeter的区别

    1.创建接口用例集(没区别) Postman是Collections,Jmeter是线程组,没什么区别. 2.步骤的实现(有区别) Postman和jmeter都是创建http请求 区别1:postm ...

  5. [MA] 有关 Likelihood

    当提到 Linear Regression 或是 Logistic regression 等关键词时,都会涉及一个概念,叫做 Likelihood Function 以及 Maximum Likeli ...

  6. CSP-S初赛

    初赛都过了好几天了,现在才想起来写点关于初赛的博客也真是...... 我是福建人,是在福建的赛点参加的CSP-S组的初赛,能力其实很弱,估分只能60多一点点.真是害怕一不小心这篇博客就变成了我的退役博 ...

  7. python 手机App数据抓取实战一

    前言 当前手机使用成为互联网主流,每天手机App产生大量数据,学习爬虫的人也不能只会爬取网页数据,我们需要学习如何从手机 APP 中获取数据,本文就以豆果美食为例,讲诉爬取手机App的流程 环境准备 ...

  8. T100——错误信息提示传入参数显示

    LET l_str1 = l_xccc.xccc901LET l_str2 = l_inat015LET l_str = l_str1.trim(),'|',l_str2.trim() CALL cl ...

  9. 安装本地jar包

    (1)安装在本地maven库 假设我们需要引入的包为 myjar-1.0.jar (1.1)打开cmd,进入myjar-1.0.jar所在的目录 (1.2)执行如下命令:mvn install:ins ...

  10. VS2019打开项目加载失败:无法找到 .NET Core SDK。请检查确保已安装此项且 global.json 中指定的版本(如有)与所安装的版本相匹配。

    问题描述: 用VS2019创建了asp.net core项目,正常运行:过几天后,再次打开,发现无法加载项目,报错无法找到.net core sdk.   分析过程: 首先怀疑环境变量的问题,重新设置 ...