DuiVision是參考了仿PC管家程式、金山界面庫、DuiEngine、DuiLib等多個基于DirectUI的界面庫開發的。
本文主要介紹基于DuiVision的自定義控制元件如何開發,通過自定義控制元件實作控制元件的擴展。
DuiVision代碼結構
DuiVision代碼的整體架構如下圖所示:

代碼可以分為DuiVision庫、DuiVision應用程式、DuiVision插件幾部分,其中DuiVision應用程式和DuiVision插件都是基于DuiVision庫開發的。
DuiVision庫的類結構中幾個重要的類說明如下:
CDuiObject:
是所有DuiVision物件的基類,每個物件都包含有ID、名字、區域這些DUI物件基礎的東西,同時也可以給物件設定一個事件處理物件(CDuiHandler),DUI基類還封裝了一些虛函式,包括物件加載(每個物件都可以對應到xml描述檔案中的一個node節點,DUI基類封裝了對xml節點的加載方法,包括讀取xml節點名、節點屬性,并對基礎的屬性進行處理)、訊息處理、設定物件更新標識等。
CControlBase:
是所有DUI控制元件的基類,定義了所有控制元件都具有的屬性,以及公共的方法、虛函式等,CControlBase定義的虛函式用于派生的控制元件實作特定的功能,包括滑鼠、鍵盤操作、控制元件的畫圖函式、控制元件大小和位置變更函式等。
CControlBaseFont:
所有需要顯示文字的控制元件都可以從此控制元件派生,這個控制元件定義了一些文字的公共屬性和方法,包括文字標題、字體、對齊方式等。
CDlgBase:
對話框類,實作了對話框的所有功能,可以支持模態和非模態對話框,對話框中會包含若干子控制元件,對話框可以定義對應的xml描述檔案,通過xml檔案可以加載下層的所有子控制元件,所有的事件處理也都是從對話框發起,然后逐層遞回呼叫到子控制元件的處理函式。
DuiSystem:
是DuiVision的所有資源的入口,承擔了所有資源的統一管理功能(圖片、配置、字串、對話框、事件處理物件都可以作為資源),同時作為工具類提供了很多公共的函式,DuiSystem是一個單例物件,一個應用程式或插件中只有一個DuiSystem的實體物件。
自定義控制元件的開發
控制元件概述
所有控制元件都是從CControlBase或ControlBase的某個派生類派生的,對于一個控制元件,主要要完成的事情包括:初始化(屬性的決議和處理)、位置資料的處理(位置和大小變更)、畫圖、事件的處理(滑鼠事件、鍵盤事件等);
初次之外,對于復雜一些的控制元件,可能還要考慮控制元件焦點、tooltip、影片、動作回應等事情。下面會依次對控制元件的這些功能如何開發進行說明。
建構式和解構式
控制元件的初始化在控制元件的建構式中:
CControlBase(HWND hWnd, CDuiObject* pDuiObject);
控制元件的解構式主要用于釋放控制元件中申請的記憶體、圖片等資源,特別需要注意的是,如果控制元件中有用到一些圖片資源,在解構式中一定要釋放,釋放的方法都比較類似,例如Button控制元件中定義了Button的圖片,在解構式中就用如下的方法進行圖片的釋放:
CDuiButton::~CDuiButton(void)
{
if(m_pImageBtn != NULL)
{
delete m_pImageBtn;
m_pImageBtn = NULL;
}
}
圖片的定義
很多控制元件中都需要定義圖片資源,例如Button控制元件中定義的Button的圖片,就是由下面這種四種狀態的圖片組成的,DuiVision中很多控制元件的圖片都需要包含多個狀態的圖片:

在ControlBase.h中有按鈕類圖片的狀態定義:
enum enumButtonState
{
enBSNormal = 0,
enBSHover,
enBSDown,
enBSDisable,
enBSHoverDown,
enBSDisableDown
};
完整的狀態有六種,分別是正常狀態、滑鼠移動到控制元件上的狀態、滑鼠在控制元件上按下的狀態、禁用狀態、按下時滑鼠移動到控制元件上的狀態、按下時控制元件被禁用的狀態,一般只會用到前四種狀態,后面兩種狀態是檢查框、廣播按鈕這樣的控制元件才有的。
DuiVision中的控制元件用到的這種多狀態圖片一般是按照每種狀態的圖片橫向排列在一個大圖中,順序就是按照上面定義的順序,當然對于自定義控制元件也可以按照其他的方式制作大圖,只要畫圖時候按照定義的方式進行決議就可以了。
除了按鈕的圖片,下面列出了一些常見的多狀態圖片。
檢查框的六狀態圖片:
廣播按鈕的六狀態圖片:

Edit控制元件的外框的四狀態圖片:

在自定義控制元件中定義圖片資源,可以使用DuiVision提供的幾個宏來減少代碼量,舉個例子,例如定義上面按鈕的圖片資源,需要寫的代碼如下:
1、在頭檔案的類定義中使用DUI_IMAGE_ATTRIBUTE_DEFINE宏來定義一個圖片,這個宏的引數是圖片資源的變數名中的一部分,例如Btn表示按鈕的圖片,這個宏一般定義在控制元件的屬性定義部分的上面:
DUI_IMAGE_ATTRIBUTE_DEFINE(Btn); // 定義按鈕圖片
DUI_DECLARE_ATTRIBUTES_BEGIN()
DUI_COLOR_ATTRIBUTE(_T("crtext"), m_clrText, FALSE)
DUI_DECLARE_ATTRIBUTES_END()
實際通過這個宏會定義兩個類成員變數,分別是:
Image* m_pImageBtn;
CSize m_szieBtn;
這兩個變數分別是圖片物件指標和圖片的大小,同時這個宏會定義幾個針對這個圖片的初始化和通過xml屬性加載用到的函式,使用這個宏之后我們可以不用關心復雜的圖片定義、加載的細節。
2、定義圖片的屬性,用于xml檔案中定義圖片時候的屬性名字,例如針對這個Btn圖片的屬性定義:
DUI_CUSTOM_ATTRIBUTE(_T("img-btn"), OnAttributeImageBtn)這里定義的img-btn就是在xml中對應的按鈕的圖片屬性名,OnAttributeImageBtn是對應的圖片屬性加載用到的函式,在上面的圖片定義宏中已經隱含的定義了這個函式,所以這里只要按照命名規則寫就可以了,命名規則就是OnAttributeImage+前面定義的圖片變數名一部分。
3、解構式中對圖片資源的釋放
按照上一節所說的解構式寫法就可以,注意其中的圖片資源的變數名就是按照 “m_pImage+前面定義的圖片變數名一部分” 這樣的規則。
4、在控制元件的cpp實作檔案的解構式下面增加下面這段代碼
// 圖片屬性的實作
DUI_IMAGE_ATTRIBUTE_IMPLEMENT(CDuiButton, Btn, 4)
通過上面這個宏可以實作在頭檔案中定義的圖片的初始化、xml屬性加載相關的幾個函式,這個宏的第一個引數是控制元件的型別,第二個引數和頭檔案中定義的名字一部分相同,第三個引數表示這個圖片橫向分割為幾個小圖片。
5、圖片的使用
主要是在畫圖函式中使用,參加控制元件的畫圖的章節。
位置資料處理
控制元件的位置資料是根據父控制元件的位置變化可能需要進行調整的,DuiVision已經對位置資料的計算進行了封裝,包括支持相對位置,當一個控制元件的位置發生變化時候,控制元件內的一些位置資訊可能需要相應的進行調整,控制元件內的位置調整就需要控制元件多載SetControlRect虛函式進行調整了。
對于自定義控制元件,如果控制元件中沒有類似需要計算的相對位置,則不需要實作這個函式,如果有的話,就需要多載這個函式進行內部的一些位置資料的計算。
畫圖
控制元件要在界面顯示出來,就必須要實作DrawControl虛函式,下面是檢查框控制元件的畫圖函式,增加了每一步的說明:
void CCheckButton::DrawControl(CDC &dc, CRect rcUpdate)
{
int nWidth = m_rc.Width();
int nHeight = m_rc.Height();
if(!m_bUpdate) // 是否需要重畫整個控制元件
{
// 申請記憶體dc
UpdateMemDC(dc, nWidth * 6, nHeight);
// 用記憶體dc初始化GDI+的graph物件
Graphics graphics(m_memDC);
CRect rcTemp(0, 0, nWidth, nHeight);
// 畫種狀態的圖片
for(int i = 0; i < 6; i++)
{
// 將背景資訊先復制到記憶體dc對應狀態的位置
m_memDC.BitBlt(i * nWidth, 0, nWidth, nHeight, &dc, m_rc.left ,m_rc.top, SRCCOPY);
// 在記憶體dc對應狀態的位置畫檢查框對應狀態的圖片
graphics.DrawImage(m_pImage, Rect(rcTemp.left, rcTemp.top + (nHeight - m_sizeImage.cy) / 2, m_sizeImage.cx, m_sizeImage.cy),
i * m_sizeImage.cx, 0, m_sizeImage.cx, m_sizeImage.cy, UnitPixel);
// 計算下一個狀態的記憶體dc位置
rcTemp.OffsetRect(nWidth, 0);
}
// 如果設定了檢查框文字,則畫文字到記憶體dc
if(!m_strTitle.IsEmpty())
{
m_memDC.SetBkMode(TRANSPARENT);
rcTemp.SetRect(0, 0, nWidth, nHeight);
// 設定字體
......
// 設定對齊方式等引數
......
// 每一種狀態的記憶體dc中疊加上文字的內容
......
}
}
// 將保存的記憶體dc內容中對應當前狀態的部分復制到實際界面顯示的dc
dc.BitBlt(m_rc.left,m_rc.top, m_rc.Width(), m_rc.Height(), &m_memDC, m_enButtonState * nWidth, 0, SRCCOPY);
}
檢查框控制元件的實際顯示效果如下:
DrawControl函式的實作思路:
1、傳入的引數dc和rcUpdate分別表示實際畫圖的dc和重繪的區域,在DrawControl中第一次需要執行一次完整的畫圖的流程,將顯示內容存盤到一個記憶體dc中,以后如果這個控制元件的界面顯示沒有變化的話,DrawControl只需要將保存的記憶體dc的內容復制到顯示的dc就可以了,這樣可以加快顯示的速度,如果所有控制元件都沒有變化,實際上整個界面的顯示就是把所有控制元件的記憶體dc內容一層一層復制到顯示dc,先后一次性顯示出來,復制時候是按照控制元件添加的順序進行復制的。DrawControl中用于控制是否重新畫圖還是僅復制記憶體dc的開關是m_bUpdate變數,如果為true表示僅復制記憶體dc,false表示要重畫控制元件。
2、如果是重畫整個控制元件,則需要使用UpdateMemDC函式申請記憶體dc,每個控制元件類中都有保存一個記憶體dc成員變數,UpdateMemDC函式中會判斷如果申請的寬度或高度和以前保存的不一樣,就會先釋放掉以前保存的記憶體dc,然后在按照新的尺寸申請一個新的,如果和以前一樣大小就使用以前的。注意申請記憶體dc時候一般并不是只申請和控制元件大小相同的記憶體dc,而是按照控制元件有多少種狀態,就申請幾倍大小的記憶體dc,例如檢查框控制元件有六種狀態,一般的按鈕有四種狀態,如果是簡單顯示一個文字或圖形,沒有多種狀態的,則申請和控制元件大小相同的記憶體dc就可以了。對于多個狀態的記憶體dc申請,可以按照每種狀態的圖片在記憶體dc中橫向排列或縱向排列,或者復雜的控制元件可能會包含了多個子圖形的多個狀態混合排列在記憶體dc中,上面檢查框的代碼中是按照橫向排列的,因此申請時候是width乘以6。
3、DuiVision大部分的控制元件都使用的GDI+進行畫圖,因此會使用記憶體dc創建一個GDI+的圖形物件,Graphics graphics(m_memDC);然后就是具體的控制元件的畫圖操作。
4、對于檢查框的具體畫圖操作,是先畫6種狀態下的檢查框圖片到記憶體dc對應圖片的位置,然后再畫文字到每種狀態對應的位置,這里檢查框圖片對應每種狀態是不同的,文字在每種狀態下都是相同的,但也需要重復畫到每種狀態的位置。
5、對于檢查框,還需要考慮控制元件處于焦點狀態下時候要畫控制元件周圍的虛線框m_bIsFocus成員變數表示當前控制元件是否處于焦點狀態,m_bShowFocus表示這個控制元件是否要顯示焦點框,這是由showfocus屬性決定的,只有這兩個變數都為true時候才需要畫焦點框。
6、DrawControl最后將記憶體dc復制到顯示dc時候,需要注意的是根據當前的狀態,決定把記憶體dc中對應狀態部分的內容復制到顯示dc,檢查框的記憶體dc是按照狀態橫向排列的,所以使用m_enButtonState * nWidth定位到橫向的具體記憶體dc位置。
事件處理
控制元件的事件處理最常用的是滑鼠和鍵盤事件的處理,相關的幾個需要實作的虛函式如下:
virtual BOOL OnControlMouseMove(UINT nFlags, CPoint point);
virtual BOOL OnControlLButtonDown(UINT nFlags, CPoint point);
virtual BOOL OnControlLButtonUp(UINT nFlags, CPoint point);
virtual BOOL OnControlKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
檢查框的滑鼠移動事件處理函式如下:
BOOL CCheckButton::OnControlMouseMove(UINT nFlags, CPoint point)
{
enumButtonState buttonState = m_enButtonState;
if (!m_bIsDisable && !m_bMouseDown)
{
// 設定狀態
......
}
if(buttonState != m_enButtonState)
{
UpdateControl();
return true;
}
return false;
}
主要實作思路就是判斷滑鼠位置是否在控制元件的范圍內,從而決定控制元件當前的狀態應該更改為什么,并且要判斷滑鼠按下和非按下情況下,對應的狀態是不一樣的。
根據這些判斷可以計算出新的控制元件狀態,在函式進入的時候會保存之前的狀態,最后需要比較一下新舊狀態是否一致,不一致情況下就需要重新重繪一下控制元件的界面,重繪方法是呼叫UpdateControl函式。
滑鼠按下和放開的事件處理和上面的函式是類似的,另外像按鈕或檢查框等控制元件,在滑鼠放開的時候需要發送一個點擊事件(滑鼠點擊就是滑鼠在一個控制元件上按下又放開,一般都是在放開時候觸發一個點擊事件),檢查框的滑鼠放開事件代碼如下,可以看到通過呼叫SendMessage函式可以發送一個點擊事件:
BOOL CCheckButton::OnControlLButtonUp(UINT nFlags, CPoint point)
{
enumButtonState buttonState = m_enButtonState;
if (!m_bIsDisable)
{
if(m_rc.PtInRect(point))
{
// 發送DUI訊息
}
else
{
......
}
}
m_bMouseDown = false;
......
return false;
}
控制元件的使用
按照以上控制元件的開發方法開發了一個控制元件之后,如果將控制元件應用在DuiVision應用程式中,有兩種方法,可以將控制元件添加到DuiVision庫代碼中或者僅作為自定義控制元件,添加到自己的應用程式中。
如果你開發的控制元件是一個很多人都會用到的控制元件,建議添加到DuiVision庫中,可以提交到github專案中,如果覺得合適會合入主線版本,這樣其他人也都可以使用。
如果僅是某個專案中使用的特殊控制元件,建議按照自定義控制元件的方式添加到自己的應用程式工程中。
控制元件添加到DuiVision庫中的方法
1)首先把控制元件的頭檔案和cpp分別放在DuiVision庫的include和source目錄下,然后添加到DuiVision工程中;
2)在DuiVision.h中添加對控制元件頭檔案的參考;
3)在DuiSystem的LoadDuiControls函式中添加如下的控制元件注冊代碼:
REGISTER_DUICONTROL(CDuiUserControl, NULL);
完成以上幾步就可以使用新的控制元件了。
控制元件添加到自己的應用程式工程中的方法
自定義的控制元件代碼開發完成后,將代碼添加到用戶自己的代碼工程中,然后在控制元件使用之前注冊到DuiVision庫中就可以,建議注冊代碼放在主程式的DuiSystem初始化之后,下面的代碼演示了在主程式中注冊類名為CDuiUserControl的自定義控制元件的方法,藍色部分代碼就是用于注冊自定義控制元件的代碼:
BOOL CDuiVision1App::InitInstance()
{
//初始化
......
// 注冊用戶自定義控制元件控制元件
REGISTER_DUICONTROL(CDuiUserControl, NULL);
// 創建主視窗
......
return FALSE;
}
說明:因為字數限制,部分代碼省略,完整內容可以參考CSDN博客中的文章
附:
DuiVision開源代碼下載地址(github):https://github.com/blueantst/DuiVision
藍螞蟻作業室主頁:http://www.blueantstudio.net
DuiVision QQ群:325880743
使用DuiVision開發的一些界面演示:








uj5u.com熱心網友回復:
學習,感謝分享。uj5u.com熱心網友回復:
厲害了uj5u.com熱心網友回復:
厲害了,我的兔哥哥
uj5u.com熱心網友回復:
uj5u.com熱心網友回復:
系統的按鈕應該五種狀態:常規、懸停、按下、禁用、焦點。你說的:滑鼠在控制元件上按下的狀態、按下時滑鼠移動到控制元件上的狀態,兩種其實是同一種狀態。焦點按鈕與常規按鈕的區別是,2k 下焦點就是邊框變粗,XP 則是帶虛線框,7 上面則是顏色深淺不斷切換的影片、8、10 則是淡藍色高亮邊框。具有焦點的按鈕,按下空格鍵、Enter 鍵應該視為按鈕被按下。系統的復選(勾選)按鈕,單選按鈕,每種狀態都是四種:常規、懸停、按下、禁用,而不是三種,不過你沒有包含禁用效果,因此常用也就三種。三態勾選框所有狀態加起來應該是 12 張圖,而圓形單選按鈕,兩種狀態加起來是 8 張圖。轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/112541.html
標籤:界面
