天天看點

duilib基本架構

最近我一個同學在項目中使用到了duilib架構,但是之前并沒有接觸過,他與我讨論這方面的内容,看着官方給出的精美的例子,我對這個庫有了很大的興趣,我自己也是初學這個東東,我在網上花了不少時間來找相關的資料,但是找到的不多,官方給的文檔又不全面,但是我還是找到了一些部落客貢獻的優秀的博文,現在我是通過博文上的講解加上自己檢視源代碼的一些心得,正在艱難的前行。現在正在看的是部落客Alberl在部落格園中的duilib基礎教程中的内容,下面的代碼都是在他部落格中給出代碼的基礎上做了一點小小的修改。​​​​

duilib的簡介

國内首個開源 的directui 界面庫,它提供了一個所見即所得的開發工具——UIDesigner,它隻有主架構視窗,其餘的空間全部采用繪制的方式實作,是以對于控件來說沒有句柄和視窗類等内容,它通過UIDesigner工具将使用者定義的視窗儲存在xml檔案中,在建立視窗時讀取xml檔案中的内容,來繪制相應的控件。目前有許多界面采用duilib編寫,大家可以去網上搜集相關資料。

環境的配置

首先我們去github上擷取相關的源代碼,這個是對應的項目位址:​​https://github.com/duilib/duilib​​​

下載下傳完後,在目錄中找到一個.sln結尾的檔案,使用visual studio編譯器打開,打開後發現有一個duilib的項目,以及其他,其實真正有用的就是這個duilib,其餘的都是官方給出的例子代碼。一般隻需要編譯這個duilib項目就可以了,當初沒注意直接點了編譯全部的,結果報了一堆錯誤,其實都是沒有對應的lib和dll檔案造成的。在VS環境下有一個編譯選項,如下圖所示

duilib基本架構

上面有4個編譯選項,最好将所有的都編譯一遍,這樣在對應項目的bin目錄下會生成四個dll檔案,這幾個檔案分别是debug下的UNICODE編碼檔案、ANSI檔案以及Release版本下的UNICODE編碼檔案、ANSI檔案。u代表unicode d代表debug。另外在lib目錄下會生成對應的lib檔案。

在建立的工程中,點選屬性在屬性對話框中選擇VC++目錄,在源檔案,庫檔案,包含檔案中将對應的路徑添加進去,分别是項目目錄和lib檔案目錄。如下圖(剛開始有點問題是以添加的内容有點多,但是不影響正常使用):

duilib基本架構

最後可以在環境變量的Path變量中添加對應的dll路徑,這樣就不需要将dll檔案拷貝到自己項目的exe檔案所在位置處。

其實上述的環境可以不用設定,如果不設定,在編寫程式包含相關路徑時就需要給定完整的路徑。

到此處為止,整個開發環境就已經搭建好了,剩下的就是代碼的編寫了。

基本的架構視窗

首先建立一個Win32類型的項目,添加主函數。然後建立一個新類,我們叫做CDuiFrameWnd,下面是類的源代碼

//頭檔案
#include 
using namespace DuiLib;
class CDuiFrameWnd :
    public CWindowWnd
{
public:
    CDuiFrameWnd();
    ~CDuiFrameWnd();
    virtual LPCTSTR GetWindowClassName() const;
    virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
protected:
    CPaintManagerUI m_PaintManager;
};      
//cpp檔案
LPCTSTR CDuiFrameWnd::GetWindowClassName() const
{
    return _T("DuiFrameWnd");
}
LRESULT CDuiFrameWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT lRes = 0;
    if (WM_CLOSE == uMsg)
    {
        ::CloseWindow(m_hWnd);
        ::DestroyWindow(m_hWnd);
    }

    if (WM_DESTROY == uMsg)
    {
        ::PostQuitMessage(0);
    }
    return __super::HandleMessage(uMsg, wParam, lParam);
}      

為了能夠使用對應的dll檔案,還需要引入對應的lib檔案,我們在公共的頭檔案中加入如下代碼

#ifdef _DEBUG
#   ifdef _UNICODE
#       pragma comment(lib, "Duilib_ud.lib")
#   else
#       pragma comment(lib, "Duilib_d.lib")
#   endif
#else
#   ifdef _UNICODE
#       pragma comment(lib, "Duilib_u.lib")
#   else
#       pragma comment(lib, "Duilib.lib")
#   endif
#endif      

在主函數中的代碼如下:

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);
    CDuiFrameWnd duiFrame;

//#define UI_WNDSTYLE_FRAME      (WS_VISIBLE | WS_OVERLAPPEDWINDOW)
    duiFrame.Create(NULL, _T("測試"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    duiFrame.ShowWindow();
    CPaintManagerUI::MessageLoop();
    return 0;
}      

這些代碼就可以幫助我們生成基本的架構視窗,另外我們需要時刻記住的是duilib是對win32 API的封裝,是以可以直接使用win32的程式設計方式,如果以後有不會用的地方完全可以使用win32 的API來完成相關的功能的編寫。

架構的剖析

既然它能夠生成單文檔的架構視窗,那麼代碼中所做的幾步基本上與用純粹的win32 API相同,是以我們沿着這個思路來進行架構的簡單剖析。

主函數中首先是代碼CPaintManagerUI::SetInstance(hInstance);至于類CPaintManagerUI到底有什麼作用,這個我也不太清楚,現在我還沒有仔細看關于這個類的相關代碼,這句話主要還是擷取了程序的執行個體句柄。現在先不關心這個。下面的幾步主要是在類CDuiFrameWnd中完成或者說在它的基類CWindowWnd中完成。

建立視窗類

主函數中的第二段代碼主要完成的是類CDuiFrameWnd對象的建立,我們跟到對應的構造函數中發現它并沒有做多餘的操作,現在先不管它是如何構造的,它下面就是調用了類的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;
}      

我們主要來看第二個if中的代碼,首先獲得了父視窗的字元串為NULL,然後執行RegisterWindowClass,我們進一步跟到RegisterWindowClass中,它的代碼如下:

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;
}      

我們發現首先進行的是視窗類的建立,在建立視窗類時主要關心的是視窗類的lpfnWndProc成員和lpszClassName 。lpszClassName 調用了函數GetWindowClassName,這個函數我們在派生類中進行了重寫,是以根據多态它會調用派生類的GetWindowClassName函數,将我們給定的字元串作為視窗類的類名

注冊視窗類

從上面的代碼可以看出注冊的代碼也是放在RegisterWindowClass中。在最後調用了RegisterClass函數完成了注冊。

建立視窗

當RegisterWindowClass執行完成後,會接着執行下面的代碼,也就是 m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);完成建立視窗的任務。

顯示視窗

Create函數執行完成後,會接着執行下面的duiFrame.ShowWindow();我們跟到這個函數中,函數代碼如下:

void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)
{
    ASSERT(::IsWindow(m_hWnd));
    if( !::IsWindow(m_hWnd) ) return;
    ::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);
}      

函數ShowWindow預設傳入參數為bShow = true bTakeFocus = false;在最後進行ShowWindow函數的調用時,根據bShow和bTakeFocus來進行值得傳入,根據代碼我們發現,當不傳入參數時調用的其實是這樣的代碼ShowWindow(m_hWnd, SW_SHOWNOACTIVATE);

消息循環

消息循環其實是通過代碼CPaintManagerUI::MessageLoop();完成,我們跟到MessageLoop函數中看

MSG msg = { 0 };
    while( ::GetMessage(&msg, NULL, 0, 0) ) {
        if( !CPaintManagerUI::TranslateMessage(&msg) ) {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
    }      

在這個函數中完成了消息循環。

回調函數

上面我們留了一個lpfnWndProc函數指針沒有說,現在來說明這個部分,跟進到對應的構造函數中,發現類本身不做任何操作,但是父類的構造函數進行了相關的初始化操作,下面是對應的代碼

CWindowWnd::CWindowWnd() : m_hWnd(NULL), m_OldWndProc(::DefWindowProc), m_bSubclassed(false)
{
}      

這樣就将lpfnWndProc指向了__WndProc,用于處理預設的消息。

這是一個靜态的處理函數,下面是它的代碼:

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;
        //當開始建立視窗将視窗類對象的指針放入到對應的GWLP_USERDATA字段中
        ::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);
    }
}      

總結