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

  

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

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. Linux精简版系统安装网络配置问题解决

    参考文档:https://www.jianshu.com/p/7579a2ad1c92 通过链接中的文档配置linux系统的时候,在执行命令yum install net-tools的这里提示错误,是 ...

  2. c++/c在两个文件公用一个变量

    在一个cpp文件定义一个文件 在另一个文件extern+定义

  3. Hibernate的入门Curd用法

    今天分享的是hibernate关系映射框架的入门用法 一:Hibernate简介 Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建 ...

  4. Spring 后台方法 重定向 与 转发

    一.重定向:重定向是客户端行为,在使用时,务必使用全路径,否则可能因为外部环境导致错误 1.URL改变为重定向的URL地址 2.前台页面不能使用Ajax请求提交, 应该使用form表单提交 方法一.参 ...

  5. SQL 不同服务器数据库操作

    https://www.cnblogs.com/lusunqing/p/3660190.html --创建远程链接服务器 execute sys.sp_addlinkedserver @server= ...

  6. MVC5项目转.Net Core 2.2学习与填坑记录(1)

    流程都是自己摸索,错误地方随便指正... 老项目过于臃肿,并且所有请求都是提交到一个api中,这样当api挂掉的时候,基本所有的项目都瘫痪掉了. 在4月底的时候,下决心将项目用微服务进行重写,刚开始的 ...

  7. 菜鸡之NetCore 使用EF操作数据库 Oracle & Sqlserver (一)

    摘要: 该篇文章主要记录netCore EFCore 如何操作Oracle和SqlServer 数据库,采用Codefirst方式创建数据库以及表. 一, 项目建立 项目采用DDD领域驱动设计模式[学 ...

  8. matlab cell

    cell元包是matlab中提供的一种数据类型,功能强大. 关于cell的创建: 1.跟一般创建举证一样,直接使用C = {A B D E}这种形式,不过这里把"[]"改成了}&q ...

  9. GraphQL实战篇(一)

    看过基础篇的都知道,GraphQL创建Schema有两种方式,Schema First和Graph Type,前者使用GraphQL Schema Language类似于EF的DB First:后者和 ...

  10. class类 - static

    不需要实例化类,即可直接通过该类来调用的方法,即称之为"静态方法".将类中的方法设为静态方法也很简单,在方法前加上static关键字即可.这样该方法就不会被实例继承! class ...