1. 程式人生 > >白喬原創:VC之美化介面篇

白喬原創:VC之美化介面篇

本文專題討論VC中的介面美化,適用於具有中等VC水平的讀者。讀者最好具有以下VC基礎:

1. 大致瞭解MFC框架的基本運作原理;

2. 熟悉Windows訊息機制,熟悉MFC的訊息對映和反射機制;

3. 熟悉OOP理論和技術;

本文根據筆者多年的開發經驗,並結合簡單的例子一一展開,希望對讀者有所幫助。

1. 美化介面之開題篇

相信使用過《金山毒霸》、《瑞星防毒》軟體的讀者應該還記得它們的精美介面:

3[65,526位元組]

圖1 瑞星防毒軟體的精美介面

程式的功能如何如何強大是一回事,它的使用者介面則是另一回事。千萬不要忽視程式的使用者介面,因為它是給使用者最初最直接的印象,醜陋的介面、不友好的風格肯定會影響使用者對軟體程式的使用。

“受之以魚,不若授之以漁”,本教程並不會向你推薦《瑞星防毒軟體》精美介面的具體實現,而只是向你推薦一些常用的美化方法。

2. 美化介面之基礎篇

美化介面需要先熟悉Windows下的繪圖操作,並明白Windows的幕後繪圖操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……

2.1 Windows下的繪圖操作

熟悉DOS的讀者可能就知道:DOS下面的圖形操作很方便,進入圖形模式,整個螢幕就是你的了,你希望在哪畫個點,那個地方就會出現一個點,紅的、或者黃的,隨你的便。你也可以花點時間畫個按鈕,畫個你自己的選單,等等……

Windows本身就是圖形介面,所以Windows下面的繪圖操作功能更豐富、簡單。要了解Windows下的繪圖操作,要實現Windows介面的美化,就必須瞭解MFC封裝的裝置環境類和圖形物件類。

2.1.1 裝置環境類

Windows下的繪圖操作說到底就是DC操作。DC(Device Context裝置環境)物件是一個抽象的作圖環境,可能是對應螢幕,也可能是對應印表機或其它。這個環境是裝置無關的,所以你在對不同的裝置輸出時只需要使用不同的裝置環境就行了,而作圖方式可以完全不變。這也就是Windows的裝置無關性。

MFC的CDC類封裝了Windows API 中大部分的畫圖函式。CDC的常見操作函式包括:

Drawing-Attribute Functions:繪圖屬性操作,如:設定透明模式

Mapping Functions:對映操作

Coordinate Functions:座標操作

Clipping Functions:剪下操作

Line-Output Functions:畫線操作

Simple Drawing Functions:簡單繪圖操作,如:繪製矩形框

Ellipse and Polygon Functions:橢圓/多邊形操作

Text Functions:文字輸出操作

Printer Escape Functions:列印操作

Scrolling Functions:滾動操作

*Bitmap Functions:點陣圖操作

*Region Functions:區域操作

*Font Functions:字型操作

*Color and Color Palette Functions:顏色/調色盤操作

其中,標註*項會用到相應的圖形物件類,參見2.1.2內容。

2.1.2 圖形物件類

裝置環境不足以包含繪圖功能所需的所有繪圖特徵,除了裝置環境外, Windows還有其他一些圖形物件用來儲存繪圖特徵。這些附加的功能包括從畫線的寬度和顏色到畫文字時所用的字型。圖形物件類封裝了所有六個圖形物件。

下面的表格列出了MFC的圖形物件類:

MFC類 圖形物件控制代碼 圖形物件目的

CBitmap HBITMAP 記憶體中的點陣圖

CBrush HBRUSH 畫刷特性—填充某個圖形時所使用的顏色和模式

CFont HFONT 字型特性—寫文字時所使用的字型

CPalette HPALETTE 調色盤顏色

CPen HPEN 畫筆特性—畫輪廓時所使用的線的粗細

CRgn HRGN 區域特性—包括定義它的點

表1 圖形物件類和它們封裝的控制代碼

使用CDC和圖形物件類,在Windows裡繪圖還算是很簡單的。觀察以下的畫面:

CDC[13,031位元組]

 圖2 使用CDC繪製出的按鈕

該畫面通過以下程式碼自行繪製的假按鈕:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)   
  2. {   
  3. //設定背景色
  1. //CBrush CUi1View::m_Back
  2.     m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));   
  3.     cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);   
  4. return CView::PreCreateWindow(cs);   
  5. }   
  6. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)    
  7. {   
  8. if (CView::OnCreate(lpCreateStruct) == -1)   
  9. return -1;   
  10. //建立字型
  11. //CFont CUi1View::m_Font
  12.     m_Font.CreatePointFont(120, "Impact");   
  13. return 0;   
  14. }   
  15. void CUi1View::OnDraw(CDC* pDC)   
  16. {   
  17. //繪製按鈕框架
  18.     pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);   
  19. //輸出文字
  20.     pDC->SetBkMode(TRANSPARENT);   
  21.     pDC->TextOut(120, 120, "Hello, CFan!");   
  22. }  

呵呵,不好意思,這並不是真的Windows按鈕,它只是一個假的空框子,當用戶在按鈕上點選滑鼠時,放心,什麼事情都不會發生。

2.2 Windows的幕後繪圖操作

在Window中,如果所有的介面操作都由使用者程式碼來實現,那將是一個很浩大的工程。筆者曾經在DOS設計過視窗圖形介面,程式碼上千行,但實現的介面還是很古板、難看,除了我那個對程式設計一竅不通的女友,沒有一個人欣賞它L;而且,更要命的是,作業系統,包括別的應用程式並不認識你的介面元素,這才是真正悲哀的。認識這些介面的只有你的程式,圖2中的按鈕永遠只是一個無用的框子。

有了Windows,一切都好辦了,Windows將諸如按鈕、選單、工具欄等等這些通用介面的繪製及動作都交給了系統,程式設計師就不用花心思再畫那些按鈕了,可以將更多的精力放在程式的功能實現方面。

所有的標準介面元素都被Windows封裝好了。Windows知道怎麼畫你的選單以及你的標註著“Hello, Cfan!”的按鈕。當CFan某個快樂的小編(譬如:小飛)點選這個按鈕的時候,Windows也明白按鈕按下去的時候該有的模樣,甚至,當這個友好的按鈕獲取焦點時,Windows也會不失時機地為它準備一個虛框……

有利必有弊。你的不滿這時候產生了:你既想使用Windows的True Button,可也嫌它的介面不夠好看,譬如,你喜歡用藍色的粗體表達你對CFan的無限情懷(正如圖2那樣)——人心不足,有辦法嗎?有的。

3. 美化介面之實現篇

Windows還是給程式設計師留下了很多後門,通過一些途徑還是可以美化介面的。本章節我們系統學習一下Windows介面美化的實現。

3.1 美化介面的途徑

如何以合法的手段來達到美化介面的效果?一般美化介面的方法包括:

1. 使用MFC類的既有函式,設定介面屬性;

2. 利用Windows的訊息機制,截獲有用的Windows的訊息。通過MFC的訊息對映(Message Mapping)和反射(Message Reflecting)機制,在Windows準備或者正在繪製該元素時,偷偷修改它的狀態和行為,譬如:讓按鈕的邊框為紅色;

3. 利用MFC類的虛擬函式機制,過載有用的虛擬函式。在MFC框架呼叫該函式的時候,重新定義它的狀態和行為;

一般來說,應用程式可以通過以下兩種途徑來實現以上的方法:

1. 在父窗口裡,截獲自身的或者由子元素(包括控制元件和選單等元素)傳遞的關於介面繪製的訊息;

2. 子類化子元素,或者為子元素準備一個新的類(一般來說該類必須繼承於MFC封裝的某個標準類,如:CButton)。在該子元素裡,截獲自身的或者從父視窗反射過來的關於介面繪製的訊息。譬如:使用者可以建立一個CXPButton類來實現具有XP風格的按鈕,CXPButton繼承於CButton。

對於應用程式,使用CXPButton類的途徑相對於對話方塊視窗和普通視窗分成兩種:

① 對話方塊視窗中,直接將原先繫結按鈕的CButton類替換成CXPButton類,或者在繫結變數時直接指定Control型別為CXPButton,如圖3所示:

7[7,737位元組]

 圖3 為按鈕指定CXPButton型別

②在普通視窗中,直接建立一個CXPButton類物件,然後在OnCreate()中呼叫CXPButton的Create方法;

以下的章節將綜合地使用以上的方法,請讀者朋友留心觀察。

3.2 使用MFC類的既有函式

在介面美化的專題中,MFC也並非一無是處。MFC類對於介面美化也做了部分的努力,以下是一些可以使用的,引數說明略去。

CWinApp::SetDialogBkColor

void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );

指定對話方塊的背景色和文字顏色。

CListCtrl::SetBkColor

CReBarCtrl::SetBkColor

CStatusBarCtrl::SetBkColor

CTreeCtrl::SetBkColor

COLORREF SetBkColor( COLORREF clr );

設定背景色。

CListCtrl::SetTextColor

CReBarCtrl::SetTextColor

CTreeCtrl::SetTextColor

COLORREF SetTextColor( COLORREF clr );

設定文字顏色。

CListCtrl::SetBkImage

BOOL SetBkImage( LVBKIMAGE* plvbkImage );

BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);

BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );

設定列表控制元件的背景圖片。

CComboBoxEx::SetExtendedStyle

CListCtrl::SetExtendedStyle

CTabCtrl::SetExtendedStyle

CToolBarCtrl::SetExtendedStyle

DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );

設定控制元件的擴充套件屬性,例如:設定列表控制元件屬性帶有表格線。

圖4是個簡單應用MFC類的既有函式來改善Windows介面的例子:

MFC[13,190位元組]

圖4 使用MFC類的既有函式美化介面

相關實現程式碼如下:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. BOOL CUi2App::InitInstance()   
  2. {   
  3. //…
  4. //設定對話方塊背景色和字型顏色
  5.     SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));    
  6. //…
  7. }   
  8. BOOL CUi2Dlg::OnInitDialog()   
  9. {   
  10. //…
  11. //設定列表控制元件屬性帶有表格線
  12. DWORD NewStyle = m_List.GetExtendedStyle();   
  13.     NewStyle |= LVS_EX_GRIDLINES;   
  14. m_List.SetExtendedStyle(NewStyle);   
  15. //設定列表控制元件字型顏色為紅色
  16.     m_List.SetTextColor(RGB(255, 0, 0));   
  17. //填充資料
  18.     m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);   
  19.     m_List.InsertColumn(1, "暱稱", LVCFMT_LEFT, 100);   
  20.     m_List.InsertItem(0, "5854165");   
  21.     m_List.SetItemText(0, 1, "白喬");   
  22.     m_List.InsertItem(1, "6823864");   
  23.     m_List.SetItemText(1, 1, "Satan");   
  24. //…
  25. }  

嗯,這樣的介面還算不錯吧?

3.3 使用Windows的訊息機制

使用MFC類的既有函式來美化介面,其功能是有限的。既然Windows是通過訊息機制進行通訊的,那麼我們就可以通過截獲一些有用的訊息來美化我們的介面,以下是一些有用的Windows訊息:

WM_PAINT

WM_ERASEBKGND

WM_CTLCOLOR*

WM_DRAWITEM*

WM_MEASUREITEM*

NM_CUSTOMDRAW*

注意,標註*的訊息是子元素髮送給父視窗的通知訊息,其它的為視窗或者子元素自身的訊息。

3.3.1 WM_PAINT

WM_PAINT訊息相信大家都很熟悉,一個視窗要重繪了,就會有一個WM_PAINT訊息傳送給視窗。

可以響應視窗的WM_PAINT,以更改它們的模樣。WM_PAINT的對映函式原型如下:

afx_msg void OnPaint();

控制元件也是視窗,所以控制元件也有WM_PAINT訊息,通過訊息對映我們完全可以定義控制元件的介面。如圖5所示:

WM_PAINT[9,133位元組]

圖5 利用WM_ PAINT訊息美化介面

實現程式碼也很簡單:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. void CLazyStatic::OnPaint()    
  2. {   
  3.     CPaintDC dc(this); // device context for painting
  4. //什麼都不輸出,僅僅畫一個矩形框
  5.     CRect rc;   
  6.     GetClientRect(&rc);   
  7.     dc.Rectangle(rc);      
  8. }   

哈哈,簡單吧?不過WM_PAINT確實絕了點,它要求應用程式完成元素介面的所有繪製過程,想象一下如何畫出一個完整的列表控制元件?太煩了吧。一般來說,很少有人喜歡使用WM_PAINT,還有其它更細緻的訊息。

3.3.2 WM_ERASEBKGND

Windows在向視窗傳送WM_PAINT訊息之前,總會發送一個WM_ERASEBKGND訊息通知該視窗擦除背景,預設情況下,Windows將以視窗的背景色清除該視窗。

可以響應視窗(包括子元素)的WM_ERASEBKGND,以更改它們的背景。WM_ERASEBKGND的對映函式原型如下:

afx_msg BOOL OnEraseBkgnd( CDC* pDC );

返回值:

指定背景是否已清除,如果為FALSE,系統將自動清除

引數:

pDC指定了繪製操作所使用的裝置環境。

圖6是個簡單的例子,通過OnEraseBkgnd為對話方塊載入了一副點陣圖背景:

WM_ERASEBKGND[77,332位元組]

圖6 利用WM_ ERASEBKGND訊息美化介面

實現程式碼也很簡單:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. BOOL CUi4Dlg::OnInitDialog()   
  2. {   
  3. //…
  4. //載入點陣圖
  5. //CBitmap m_Back;
  6.     m_Back.LoadBitmap(IDB_BACK);   
  7. //…
  8. }   
  9. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)    
  10. {   
  11.     CDC dc;   
  12.     dc.CreateCompatibleDC(pDC);   
  13.     dc.SelectObject(&m_Back);   
  14. //獲取BITMAP物件
  15.     BITMAP hb;   
  16.     m_Back.GetBitmap(&hb);   
  17. //獲取視窗大小
  18.     CRect rt;   
  19.     GetClientRect(&rt);   
  20. //顯示點陣圖
  21.     pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),   
  22.         &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);   
  23. return TRUE;   
  24. }   
  25. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)    
  26. {   
  27. //設定透明背景模式
  28.     pDC->SetBkMode(TRANSPARENT);   
  29. //設定背景刷子為空
  30. return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);   
  31. }   

同時別忘了響應OnCtlColor,否則窗口裡面的控制元件就不透明瞭。OnCtlColor的內容,詳見3.3.3章節。

3.3.3 WM_CTLCOLOR

在控制元件顯示之前,每一個控制元件都會向父對話方塊傳送一個WM_CTLCOLOR訊息要求獲取繪製所需要的顏色。WM_CTLCOLOR訊息預設處理函式CWnd::OnCtlColor返回一個HBRUSH型別的控制代碼,這樣,就可以設定前景和背景文字顏色,併為控制元件或者對話方塊的非文字區域選定一個刷子。

WM_CTLCOLOR的對映函式原型如下:

afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );

返回值:

用以指定背景的刷子

引數:

pDC指定了繪製操作所使用的裝置環境。

pWnd 控制元件指標

nCtlColor 指定控制元件型別,其取值如表2所示:

型別值 含義

CTLCOLOR_BTN 按鈕控制元件

CTLCOLOR_DLG 對話方塊

CTLCOLOR_EDIT  編輯控制元件

CTLCOLOR_LISTBOX  列表框

CTLCOLOR_MSGBOX  訊息框

CTLCOLOR_SCROLLBAR 滾動條

CTLCOLOR_STATIC 靜態控制元件

表2 nCtlColor的型別值與含義

作為一個簡單的例子,觀察以下的程式碼:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. BOOL CUi5Dlg::OnInitDialog()   
  2. {   
  3. //…
  4. //建立字型
  5. //CFont CUi1View::m_Font1, CUi1View::m_Font2
  6.     m_Font1.CreatePointFont(120, "Impact");   
  7.     m_Font3.CreatePointFont(120, "Arial");   
  8. return TRUE;  // return TRUE  unless you set the focus to a control 
  9. }   
  10. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)    
  11. {   
  12. HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);   
  13. if(nCtlColor == CTLCOLOR_STATIC)   
  14.     {   
  15. //區分靜態控制元件
  16. switch(pWnd->GetDlgCtrlID())   
  17.         {   
  18. case IDC_STATIC1:   
  19.             {   
  20.                 pDC->SelectObject(&m_Font1);   
  21.                 pDC->SetTextColor(RGB(0, 0, 255));   
  22. break;   
  23.             }   
  24. case IDC_STATIC2:   
  25.             {   
  26.                 pDC->SelectObject(&m_Font2);   
  27.                 pDC->SetTextColor(RGB(255, 0, 0));   
  28. break;   
  29.             }   
  30.         }   
  31.     }   
  32. return hbr;   
  33. }   

生成的介面如下:

WM_CTLCOLOR[9,407位元組]

 圖7 利用WM_CTLCOLOR訊息美化介面

3.3.4 WM_DRAWITEM

OnCtlColor只能修改元素的顏色,但不能修改元素的介面框架,WM_DRAWITEM則可以。

當一個具有Owner draw風格的元素(包括按鈕、組合框、列表框和選單等)需要顯示外觀時,該元素會發送一條WM_DRAWITEM訊息至它的隸屬視窗(Owner)。

WM_DRAWITEM的對映函式原型如下:

afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );

引數:

nIDCtl 該控制元件的ID,如果該元素為選單,則nIDCtl為0

lpDrawItemStruct 指向DRAWITEMSTRUCT結構物件的指標,DRAWITEMSTRUCT的結構定義如下:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. typedefstruct tagDRAWITEMSTRUCT   
  2. {   
  3. UINT   CtlType;    
  4. UINT   CtlID;    
  5. UINT   itemID;   
  6. UINT   itemAction;   
  7. UINT   itemState;   
  8. HWND   hwndItem;   
  9. HDC    hDC;   
  10.     RECT   rcItem;   
  11. DWORD  itemData;   
  12. }DRAWITEMSTRUCT;   

CtlType指定了控制元件的型別,其取值如表3所示:

型別值 含義

ODT_BUTTON 按鈕控制元件

ODT_COMBOBOX 組合框控制元件

ODT_LISTBOX 列表框控制元件

ODT_LISTVIEW 列表檢視

ODT_MENU 選單項

ODT_STATIC 靜態文字控制元件

ODT_TAB Tab控制元件

表3 CtlType的型別值與含義

CtlID 指定自繪控制元件的ID值,該成員不適用於選單項

itemID表示選單項ID,也可以表示列表框或者組合框中某項的索引值。對於一個空的列表框或組合框,該成員的值為?C1。這時應用程式只繪製焦點矩形(該矩形的座標由rcItem 成員給出)雖然此時控制元件中沒有需要顯示的項,但是繪製焦點矩形還是很有必要的,因為這樣做能夠提示使用者該控制元件是否具有輸入焦點。當然也可以設定itemAction 成員為合適值,使得無需繪製焦點。

itemAction 指定繪製行為,其取值為表4中所示值的一個或者多個的聯合:

型別值 含義

ODA_DRAWENTIRE 當整個控制元件都需要被繪製時,設定該值。

ODA_FOCUS 如果控制元件需要在獲得或失去焦點時被繪製,則設定該值。此時應該檢查itemState成員,以確定控制元件是否具有輸入焦點。

ODA_SELECT 如果控制元件需要在選中狀態改變時被繪製,則設定該值。此時應該檢查itemState 成員,以確定控制元件是否處於選中狀態。

表4 itemAction的型別值與含義

itemState 指定了當前繪製項的狀態。例如,如果選單項應該被灰色顯示,則可以指定ODS_GRAYED狀態標誌。其取值為表5中所示值的一個或者多個的聯合:

型別值 含義

ODS_CHECKED 標記狀態,僅適用於選單項。

ODS_DEFAULT 預設狀態。

ODS_DISABLED 禁止狀態。

ODS_FOCUS 焦點狀態。

ODS_GRAYED 灰化狀態,僅適用於選單項。

ODS_SELECTED 選中狀態。

ODS_HOTLIGHT 僅適用於Windows 98/Me/Windows 2000/XP,熱點狀態:如果滑鼠指標位於控制元件之上,則設定該值,這時控制元件會顯示高亮顏色。

ODS_INACTIVE 僅適用於Windows 98/Me/Windows 2000/XP,非啟用狀態。

ODS_NOACCEL 僅適用於Windows 2000/XP,控制元件是否有快速鍵。

ODS_COMBOBOXEDIT 在自繪組合框控制元件中只繪製選擇區域。

ODS_NOFOCUSRECT 僅適用於Windows 2000/XP,不繪製捕獲焦點的效果。

表5 itemState的型別值與含義

hwndItem 指定了組合框、列表框和按鈕等自繪控制元件的視窗控制代碼;如果自繪的物件為選單項,則表示包含該選單項的選單控制代碼。

hDC 指定了繪製操作所使用的裝置環境。

rcItem 指定了將被繪製的矩形區域。這個矩形區域就是上面hDC的作用範圍。系統會自動裁剪組合框、列表框或按鈕等控制元件的自繪製區域以外的部分。也就是說rcItem中的座標點(0,0)指的就是控制元件的左上角。但是系統不裁剪選單項,所以在繪製選單項的時候,必須先通過一定的換算得到該選單項的位置,以保證繪製操作在我們希望的區域中進行。

itemData

對於選單項,該成員的取值為由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函式傳遞給選單的值。

對於列表框或這組合框,該成員的取值為由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函式傳遞給控制元件的值。

如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值為0。

圖5是個相應的例子,它修改了按鈕的介面:

WM_DRAWITEM[10,155位元組]

圖8 利用WM_DRAWITEM訊息美化介面

實現程式碼如下:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. BOOL CUi6Dlg::OnInitDialog()   
  2. {   
  3. //…
  4. //建立字型
  5. //CFont CUi1View::m_Font
  6.     m_Font.CreatePointFont(120, "Impact");   
  7. //…
  8. }   
  9. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)    
  10. {   
  11. if(nIDCtl == IDC_HELLO_CFAN)   
  12.     {   
  13. //繪製按鈕框架
  14. UINT uStyle = DFCS_BUTTONPUSH;   
  15. //是否按下去了?
  16. if (lpDrawItemStruct->itemState & ODS_SELECTED)   
  17.             uStyle |= DFCS_PUSHED;   
  18.         CDC dc;   
  19.         dc.Attach(lpDrawItemStruct->hDC);   
  20.         dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);   
  21. //輸出文字
  22.         dc.SelectObject(&m_Font);   
  23.         dc.SetTextColor(RGB(0, 0, 255));   
  24.         dc.SetBkMode(TRANSPARENT);   
  25. CString sText;   
  26.         m_HelloCFan.GetWindowText(sText);   
  27.         dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);   
  28. //是否得到焦點
  29. if(lpDrawItemStruct->itemState & ODS_FOCUS)   
  30.         {   
  31. //畫虛框
  32.             CRect rtFocus = lpDrawItemStruct->rcItem;   
  33.             rtFocus.DeflateRect(3, 3);   
  34.             dc.DrawFocusRect(&rtFocus);   
  35.         }   
  36. return;   
  37.     }   
  38.     CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);   
  39. }   

別忘了標記Owner draw屬性:

9[7,386位元組]

 圖9 指定按鈕的Owner draw屬性

值得一提的是,CWnd內部截獲了WM_DRAWITEM、WM_MEASUREITEM等訊息,並對映成子元素的相應虛擬函式的呼叫,如CButton::DrawItem()。所以,以上例子也可以通過派生出一個CButton的派生類,並重載該類的DrawItem()函式來實現。使用虛擬函式機制實現介面美化參見3.4章節。

3.3.5 WM_MEASUREITEM

僅僅WM_DRAWITEM還是不夠的,對於一些特殊的控制元件,如ListBox,系統在傳送WM_DRAWITEM訊息前,還發送WM_MEASUREITEM訊息,需要你設定ListBox中每個專案的高度。

WM_DRAWITEM的對映函式原型如下:

afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );

nIDCtl 該控制元件的ID,如果該元素為選單,則nIDCtl為0

lpMeasureItemStruct指向MEASUREITEMSTRUCT結構物件的指標,MEASUREITEMSTRUCT的結構定義如下:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. typedefstruct tagMEASUREITEMSTRUCT   
  2. {   
  3. UINT   CtlType;   
  4. UINT   CtlID;   
  5. UINT   itemID;   
  6. UINT   itemWidth;   
  7. UINT   itemHeight;   
  8. DWORD  itemData   
  9. } MEASUREITEMSTRUCT;   

CtlType指定了控制元件的型別,其取值如表6所示:

型別值 含義

ODT_COMBOBOX 組合框控制元件

ODT_LISTBOX 列表框控制元件

ODT_MENU 選單項

表6 CtlType的型別值與含義

CtlID 指定自繪控制元件的ID值,該成員不適用於選單項

itemID表示選單項ID,也可以表示可變高度的列表框或組合框中某項的索引值。該成員不適用於固定高度的列表框或組合框。

itemWidth 指定選單項的寬度

itemHeight指定選單項或者列表框中某項的的高度,最大值為255

itemData

對於選單項,該成員的取值為由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函式傳遞給選單的值。

對於列表框或這組合框,該成員的取值為由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函式傳遞給控制元件的值。

圖示出了OnMeasureItem的效果:

WM_MEASUREITEM[9,517位元組]

 圖10 利用WM_MEASUREITEM訊息美化介面

相應的OnMeasureItem()實現如下:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)    
  2. {   
  3. if(nIDCtl == IDC_COLOR_PICKER)   
  4.     {   
  5. //設定高度為30
  6.         lpMeasureItemStruct->itemHeight = 30;   
  7. return;   
  8.     }   
  9.     CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);   
  10. }   

同樣別忘了指定列表框的Owner draw屬性:

11[7,807位元組]

圖11 指定下拉框的Owner draw屬性

3.3.6 NM_CUSTOMDRAW

大家也許熟悉WM_NOTIFY,控制元件通過WM_NOTIFY向父視窗傳送訊息。在WM_NOTIFY訊息體中,部分控制元件會發送NM_CUSTOMDRAW告訴父視窗自己需要繪圖。

可以反射NM_CUSTOMDRAW訊息,如:

ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)

afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);

引數:

pNMHDR 說到底只是一個指標,大多數情況下它指向一個NMHDR結構物件,NMHDR結構如下:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. typedefstruct tagNMHDR   
  2. {    
  3. HWND hwndFrom;    
  4. UINT idFrom;    
  5. UINT code;    
  6. } NMHDR;   

其中:

hwndFrom 傳送方控制元件的視窗控制代碼

idFrom 傳送方控制元件的ID

code 通知程式碼

對於某些控制元件來說,pNMHDR則會解釋成其它內容更豐富的結構物件的指標,如:對於列表控制元件來說,pNMHDR常常指向一個NMCUSTOMDRAW物件,NMCUSTOMDRAW結構如下:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. typedefstruct tagNMCUSTOMDRAWINFO   
  2. {   
  3.     NMHDR  hdr;   
  4. DWORD  dwDrawStage;   
  5. HDC    hdc;   
  6.     RECT   rc;   
  7. DWORD  dwItemSpec;   
  8. UINT   uItemState;   
  9. LPARAM lItemlParam;   
  10. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;   

hdr NMHDR物件

dwDrawStage 當前繪製狀態,其取值如表7所示:

型別值 含義

CDDS_POSTERASE 擦除迴圈結束

CDDS_POSTPAINT 繪製迴圈結束

CDDS_PREERASE 準備開始擦除迴圈

CDDS_PREPAINT 準備開始繪製迴圈

CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam引數有效

CDDS_ITEMPOSTERASE 列表項擦除結束

CDDS_ITEMPOSTPAINT 列表項繪製結束

CDDS_ITEMPREERASE 準備開始列表項擦除

CDDS_ITEMPREPAINT 準備開始列表項繪製

CDDS_SUBITEM 指定列表子項

表7 dwDrawStage的型別值與含義

hdc指定了繪製操作所使用的裝置環境。

rc指定了將被繪製的矩形區域。

dwItemSpec 列表項的索引

uItemState 當前列表項的狀態,其取值如表8所示:

型別值 含義

CDIS_CHECKED 標記狀態。

CDIS_DEFAULT 預設狀態。

CDIS_DISABLED 禁止狀態。

CDIS_FOCUS 焦點狀態。

CDIS_GRAYED 灰化狀態。

CDIS_SELECTED 選中狀態。

CDIS_HOTLIGHT 熱點狀態。

CDIS_INDETERMINATE 不定狀態。

CDIS_MARKED 標註狀態。

表8 uItemState的型別值與含義

lItemlParam 當前列表項的繫結資料

pResult 指向狀態值的指標,指定系統後續操作,依賴於dwDrawStage:

當dwDrawStage為CDDS_PREPAINT,pResult含義如表9所示:

型別值 含義

CDRF_DODEFAULT 預設操作,即系統在列表項繪製迴圈過程不再發送NM_CUSTOMDRAW。

CDRF_NOTIFYITEMDRAW 指定列表項繪製前後傳送訊息。

CDRF_NOTIFYPOSTERASE 列表項擦除結束時傳送訊息。

CDRF_NOTIFYPOSTPAINT 列表項繪製結束時傳送訊息。

表9 pResult的型別值與含義(一)

當dwDrawStage為CDDS_ITEMPREPAINT,pResult含義如表10所示:

型別值 含義

CDRF_NEWFONT 指定後續操作採用應用中指定的新字型。

CDRF_NOTIFYSUBITEMDRAW 列表子項繪製時傳送訊息。

CDRF_SKIPDEFAULT 系統不必再繪製該子項。

表10 pResult的型別值與含義(二)

以下是一個利用NM_CUSTOMDRAW訊息繪製出的多色列表框的例子:

NM_CUSTOMDRAW[16,453位元組]

圖12 利用NM_CUSTOMDRAW訊息美化介面

對應程式碼如下:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)   
  2. {   
  3. //型別安全轉換
  4.     NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);   
  5.     *pResult = 0;   
  6. //指定列表項繪製前後傳送訊息
  7. if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)   
  8.     {   
  9.         *pResult = CDRF_NOTIFYITEMDRAW;   
  10.     }   
  11. elseif(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)   
  12.     {   
  13. //奇數行
  14. if(pLVCD->nmcd.dwItemSpec % 2)   
  15.             pLVCD->clrTextBk = RGB(255, 255, 128);   
  16. //偶數行
  17. else
  18.             pLVCD->clrTextBk = RGB(128, 255, 255);   
  19. //繼續
  20.         *pResult = CDRF_DODEFAULT;   
  21.     }   
  22. }   

注意到上例採取了3.1所推薦的第2種實現方法,派生了一個新類CCoolList。

3.4 使用MFC類的虛擬函式機制

修改Windows介面,除了從Windows訊息機制下功夫,也可以從MFC類下功夫,這應該得益於類的虛擬函式機制。為了防止諸如“面向物件技術”等術語在此氾濫,以下僅舉一段程式碼作為例子:

檢視原始碼拷貝至剪貼簿列印程式碼
  1. void CView::OnPaint()   
  2. {   
  3. // standard paint routine
  4.     CPaintDC dc(this);   
  5.     OnPrepareDC(&dc);   
  6.     OnDraw(&dc);   
  7. }   

這是MFC中viewcore.cpp中的原始碼,很多讀者總不明白OnDraw()和OnPaint()之間的關係,從以上的程式碼中很容易看出,CView的WM_PAINT訊息響應函式OnPaint()會自動呼叫CView::OnDraw()。而作為開發者的使用者,可以通過簡單的OnDraw()的過載實現對WM_PAINT的處理。所以說,對MFC類的虛擬函式的過載是對訊息機制的擴充套件。

以下列出了與介面美化相關的虛擬函式,引數說明略去:

CButton::DrawItem

CCheckListBox::DrawItem

CComboBox::DrawItem

CHeaderCtrl::DrawItem

CListBox::DrawItem

CMenu::DrawItem

CStatusBar::DrawItem

CStatusBarCtrl::DrawItem

CTabCtrl::DrawItem

virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );

Owner draw元素自繪函式

很顯然,點陣圖選單都是通過這個DrawItem畫出來的。限於篇幅,在此不再附以例程。

本文為白喬原創,曾經在《電腦愛好者》合訂本上發表。最新版本已收錄至《把脈VC++》。

===========================================

相關推薦

原創VC美化介面

本文專題討論VC中的介面美化,適用於具有中等VC水平的讀者。讀者最好具有以下VC基礎: 1. 大致瞭解MFC框架的基本運作原理; 2. 熟悉Windows訊息機制,熟悉MFC的訊息對映和反射機制; 3. 熟悉OOP理論和技術; 本文根據筆者多年的開發經驗,並結合簡單的

VC美化介面

本文專題討論VC中的介面美化,適用於具有中等VC水平的讀者。讀者最好具有以下VC基礎: 1. 大致瞭解MFC框架的基本運作原理; 2. 熟悉Windows訊息機制,熟悉MFC的訊息對映和反射機制; 3. 熟悉OOP理論和技術; 本文根據筆者多年的開發經驗,並結合簡單的例子一一展開,希望對讀者有所幫助

原創solr cloud的sql查詢引擎solr-sql

SolrCloud介紹   在我們應用還很渺小的時候,一臺Solr伺服器能完全勝任這份工作,隨著我們應用慢慢長大,訪問也越來越多,一臺Solr伺服器的弊病也逐漸顯現如查詢變慢了,機器宕機就無法繼續提供服務,於是乎我們引入了Solr叢集,通過前端負載均衡和索引Replica

原創圖資料線上互動框架InteractiveGraph

InteractiveGraph 詳細介紹     InteractiveGraph 是一個使用JavaScript開發的開源專案,為大規模圖資料提供了一個基於Web的互動操作框架,其資料可以來自於本地的GSON檔案。  &nb

原創圖資料線上互動伺服器 InteractiveGraph-neo4j

InteractiveGraph-neo4j日前釋出v0.0.1版本。 InteractiveGraph-neo4j基於Neo4j資料庫為InteractiveGraph提供伺服器後端。InteractiveGraph為大型圖資料提供了一個基於web的互動操作框架,其資料可以來自於GSON檔

原創實戰軟體DIY

開發一個屬於自己的軟體?讓流行軟體的包裝打上你的名字?也許這是最令你感到振奮的事情了。然而,這並不是很容易做到的事情。如何從一個好的思路出發,到軟體的最終出品,箇中艱辛足以讓你感到迷茫……本文將圍繞筆者自行開發的免費軟體《FlashNow!動畫瀏覽器》的具體開發過程,循序漸進

原創主考官不是上帝

由於部門經常為一些橫向專案所累,所以我們常常需要招聘新職員,也以此接觸到了很多面試者,包括剛出校門的應屆生和打拼多年的開發人員。最近網路上關於面試技巧的文章多起來了,多有同感,其實我最想對無數準備面試的同仁們說的是:記住,擺正心態,主考官不是上帝! 我們想想,主考官是幹嘛的

VC美化介面(內容覆蓋十分全面,經典)

介面美化 摘要 本文專題討論VC中的介面美化,適用於具有中等VC水平的讀者。讀者最好具有以下VC基礎: 1. 大致瞭解MFC框架的基本運作原理; 2. 熟悉Windows訊息機制,熟悉MFC的訊息對映和反射機制; 3. 熟悉OOP理論和技術; 本文根據筆者多年的開發經驗,並

VC控制元件原創

1 使用Windows標準控制元件 1.1 常見控制元件列表 Windows標準控制元件即普通控制元件,撰寫此文時,筆者每天面對的Word就帶了一臉的控制元件,當然你肯定也熟悉:字型選擇下拉框、工具欄、滾動條、狀態列,如此等等。 常見的Windows標準控制元件在VC裡

原創】寫二十年的程式碼是一種什麼樣的體驗?

原文發表於知乎,http://www.zhihu.com/question/35100740/answer/6235494370後老人,來自皖南山區純的農村娃,大學之後才有的程式設計經歷受前面一個兄弟的

信息安全-2pythonhill密碼算法[原創]

blog 計算 教材 文字 成功 view 思路 html 測試 轉發註明出處:http://www.cnblogs.com/0zcl/p/6106513.html 前言: hill密碼算法我打算簡要介紹就好,加密矩陣我用教材上的3*3矩陣,只做了加密,解密沒有做,不過

《呂鑫VC++6.0就業培訓寶典MFC視頻教程》學習筆記 -- 第二章 MFC原理介紹

第一個 寶典 數據類型 對話 視頻 資源管理 bsp 程序開發 第二章 第二章 MFC原理介紹 2.1 第一個Win32軟件 2.2 Win32對話框程序開發 2.3 程序資源管理和Windows數據類型 2.4 Win32環境下的多對話框管理 2.5 初步學習MFC軟件

原創聊Python小如何系統自學成為Python大牛(基礎一)上

Python Python學習 Python開發 Python自學 原創:聊Python小白如何系統自學成為Python大牛(基礎篇一)上 支持原創 本文章,由頭條py柯西發表,禁止轉載,希望大家支持原創 歡迎大家點擊復制鏈接看原文https://www.toutiao.com/i654581

robotframework 學習(2) 使用RIDE進行介面測試傳送請求和接收資料斷言

一、RIDE的介紹:         RIDE是robotframework圖形操作前端,也可以理解為一種編輯器,它以cell的形式來進行定義資料和方法,返回結果等,我們可以使用它進行建立測試用例和編寫測試指令碼,並且執行自動化測試。  

二十一天學通VC++建立使用者介面執行緒

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Java抽象類與介面對比

先上圖: 下面詳細說下: 1、相同點 (1)都是抽象型別; (2)都可以有實現方法;抽象類中可以實現普通方法,介面中可以實現預設方法(Java 8)。 (3) 都可以不需要實現類或者繼承者去實現所有方法。(以前不行,現在介面中預設方法不需

Linux驅動字元設備註冊新介面(未完)

驅動之字元設備註冊新介面 目前尚不是最終版本,還望有心人自己學習的時候,把自己整合的知識點相關的答案也好問題也好,或者實踐過程中的一些操作截圖,再或者其他的一些想要分享材料發給筆者郵箱:[email protected],我們一起完善這篇部落格!筆者寫這篇部

【搞定Java併發程式設計】第18佇列同步器AQS原始碼分析Condition介面、等待佇列

AQS系列文章: 1、佇列同步器AQS原始碼分析之概要分析 2、佇列同步器AQS原始碼分析之獨佔模式 3、佇列同步器AQS原始碼分析之共享模式 4、佇列同步器AQS原始碼分析之Condition介面、等待佇列 通過前面三篇關於AQS文章的學習,我們深入瞭解了AbstractQ

ASP.NET Core 實戰Linux 小的 .NET Core 部署

 一、前言    最近一段時間自己主要的學習計劃還是按照畢業後設定的計劃,自己一步步的搭建一個前後端分離的 ASP.NET Core 專案,目前也還在繼續學習 Vue 中,雖然中間斷了很長時間,好歹還是堅持下來了,嗯,看了看時間,原本決定的半年完成肯定是完不成了。這兩週重新拾起來學習 Vue,文章也在慢慢的

contos 7新手上路使用與美化

一、windows VS centos 對於從windows轉過來的新手而言,Centos 7使用起來還是有些不習慣的,突出表現在: 一是功能佈局變化很大 比如:在windows下,我們習慣了在桌面或某個資料夾下點:右鍵,然後選 重新整理,以便能獲取最新的目錄或檔案資訊