天天看點

duilib入門問題集

引入duilib時請確定引入頭檔案開始時先引入COMUTIL.H頭檔案

#include "COMUTIL.H"

#include "UIlib.h"

duilib基本程式結構

在stdafx.h檔案中加入

<span style="font-size:18px;">#include "COMUTIL.H"
#include "UIlib.h"
using namespace DuiLib;</span>
           
<span style="font-size:18px;">#ifndef LoginView_h__
#define LoginView_h__


class LoginView :
    public WindowImplBase// 隻能放在最後,否則其消息路由出問題
{


public:
    DUI_DECLARE_MESSAGE_MAP()
public:
    LoginView(void);
    ~LoginView(void);


    void on_btn_click( DuiLib::TNotifyUI &msg );


    virtual CDuiString GetSkinFolder() { return _T ("skin\\"); }
    virtual CDuiString GetSkinFile() { return _T("login.xml"); }
    virtual LPCTSTR GetWindowClassName( void ) const { return _T("LoginView"); }
};


#endif // LoginView_h__</span>
           
<span style="font-size:18px;">#include "StdAfx.h"
#include "LoginView.h"

DUI_BEGIN_MESSAGE_MAP(LoginView, WindowImplBase)
DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK,on_btn_click)
DUI_END_MESSAGE_MAP()


LoginView::LoginView(void)
{
}

LoginView::~LoginView(void)
{
}


void LoginView::on_btn_click( DuiLib::TNotifyUI &msg )
{

}</span>
           
<span style="font-size:18px;">// chatme.cpp : 定義應用程式的入口點。
//

#include "stdafx.h"
#include "chatme.h"

#include "LoginView.h"

int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);
    CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());


    HRESULT Hr = ::CoInitialize(NULL);
    if( FAILED(Hr) ) return 0;

    LoginView login_view;
    login_view.Create(NULL, _T("LoginView"), UI_WNDSTYLE_DIALOG, WS_EX_STATICEDGE | WS_EX_APPWINDOW);

    login_view.CenterWindow();

    CPaintManagerUI::MessageLoop();
    ::CoUninitialize();
    return 0;
}</span>
           

問:如何把資源放入zip?

答: 先SetResourcePath設定資源目錄,再SetResourceZip設定壓縮資源檔案名

問:如何設定窗體的初始化大小?

答:設定XML檔案的Window标簽的size屬性。

問:如何設定滑鼠可拖動窗體的範圍大小?

答:設定XML檔案的Window标簽的caption屬性。

問:如何設定窗體可以通過拖動邊緣改變大小?

答:在窗體建立函數的第三個參數設定為UI_WNDSTYLE_FRAME才可響應拖動改變大小,和輕按兩下标題事件。

問:為何滑鼠移動到邊緣沒有改變窗體大小的箭頭出現,不能通過拖動改變窗體大小?

答:設定window标簽的sizebox屬性,例如sizebox="2,2,2,2"

問:窗體不可輕按兩下最大化如何實作?

答:在窗體建立函數的第三個參數設定為UI_WNDSTYLE_DIALOG。

問:應用程式exe圖示如何設定?

答:使用窗體成員函數SetIcon,參數為資源icon的id。

問:初始化時,最大化窗體如何實作?

答:調用窗體的SendMessage給窗體發送最大化消息SC_MAXIMIZE,SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE,0);

問:動态改變窗體的大小如何實作?

答:使用窗體函數ResizeClient,參數分别重設的寬和高。

問:如何設定窗體螢幕居中顯示?

答:使用窗體的CenterWindow函數。

問:窗體透明度如何設定?

答:設定window标簽屬性bktrans="true" alpha="200" alpha的值為0-255。這種設定是全體窗體透明度,所有控件都将變透明。

如果想單純設定背景透明度控件不透明度,可以制作半透明的背景圖檔,設定window标簽的bktrans="true",并且不設定alpha屬性,切記!此時背景透明,其它控件不透明。

單獨設定某個控件的透明度,可以使用圖檔的fade屬性,或者mask屬性。fade表示設定圖檔透明度,取值0-255。mask為設定透明的顔色。

問:預設設定的圖檔為拉伸平鋪模式,如何設定不拉伸顯示?

答:設定圖檔的source和dest屬性,soure="左,上,右,下" dest="左,上,右,下" 

表示将source區域的圖檔顯示到按鈕的dest區域上。這裡的右和下是指坐标,不是指寬度和高度。右=左+width.下=top+height。

問:如何設定標明編輯框文字的背景顔色?

答:設定nativebkcolor屬性。

問:如何設定按鈕的滑鼠懸浮時的字型顔色?

答:設定按鈕的hottextcolor屬性,相對的還有pushedtextcolor和focustextcolor.

問:如何設定按鈕按下時字型的顔色?

答:設定按鈕的pushedtextcolor屬性,相對的還有hottextcolor和focustextcolor.

問:如何自定義xml控件?

答:自定義控件和複雜的控件類型都是由簡單基本控件組成。

在寫好一個自定義的控件xml模闆後,

CDialogBuilder dlg_builder;

CControlUI * pControl = dlg_builder.Create("item.xml");

注意這裡的item.xml要放在主界面的xml所在的檔案夾内,并且無需在指定路徑了。

該函數傳回一個CControlUI的一個句柄,得到這樣一個句柄就是一個控件了。

如果要擷取複雜控件的某個子控件的句柄,然後想通過該句柄改變子控件的狀态。

首先給這個子控件取一個名字,然後可以通過pControl的FindSubControl("name")來擷取該

控件的句柄了。得到句柄後就可以設定它的所有屬性了,例如

pbtn->SetAttribute(_T("normalimage"), _T("file='images\\downlist_ok.png' dest='20,14,32,26'"));就能更改它的狀态圖檔了。

問:多線程下如何更改dui的界面資訊?

答:線程裡不要操作界面的資訊,應該通過SendMessage或者PostMessage給界面的m_hWnd發送自定義消息。然後在界面的消息循環裡面在做操作界面的動作。

自定義處理的消息處理函數可以從重寫方法

LRESULT MyWnd::HandleCustomMessage( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )

{

//這裡處理完後,bHandled置true 傳回基類,讓基類去操作

return WindowImplBase::HandleCustomMessage(uMsg, wParam, lParam, bHandled);

}

具體可以如下使用:

#define ON_PERCENT_MSG              WM_USER + 500

然後線上程函數中發送消息給界面

int DownloadView::on_percent( double percent, int index, INT_PTR user_data )

{

    ::SendMessage(m_hWnd, ON_PERCENT_MSG, (WPARAM)&percent, (LPARAM)user_data);

    return 0;

}

最後在界面消息循環中進行處理消息

LRESULT DownloadView::HandleCustomMessage( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )

{

    switch (uMsg)

    {

    case ON_PERCENT_MSG:

         // 處理界面相關的操作

        break;

    default:

        break;

    }

    return WindowImplBase::HandleCustomMessage(uMsg, wParam, lParam, bHandled);

}

問:如何讓使用duilib的win32工程支援MFC?

答:

1、在stdafx.h加入以下

#define VC_EXTRALEAN

#include <afxwin.h>         // MFC 核心元件和标準元件

#include <afxext.h>         // MFC 擴充

#include <afxdisp.h>        // MFC 自動化類

#ifndef _AFX_NO_OLE_SUPPORT

#include <afxdtctl.h> // MFC 對 Internet Explorer 4 公共控件的支援

#endif

#ifndef _AFX_NO_AFXCMN_SUPPORT

#include <afxcmn.h> // MFC 對 Windows 公共控件的支援

#endif // _AFX_NO_AFXCMN_SUPPORT

2、在程式初始化main的開始加入mfc的初始化。

// initialize MFC and print and error on failure

if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))

{

// TODO: change error code to suit your needs

_tprintf(_T("Fatal Error: MFC initialization failed\n"));

return 1;

}

3、設定頁屬性-->正常-->使用mfc設定為 Use MFC in a Shared DLL

4、設定C++-->代碼生成-->運作時庫根據debug或者release設定為MDD或者MD。

#pragma once


#define DUI_MFCCTRL_COMMAND_MSG _T("MFC_CTRL_NOTIFY_MSG")
#define DUI_MFCCTRL_NOTIFY_MSG _T("MFC_CTRL_COMMAND_MSG")


// 封裝MFC控件到DUI控件中,實作duilib中嵌入MFC控件
// 通過維護一個HWND實作
class CDUIMFCCtrlWrapper : public DuiLib::CControlUI, public DuiLib::IMessageFilterUI
{
public:
	CDUIMFCCtrlWrapper(void) : m_hWnd(NULL), m_bAddedMessageFilter(FALSE){}


	~CDUIMFCCtrlWrapper(void){}


    // 綁定
    BOOL Attach(HWND hWndNew);


    HWND Detach();


protected:
	// 控制顯示
	virtual void SetInternVisible(bool bVisible = true);


	// 控制位置
	virtual void SetPos(RECT rc);


	// 對控件消息的分派,例如對發送給如對指令消息和通知消息進行分派
	// 通過SendNotify實作,可以在OnNotify中進行響應。
	virtual LRESULT MessageHandler( UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled );


	virtual void SetManager( DuiLib::CPaintManagerUI* pManager, DuiLib::CControlUI* pParent, bool bInit = true );


protected:
	HWND m_hWnd;
	BOOL m_bAddedMessageFilter; // 防止重複設定消息監聽
};
           
#include "StdAfx.h"


#include "DUIMFCCtrlWrapper.h"


using namespace DuiLib;


void CDUIMFCCtrlWrapper::SetManager( CPaintManagerUI* pManager, CControlUI* pParent, bool bInit /*= true */ )
{
    if (pManager && !m_bAddedMessageFilter)
    {
        m_bAddedMessageFilter = TRUE;
        // 設定本控件封裝能接收到消息,進而進行分派
        pManager->AddMessageFilter(this);
    }
    CControlUI::SetManager(pManager, pParent, false);
}


LRESULT CDUIMFCCtrlWrapper::MessageHandler( UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled )
{
    bHandled = TRUE; // 如果是指令消息和通知消息等MFC控件消息則不用再傳遞下去
    if (uMsg == WM_NOTIFY)
    {
        NMHDR* pNMHDR = (NMHDR*)lParam;
        HWND hWndCtrl = pNMHDR->hwndFrom;
        UINT nId = LOWORD(wParam);
        int nCode = pNMHDR->code;
        ASSERT(NULL != hWndCtrl);
        ASSERT(::IsWindow(hWndCtrl));
        typedef struct _CtrlNotifyStruct
        {
            NMHDR* pNMHDR;
            int nCode;
        }CtrlNotifyStruct;
        CtrlNotifyStruct ns;
        ns.pNMHDR = pNMHDR;
        ns.nCode = nCode;
        m_pManager->SendNotify(this, DUI_MFCCTRL_COMMAND_MSG, WPARAM(nId), LPARAM(&ns));
    }
    else if (WM_COMMAND == uMsg)
    {
        UINT nID = LOWORD(wParam);
        HWND hWndCtrl = (HWND)lParam;
        int nCode = HIWORD(wParam);
        m_pManager->SendNotify(this, DUI_MFCCTRL_NOTIFY_MSG, nID, nCode);
    }
    //else if(XTPWM_PROPERTYGRID_NOTIFY == uMsg)
    //{
    //    m_pManager->SendNotify(this, DUI_MFCCTRL_NOTIFY_MSG, wParam, lParam);
    //}
    else
    {
        // 否則該消息需要繼續傳遞下去
        bHandled = FALSE;
        return 1;
    }
    return 0;
}


void CDUIMFCCtrlWrapper::SetPos( RECT rc )
{
    __super::SetPos(rc);
    ::SetWindowPos(m_hWnd, NULL, rc.left, 
        rc.top, rc.right - rc.left, rc.bottom - rc.top,
        SWP_NOZORDER | SWP_NOACTIVATE);
}


void CDUIMFCCtrlWrapper::SetInternVisible( bool bVisible /*= true*/ )
{
    __super::SetInternVisible(bVisible);
    ::ShowWindow(m_hWnd, bVisible);
}


HWND CDUIMFCCtrlWrapper::Detach()
{
    HWND hWnd = m_hWnd;
    m_hWnd = NULL;
    return hWnd;
}


BOOL CDUIMFCCtrlWrapper::Attach( HWND hWndNew )
{
    if (! ::IsWindow(hWndNew))
    {
        return FALSE;
    }
    m_hWnd = hWndNew;
    return TRUE;
}
           
#include "stdafx.h"
#include "DUIMFCCtrlWrapper.h"
#include "Mycug.h"


using namespace DuiLib;


class CFrameWindowWnd : public WindowImplBase
{
    DUI_DECLARE_MESSAGE_MAP()
public:
    
protected:
    virtual CDuiString GetSkinFolder() 
    {
        return CPaintManagerUI::GetInstancePath();
    }


    virtual CDuiString GetSkinFile() 
    {
        return _T("test.xml");
    }


    virtual LPCTSTR GetWindowClassName( void ) const
    {
        return _T("CFrameWindowWnd");
    }


    virtual CControlUI* CreateControl( LPCTSTR pstrClass ) 
    {
        if (_tcscmp(pstrClass, _T("MfcCtrl")) == 0)
        {
            return new CDUIMFCCtrlWrapper();
        }
        return NULL;
    }
 
    void OnClick(TNotifyUI& msg)
    {
        CDuiString sCtrlName = msg.pSender->GetName();
        if( sCtrlName == _T("closebtn") )
        {
            PostMessage(WM_QUIT, 0, 0);
            return; 
        }
        else if( sCtrlName == _T("minbtn"))
        { 
            SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0); 
            return; 
        }
        else if( sCtrlName == _T("maxbtn"))
        { 
            SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, 0); 
            return; 
        }
        else if( sCtrlName == _T("restorebtn"))
        { 
            SendMessage(WM_SYSCOMMAND, SC_RESTORE, 0); 
            return; 
        }
    } 


    virtual void InitWindow() 
    {
        CDUIMFCCtrlWrapper* pCtrl = (CDUIMFCCtrlWrapper*)m_PaintManager.FindControl(_T("grid"));
        if (pCtrl)
        {
            m_GridCtrl.CreateGrid(WS_CHILD|WS_VISIBLE,CRect(0,0,0,0),CWnd::FromHandle(GetHWND()),1234);
            pCtrl->Attach(m_GridCtrl.GetSafeHwnd());
        }
    }


    MyCug m_GridCtrl;
};
           
DUI_BEGIN_MESSAGE_MAP(CFrameWindowWnd, WindowImplBase)
    DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK,OnClick)
DUI_END_MESSAGE_MAP()


int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow)
{
    // initialize MFC and print and error on failure
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
    {
        // TODO: change error code to suit your needs
        _tprintf(_T("Fatal Error: MFC initialization failed\n"));
        return 1;
    }
    CPaintManagerUI::SetInstance(hInstance);
    CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());


    HRESULT Hr = ::CoInitialize(NULL);
    if( FAILED(Hr) ) return 0;


    CFrameWindowWnd* pFrame = new CFrameWindowWnd();
    if( pFrame == NULL ) return 0;
    pFrame->Create(NULL, _T("測試"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    pFrame->CenterWindow();
    pFrame->ShowWindow(true);
    CPaintManagerUI::MessageLoop();


    ::CoUninitialize();
    return 0;
}
           

duilib繪圖部分

圖像的繪制大部分使用了繪制引擎的DrawImageString函數,該函數實作知道圖像名稱繪制到指定目标區域的功能

并且通過指定pStrModify的屬性能夠進行不同需求的繪制,例如,僅扣取源圖像的某個位置繪制到目标的某個區域、

設定四邊圓角繪制、為bmp等指定某種顔色為透明顔色、平鋪或者拉伸繪制、設定透明度等。

DrawImageString最終又調用CRenderEngine::DrawImage進行繪制。

// hDc HDC句柄
// CPaintManagerUI句柄
// rc 目标畫布的大小
// rcPaint 繪制區域
// pStrImage 為源圖像的名稱,不需提供路徑,函數内部會自動加上資源位置的路徑,路徑為CPaintManagerUI::SetResourcePath設定的路徑。
// pStrModify 設定繪制屬性,含義為:
// 2、file='aaa.jpg' res='' restype='0' dest='0,0,0,0' source='0,0,0,0' corner='0,0,0,0' 
// mask='#FF0000' fade='255' hole='false' xtiled='false' ytiled='false'
// source和dest表示從源圖像的source區域貼到目标圖像的dest區域,mask表示讓某顔色為透明色,例如mask="#FF000000",設定黑色為透明色。
// xtiled,ytiled 設定為true表示橫向和縱向的圖像不拉伸顯示而是平鋪顯示
// hole 為true表示不繪制中間部分,為某些場合提高性能
bool CRenderEngine::DrawImageString(HDC hDC, CPaintManagerUI* pManager, const RECT& rc, const RECT& rcPaint, 
                                          LPCTSTR pStrImage, LPCTSTR pStrModify)
{
	if ((pManager == NULL) || (hDC == NULL)) return false;


    // 1、aaa.jpg
    // 2、file='aaa.jpg' res='' restype='0' dest='0,0,0,0' source='0,0,0,0' corner='0,0,0,0' 
    // mask='#FF0000' fade='255' hole='false' xtiled='false' ytiled='false'


}
void CRenderEngine::DrawImage(HDC hDC, HBITMAP hBitmap, const RECT& rc, const RECT& rcPaint,
                                    const RECT& rcBmpPart, const RECT& rcCorners, bool alphaChannel, 
                                    BYTE uFade, bool hole, bool xtiled, bool ytiled)
           

duilib中所有元素的顯示都在整個消息循環的WM_PAINT中進行繪制。

第一次繪制之前會發送一個名為_T("windowinit")的通知。

然後為CControlUI的繪制,繪制的順序為:背景顔色->背景圖->狀态圖->文本->邊框

會順序調用CControlUI的以下函數。        

PaintBkColor(hDC);            // 繪制背景顔色
PaintBkImage(hDC);          // 繪制背景圖
PaintStatusImage(hDC);   // 繪制狀态圖
PaintText(hDC);                 // 繪制文本
PaintBorder(hDC);            // 繪制邊框
           

是以有需求在界面上動态繪制一些内容時,可以通過CControlUI進行子類化,然後重寫PaintStatusImage,

在PaintStatusImage裡面進行繪圖操作。繪制時可以直接調用duilib繪制引擎進行繪制,比較便捷。當然也可以使用GDI+等庫直接進行繪制。

duilib的圖像檔案的加載是通過一個開源庫stb_image.c實作。

并隻使用了該庫的stbi_load_from_memory和stbi_image_free這兩個功能函數。

該庫的連結位址:http://nothings.org/stb_image.c

該庫對圖檔的格式的支援情況說明如下:

JPEG 支援Baseline标準型的JPEG,不支援漸進式Progressive的JPEG

PNG   僅支援8位的png圖像

BMP  不支援1bpp的bmp,不支援行程編碼RLE的bmp

PSD  緊顯示合成的圖像,不支援額外的通道

GIF *comp always reports as 4-channel

是以在duilib中使用的圖檔時需要注意到這個限制,否則有些圖檔将顯示不出來。

duilib中把圖檔檔案加載到記憶體後,由庫stb_image.c解析并最終轉為DIB處理。

duilib支援從圖檔檔案、從包含圖檔檔案的壓縮封包件和VC資源中載入圖檔。

載入後最終傳回TImageInfo結構體指針,包含HBITMAP、圖像寬度、圖像高度、alpha通道、mask和資源類型等資訊。

載入函數為:TImageInfo* CRenderEngine::LoadImage(STRINGorID bitmap, LPCTSTR type, DWORD mask);

duilib中所有的圖檔資源隻從加載一次,然後儲存在CPaintManagerUI中的m_mImageHash成員中,該成員為一個

hashmap存儲TImageInfo*資訊,當已經存在要繪制顯示的圖檔資訊時,直接從map中擷取顯示,否則從檔案加載

在添加到map中。

問:如何管理duilib界面皮膚資源?

答:把所有XML檔案在同一個目錄下,圖檔資源另外放在一個地方。當如下進行布局資源時,skin目錄為設定資源的目錄,imagedir1目錄放置1.xml檔案利用到的圖檔資源,imagedir2放置2.xml檔案利用到的資源。

然後1.xml檔案檔案中的圖檔資源名稱需要加上相對于目前xml檔案的路徑資訊即可。例如:normalimage="imagedir1/1.png"這樣的形式。使用設計器為XML添加資源時會自動轉化為相對路徑。

skin/

1.xml

2.xml

imagedir1/

1.png

2.png

imagedir2/

1.png

2.png

需注意一個問題,那就是字型的管理。字型對于一個視窗來說是全局的,索引從0開始,font = 0表示使用排在第一位的字型。如果一個界面被拆分為多個XML檔案管理,并且當每個單獨的檔案自己添加字型時,聯合起來的時候,會跟其它的字型發生沖突,即索引不是原來的索引了。索引得從全局來看。按照全局的方式進行設定好索引後,對于每個xml檔案的預覽操作時,又看不到所見即所得的效果了。

關于設計器

問題:為什麼給控件設定name屬性後儲存,但該屬性資訊丢失?

答:設定控件名稱,需要注意,名稱中不能含有該控件類名的字段,例如設定其中一個選項按鈕的名字為OptionUIMyTest,将不會被儲存,因為預設的OptionUI1、OptionUI2等為設計器預設使用的名字當存在OptionUI時,設計器認為是預設的,并非使用者設定,是以沒有儲存該資訊到XML檔案中。

問:如何把一個内容很長的xml檔案分為拆分進行管理?

答:可以使用ChildLayout布局。指定xmlfile屬性設定為引用的xml檔案。例如以下例子,必須注意一個問題,就是tab_main.xml設定上<Window>标簽,并且不要設定size屬性,否則會跟主窗體沖突,該大小會作為主窗體的大小而發生錯誤。

            <TabLayout name="MainTab" width="870" height="362">

                <ChildLayout xmlfile="tab_main.xml" />

                <VerticalLayout padding="1,0,1,1" bkcolor="#FFFFFFFF" />

            </TabLayout>

除了使用ChildLayout布局外,可以使用Include标簽,例如:

<Include source="scrollbar.xml" />