Using Duilib to build GUI

Duilib is a free open source DirectUI library on Windows , because its simple and expandable design and stable and efficient implementation, accepted generally by the major Internet companies widely used in IM, video client, stock market software, navigation software, mobile phone support Software, security software and many PC client software in other industries. When We developed a IM in a company I worked, we even use this library to replace Qt GUI part we used in our code.

Basic principle

The native Windows user interface is composed of multiple WND, each WND has its own Handle and WndProc, while the DirectUI is directly drawn on the parent window (Paint on parent DC). That is, the child window is not created in the form of a window handle (windowless), but a logical window, using the GDI function to draw on the parent window. There is no extra WND, so the controls are all virtual, do not have their own message loop, they are using WND message loop. When the WND receives the message, it will locate the control and then passes the message to the specific control, which will response this message.

Class architecture of library

Basic library

Tool library

DUILib is not depends on any other external library, so it implement basic tool library internally to support this project.

  • UI related: CPoint, CSize, CDuiRect

  • Simple Container: CStdPtrArray, CStdValArray, CStdString, CStdStringPtrMap

Control library

Control library has two parts to be implemented seperately in DUILib.

  • Core - contains common parts all controls used and basic class.

  • Control - contains all real(instantiaed) controls.

Message process mechanism

Basic process

The DUILib framework’s basic message process similar to create window process in WIN32.

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);        // 1, Instantiate handle and attach to render
    CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T("skin"));
    HRESULT Hr = ::CoInitialize(NULL);              // 2,Initialize COM, and support to load COM
    if( FAILED(Hr) ) return 0;
    CMainFrameWnd* pFrame = new CMainFrameWnd();    // 3,Create window class
    if( pFrame == NULL ) return 0;
    pFrame->Create(NULL, _T("Main"), UI_WNDSTYLE_FRAME, 0L, 0, 0, 800, 600); // 4, Register window class and create window instance.
    // Actually call Create to create a Win32 WND, do these operations internally:
    //  -> RegisterSuperclass (Register a super class)
    //  -> RegisterWindowClass (Register window class)
    //  -> ::CreateWindowEx (Create window,trigger WM_CREATE)
    //  -> HandleMessage  ( WM_CREATE process OnCreate)

    pFrame->CenterWindow();          // 5, Center window
    ::ShowWindow(*pFrame, SW_SHOW);
    CPaintManagerUI::MessageLoop(); // 6, Process message loop
    ::CoUninitialize();            // 7, Exit program and release COM
    return 0;
}
  • Main message loop - CPaintManagerUI::MessageLoop, message will be first received by CPaintManagerUI’s TranslateMessage.
 void CPaintManagerUI::MessageLoop()
 {
     MSG msg = { 0 };
     while( ::GetMessage(&msg, NULL, 0, 0) ) {    
         if( !CPaintManagerUI::TranslateMessage(&msg) ) { 
             ::TranslateMessage(&msg);
             ::DispatchMessage(&msg); 
         }
     }
 }
  • Create window - call CWindowWnd::Create to create window, and finish operations below:

    • If subclass system control(not DUILib control), set __ControlProc function as callback function.

    • If not subclass, set __WndProc function as callback function.

    • Use CreateWindowEx to create window instance.

  • __WndProc

LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CWindowWnd* pThis = NULL;
    if( uMsg == WM_NCCREATE ) { // Bind class and window in this message
        LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);   // From CreateWindowEx's last parameter( ptr to CWindowWnd )
        pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
        pThis->m_hWnd = hWnd;
        ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis)); // Set to window's user data
    }
    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);    // Cancel class and window's bind relation
            if( pThis->m_bSubclassed ) pThis->Unsubclass();
            pThis->m_hWnd = NULL;
            pThis->OnFinalMessage(hWnd);
            return lRes;
        }
    }
    if( pThis != NULL ) {
        return pThis->HandleMessage(uMsg, wParam, lParam);  // Call subclass's messge process function
    }
    else {
        return ::DefWindowProc(hWnd, uMsg, wParam, lParam); 
    }
}
  • Call CWindowWnd virtual function to subclass message.

  • Event - DUILlib encapsulated message to be event and dispatch it to control.

  • Notify - If want window to process some control’s message, call this function, let control notify window, while window will register itself in Notify receiver queue in its OnCreate.

Using tips

  • Inheriting the WindowImplBase class, then implement its unimplemented virtual metheds to satify specified situation;

  • WindowImplBase class already deal with some windows message by default,if some can’t satify specified situation,override it;

  • If some windows messages have been not dealt with by WindowImplBase class, deal with in HandleCustomMessage method;

  • To use DUI_DECLARE_MESSAGE_MAP macro map message functon;

  • Using CDialogBuilder to create custom control or container from xml file;

  • Remember this point : Control and custom UI in xml file always a good choice in most of case;

  • Organizing whole UI element in separated xml files and do not conflit with C++ class inheritance through CreateControl method;