美文网首页
2.duilib消息处理简单理解

2.duilib消息处理简单理解

作者: _张鹏鹏_ | 来源:发表于2021-08-02 15:24 被阅读0次

    基于之前对windows通信的大致了解网友Alberl的教程。我修改了下Alberl在他教程中的HelloWorld程序,添加了两个按键。

    使用duilib时,一般在Notify里面处理各个控件的消息,HandleMessage里面一般是创建窗口。刚开始看到这个代码的时候,有几个疑问:

    1.框架是怎么调用这两个接口的?

    2.都是处理消息为啥搞两个接口,这两个有啥区别?

    class CDuiFrameWnd : public CWindowWnd, private INotifyUI
    {
    public:
        virtual LPCTSTR GetWindowClassName() const
        {
            return _T("DUIMainFrame4444");
        }
        virtual void    Notify(TNotifyUI& msg)
        {
            if (msg.sType == _T("click"))
            {
                if (msg.pSender->GetName() == _T("btnHello1"))
                {
                    ::MessageBox(NULL, _T("我是按钮1"),_T("点击了按钮1"),NULL);
                }
    
                if (msg.pSender->GetName() == _T("btnHello2"))
                {
                    ::MessageBox(NULL, _T("我是按钮2"), _T("点击了按钮2"), NULL);
                }
            }
        }
    
        virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
        {
            LRESULT lRes = 0;
    
            if (uMsg == WM_CREATE)
            {
                m_PaintManager.Init(m_hWnd);  // 关联ui管理器;  m_hWnd --->CreateWindowEx时操作系统分配的窗口句柄; 
                CControlUI *pVLayout = new CVerticalLayoutUI;
                pVLayout->SetName(_T("mainVertical"));
                pVLayout->SetBkColor(0xFF00FF00);
                m_PaintManager.AttachDialog(pVLayout); // CPaintManagerUI成员部分重新初始化操作
                m_PaintManager.AddNotifier(this);
    
                auto mainVertical = (DuiLib::CVerticalLayoutUI*)m_PaintManager.FindControl(_T("mainVertical"));
    
                // 
                CControlUI *pButton1 = new CButtonUI;
                pButton1->SetFloat();
                SIZE leftTop = { 30, 80};
                pButton1->SetFixedXY(leftTop);
                pButton1->SetFixedWidth(70);
                pButton1->SetFixedHeight(30);
    
                pButton1->SetText(_T("btnHello1"));   // 设置文字
                pButton1->SetBkColor(0xFF00FFFF);       // 设置背景色
                pButton1->SetName(_T("btnHello1"));
                mainVertical->Add(pButton1);
    
                //
                CControlUI *pButton2 = new CButtonUI;
                pButton2->SetFloat();
                SIZE leftTop1 = { 120, 80 };
                pButton2->SetFixedXY(leftTop1);
                pButton2->SetFixedWidth(70);
                pButton2->SetFixedHeight(40);
    
                pButton2->SetText(_T("btnHello2"));   // 设置文字
                pButton2->SetBkColor(0xFF00FFFF);       // 设置背景色
                pButton2->SetName(_T("btnHello2"));
                mainVertical->Add(pButton2);
                return lRes;
            }
    
            if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))
            {
                return lRes;
            }
            return __super::HandleMessage(uMsg, wParam, lParam);
        }
    
    protected:
        CPaintManagerUI m_PaintManager;
    };
    
    int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
    {
        CPaintManagerUI::SetInstance(hInstance);  // 应用程序分配的实例句柄;
    
        CDuiFrameWnd duiFrame;
        // 注册窗口类、创建窗口实例;
        duiFrame.Create(NULL, _T("DUIWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
        duiFrame.ShowModal();  // 消息循环; 接收、处理windows消息
        return 0;
    }
    

    带着这些疑问,走读源码,梳理如下:

    duiFrame.Create会进行窗口类注册窗口实例创建

    HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
    {
        if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
        if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
        m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
        ASSERT(m_hWnd!=NULL);
        return m_hWnd;
    }
    

    注册窗口过程的回调函数是CWindowWnd::__WndProc,当windows给窗口发送消息时会调用到这个回调函数;

    这里CreateWindowEx的最后一个参数LPVOID lpParam被赋值为thisduiFrame对象的地址,这个十分重要,后面会用到;

    这是窗口过程的代码:

    LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        CWindowWnd* pThis = NULL;
        if( uMsg == WM_NCCREATE ) {
            // 很重要;
            LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
            pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);  // pThis实际指向duiFrame
            pThis->m_hWnd = hWnd;
            ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
        } 
        else {
            // pThis实际指向duiFrame
            pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
            if( uMsg == WM_NCDESTROY && pThis != NULL ) {
                LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
                ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
                if( pThis->m_bSubclassed ) pThis->Unsubclass();
                pThis->m_hWnd = NULL;
                pThis->OnFinalMessage(hWnd);
                return lRes;
            }
        }
        if( pThis != NULL ) {
            return pThis->HandleMessage(uMsg, wParam, lParam);
        } 
        else {
            return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
    }
    

    WM_NCCREATE消息在WM_CREATE消息之前发送,lParam携带LPCREATESTRUCT结构体地址,该结构体的成员lpCreateParams 为调用CreateWindowEx时传入的最后一个参数,即duiFrame对象的地址。拿到这个地址后,就能够调用CDuiFrameWnd类的HandleMessage方法。上述就是HandleMessage方法的调用过程。

    在这个方法里面,我们处理了WM_CREATE消息,我们创建了一个垂直布局和两个按键。m_PaintManager.AddNotifier是添加控件等消息响应,这样消息就会传达到duilib的消息循环,我们可以在Notify函数里做消息处理。

    m_PaintManager.AttachDialog(pVLayout)做了很多工作,其中InitControls用于设置控件的父节点和该控件所属的UI管理实例(即CPaintManagerUI)。

    mainVertical->Add(pButton1);也是这个作用,这个很重要,Notify(TNotifyUI& msg)的调用依赖于这步配置。

        virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
        {
            LRESULT lRes = 0;
    
            if (uMsg == WM_CREATE)
            {
                m_PaintManager.Init(m_hWnd);  // 关联ui管理器;  m_hWnd --->CreateWindowEx时操作系统分配的窗口句柄; 
                CControlUI *pVLayout = new CVerticalLayoutUI;
                pVLayout->SetName(_T("mainVertical"));
                pVLayout->SetBkColor(0xFF00FF00);
                m_PaintManager.AttachDialog(pVLayout); // CPaintManagerUI成员部分重新初始化操作
                m_PaintManager.AddNotifier(this);
    
                auto mainVertical = (DuiLib::CVerticalLayoutUI*)m_PaintManager.FindControl(_T("mainVertical"));
    
                // 
                CControlUI *pButton1 = new CButtonUI;
                pButton1->SetFloat();
                SIZE leftTop = { 30, 80};
                pButton1->SetFixedXY(leftTop);
                pButton1->SetFixedWidth(70);
                pButton1->SetFixedHeight(30);
    
                pButton1->SetText(_T("btnHello1"));   // 设置文字
                pButton1->SetBkColor(0xFF00FFFF);       // 设置背景色
                pButton1->SetName(_T("btnHello1"));
                mainVertical->Add(pButton1);
    
                //
                CControlUI *pButton2 = new CButtonUI;
                pButton2->SetFloat();
                SIZE leftTop1 = { 120, 80 };
                pButton2->SetFixedXY(leftTop1);
                pButton2->SetFixedWidth(70);
                pButton2->SetFixedHeight(40);
    
                pButton2->SetText(_T("btnHello2"));   // 设置文字
                pButton2->SetBkColor(0xFF00FFFF);       // 设置背景色
                pButton2->SetName(_T("btnHello2"));
                mainVertical->Add(pButton2);
                return lRes;
            }
    
            if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))
            {
                return lRes;
            }
            return __super::HandleMessage(uMsg, wParam, lParam);
        }
    

    当点击按键时,windows系统先后发送WM_LBUTTONDOWNWM_LBUTTONUP消息。消息被投递到窗口过程 CWindowWnd::__WndProcCDuiFrameWnd类的HandleMessage再次被调用,消息会投递到m_PaintManager.MessageHandler进行处理。

    在这里,duilib会根据坐标的位置,得到控件实例。本例中即按钮控件对象的地址。按钮对象知道自己所属的UI和父控件(初始化时设置的)。这样就调用到Notify了。

    因此Notify方法的调用,是先调用HandleMessage而来,然后在该方法里面再调回到Notify
    个人感觉,从使用来说HandleMessage里面一般处理窗口、控件创建,Notify中处理各个控件产生的事件。

    参考文献:

    1. duilib简明教程
    2. duilib进阶教程
    3. duilib使用心得
    4. duilib各种布局的作用,相对布局与绝对布局的的意义与用法_Redrain的专栏-CSDN博客
    5. Duilib教程-自动布局2 - 夜雨無聲 - 博客园 (cnblogs.com)
    6. duilib开发(六):基本控件介绍_yp18792574062的博客-CSDN博客_duilib radio
    7. duilib开发(十):动态添加控件_yp18792574062的博客-CSDN博客
    8. DUILIB UI创建过程 - Guozht - 博客园 (cnblogs.com
    9. duilib库框架介绍_架构师之路-CSDN博客_duilib框架
    10. DuiLib源码分析_万千知了的博客

    相关文章

      网友评论

          本文标题:2.duilib消息处理简单理解

          本文链接:https://www.haomeiwen.com/subject/bkcjvltx.html