zoukankan      html  css  js  c++  java
  • duilib学习领悟(1)

      学习duilib已经有一段时间,一直没时间写总结,今天得出空来,写写心得体会!

      由于本人知识有限,若有错误地方,望批评指正.多谢.!

      

      初识duilib

      刚开始接触duilib的时候,觉的他好神奇,整个界面只有一个句柄,怎么控制子控件的?怎么布局的?觉得很神奇,打算一探究竟!

      其实任何基于Windows的界面开发都离不开五步曲:

    •    注册窗口类
    •      创建窗口
    •      显示更新窗口
    •      消息循环
    •      退出

      duilib也逃不过微软的魔爪! 接下来我会基于这个五步曲逐步接开duilib的神秘面纱!

      对diulib熟悉的人,不会觉的duilib有多神秘,so do I! 因为总的来讲,duilib只不过是一种思想,什么思想?

      duilib,从本质来讲,就是在一个真实的窗口(_tWinMain里创建的窗口)之上画出各种控件!  这就是duilib的思想. 这个画不是假画,是真画!实打实的画出来的!有些同学可能急着要问:"既然是画出来的,那窗口是怎么处理消息的? 窗口是如何判断当前鼠标点击是哪一个BUTTON?" 等等各种疑问,接下来 我将剖析duilib的框架,接开这两个疑问!

      问题回到五步曲的第一步:注册窗口类

      duilib将窗口类封装成类CWindowWnd,这个类相当于我们MFC里面的CWnd,里面有窗口类的注册,请看代码:

      

    bool CWindowWnd::RegisterWindowClass()
    {
        WNDCLASS wc = { 0 };
        wc.style = GetClassStyle();
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hIcon = NULL;
        wc.lpfnWndProc = CWindowWnd::__WndProc;//注意这个是回调函数
        wc.hInstance = CPaintManagerUI::GetInstance();
        wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = NULL;
        wc.lpszMenuName  = NULL;
        wc.lpszClassName = GetWindowClassName();
        ATOM ret = ::RegisterClass(&wc);
        ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
        return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
    }

      那么这个

     bool CWindowWnd::RegisterWindowClass()

       注册窗口类的函数是在哪里调用的?

       在同一个源文件中,不难发现如下代码:

      

    HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, const RECT rc, HMENU hMenu)
    {
        return Create(hwndParent, pstrName, dwStyle, dwExStyle, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hMenu);
    }
    
    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;
    }

       那么这个Create函数又是在哪里调用的?请看_tWinMain()里的代码

    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();
        return 0;
    }

       这样我们就完成了整个窗口类的注册流程. 从这里看,duilib封装的CWindowWnd是不是和MFC的CWnd大同小异呢?

       接下来,我带同学们看看CWindowWnd::__WndProc()函数!

       这是专门用来处理窗口消息的过程函数!和CWnd::WindowProc()是一样的!只不过CWnd::WindowProc()是个虚函数,我们可以重载这个虚函数,拦截消息.但是CWindowWnd::__WndProc()并没有被定义为虚函数,那么我们是不是没有办法拦截消息了呢?答案是否定的! duilib采用了另外的机制来处理消息!

    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->m_hWnd = hWnd;
            ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
        } 
        else {
            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);
        }
    }

       

        上面的代码 标红的部分,是可以帮助你理解duilib消息处理机制的最重要部分,不知大家有没有忘记CWindowWnd里的创建窗口的函数Create()中::CreateWindowEx(...,this);函数参数的最后传递的这个this指针!

    这个this对于duilib来说相当重要,它是duilib处理消息的基础,那么这个this到底代码什么呢?它代表的是我们在_tWinMain()函数里声明的CDuiFrameWnd duiFrame;这个对象的地址.这个CDuiFrameWnd派生自哪里?就是派生自CWindowWnd,当然为了处理通知消息它还从INotifyUI派生(这个以后再讲为什么要从这个类派生).所以这个传递进来的this指针的首地址也代表了duiFrame在构造基类的对象时的首地址,请耐心体会这句话.

         在上面的代码段中,过程函数__WndProc()拦截了WM_NCCREATE,这个消息是::CreateWindowEx被调用之后会发送的消息,然后做了什么?就是把这个this指针取出来,然后保存::SetWindowLongPtr()函数.接着else部分就是将这个this从USER_DATA中取出来,取出来干嘛呢?这个this指针有什么用呢?请接着往下看那个if else语句部分,看到这,你可能还是糊涂,"消息还是没流到我的派生类来啊!",我来帮你解答这个问题,我们看pThis->HandleMessage(uMsg, wParam, lParam); 的这个HandleMessage()也许你已经猜到这个函数肯定是个虚函数,要不取出pThis有什么用?是的,你猜的没错,这个函数就是个虚函数,直指我们的的派生类CDuiFrameWnd重载函数HandleMessage(uMsg, wParam, lParam);也许有同学会问,我不用this指针行不行,我不想把这个this指针保存在USER_DATA里!请注意这个过程函数是个静态的,它不属于任何对象,静态函数要访问非静态的数据,必须通过pThis指针!这是C++的特性!

  • 相关阅读:
    【Alpha】技术规格说明书
    【Alpha】第十次Scrum meeting
    【Alpha】第九次Scrum meeting
    【Alpha】第八次Scrum meeting
    【Alpha】第七次Scrum meeting
    【Alpha】特殊情况通知
    S2X环境搭建与示例运行
    [2018福大至诚软工助教]结对作业1测试结果
    机器学习
    机器学习
  • 原文地址:https://www.cnblogs.com/xiejiulong/p/3792457.html
Copyright © 2011-2022 走看看