引入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" />