天天看點

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

導讀:

  本文專題讨論VC中的界面美化,适用于具有中等VC水準的讀者。讀者最好具有以下VC基礎:

  1. 大緻了解MFC架構的基本運作原理;

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

  3. 熟悉OOP理論和技術;

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

   1. 美化界面之開題篇

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

  

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

  

  圖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裡繪圖還算是很簡單的。觀察以下的畫面:

  

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

  

  圖2 使用CDC繪制出的按鈕

  該畫面通過以下代碼自行繪制的假按鈕:

  BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs){ //設定背景色 //CBrush CUi1View::m_Back m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE)); cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); return CView::PreCreateWindow(cs);} int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; //建立字型 //CFont CUi1View::m_Font m_Font.CreatePointFont(120, "Impact"); return 0;} void CUi1View::OnDraw(CDC* pDC){ //繪制按鈕架構 pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH); //輸出文字 pDC->SetBkMode(TRANSPARENT); pDC->TextOut(120, 120, "Hello, CFan!");}

  呵呵,不好意思,這并不是真的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所示:

  

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

  

  圖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界面的例子:

  

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

  

  圖4 使用MFC類的既有函數美化界面

  相關實作代碼如下:

  BOOL CUi2App::InitInstance(){ //… //設定對話框背景色和字型顔色 SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255)); //…} BOOL CUi2Dlg::OnInitDialog(){ //… //設定清單控件屬性帶有表格線 DWORD NewStyle = m_List.GetExtendedStyle(); NewStyle |= LVS_EX_GRIDLINES; m_List.SetExtendedStyle(NewStyle); //設定清單控件字型顔色為紅色 m_List.SetTextColor(RGB(255, 0, 0)); //填充資料 m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100); m_List.InsertColumn(1, "昵稱", LVCFMT_LEFT, 100); m_List.InsertItem(0, "5854165"); m_List.SetItemText(0, 1, "白喬"); m_List.InsertItem(1, "6823864"); m_List.SetItemText(1, 1, "Satan"); //…}

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

   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所示:

  

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

  

  圖5 利用WM_ PAINT消息美化界面

  實作代碼也很簡單:

  void CLazyStatic::OnPaint() { CPaintDC dc(this); // device context for painting //什麼都不輸出,僅僅畫一個矩形框 CRect rc; GetClientRect(&rc); dc.Rectangle(rc); }

  哈哈,簡單吧?不過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為對話框加載了一副位圖背景:

  

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

  

  圖6 利用WM_ ERASEBKGND消息美化界面

  實作代碼也很簡單:

  BOOL CUi4Dlg::OnInitDialog(){//… //加載位圖 //CBitmap m_Back; m_Back.LoadBitmap(IDB_BACK); //…} BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) { CDC dc; dc.CreateCompatibleDC(pDC); dc.SelectObject(&m_Back); //擷取BITMAP對象 BITMAP hb; m_Back.GetBitmap(&hb); //擷取視窗大小 CRect rt; GetClientRect(&rt); //顯示位圖 pDC->StretchBlt(0, 0, rt.Width(), rt.Height(), & dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY); return TRUE;} HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { //設定透明背景模式 pDC->SetBkMode(TRANSPARENT); //設定背景刷子為空 return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);}

  同時别忘了響應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的類型值與含義

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

  BOOL CUi5Dlg::OnInitDialog(){ //… //建立字型 //CFont CUi1View::m_Font1, CUi1View::m_Font2 m_Font1.CreatePointFont(120, "Impact"); m_Font3.CreatePointFont(120, "Arial"); return TRUE; // return TRUE unless you set the focus to a control } HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); if(nCtlColor == CTLCOLOR_STATIC) { //區分靜态控件 switch(pWnd->GetDlgCtrlID()) { case IDC_STATIC1: { pDC->SelectObject(&m_Font1); pDC->SetTextColor(RGB(0, 0, 255)); break; } case IDC_STATIC2: { pDC->SelectObject(&m_Font2); pDC->SetTextColor(RGB(255, 0, 0)); break; } } } return hbr;}

  生成的界面如下:

  

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

  

  圖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的結構定義如下:

  typedef struct tagDRAWITEMSTRUCT{ UINT CtlType; UINT CtlID; UINT itemID; UINT itemAction; UINT itemState; HWND hwndItem; HDC hDC; RECT rcItem; DWORD itemData;}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是個相應的例子,它修改了按鈕的界面:

  

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

  

  圖8 利用WM_DRAWITEM消息美化界面

  實作代碼如下:

  BOOL CUi6Dlg::OnInitDialog(){ //… //建立字型 //CFont CUi1View::m_Font m_Font.CreatePointFont(120, "Impact"); //…} void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { if(nIDCtl == IDC_HELLO_CFAN) { //繪制按鈕架構 UINT uStyle = DFCS_BUTTONPUSH; //是否按下去了? if (lpDrawItemStruct->itemState &ODS_SELECTED) uStyle |= DFCS_PUSHED; CDC dc; dc.Attach(lpDrawItemStruct->hDC); dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle); //輸出文字 dc.SelectObject(&m_Font); dc.SetTextColor(RGB(0, 0, 255)); dc.SetBkMode(TRANSPARENT); CString sText; m_HelloCFan.GetWindowText(sText); dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText); //是否得到焦點 if(lpDrawItemStruct->itemState &ODS_FOCUS) { //畫虛框 CRect rtFocus = lpDrawItemStruct->rcItem; rtFocus.DeflateRect(3, 3); dc.DrawFocusRect(&rtFocus); } return; } CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);}

  别忘了标記Owner draw屬性:

  

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

  

  圖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的結構定義如下:

  typedef struct tagMEASUREITEMSTRUCT{ UINT CtlType; UINT CtlID; UINT itemID; UINT itemWidth; UINT itemHeight; DWORD itemData} 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的效果:

  

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

  

  圖10 利用WM_MEASUREITEM消息美化界面

  相應的OnMeasureItem()實作如下:

  void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if(nIDCtl == IDC_COLOR_PICKER) { //設定高度為30 lpMeasureItemStruct->itemHeight = 30; return; } CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);}

  同樣别忘了指定清單框的Owner draw屬性:

  

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

  

  圖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結構如下:

  typedef struct tagNMHDR{ HWND hwndFrom; UINT idFrom; UINT code; } NMHDR;

  其中:

  hwndFrom 發送方控件的視窗句柄

  idFrom 發送方控件的ID

  code 通知代碼

  對于某些控件來說,pNMHDR則會解釋成其它内容更豐富的結構對象的指針,如:對于清單控件來說,pNMHDR常常指向一個NMCUSTOMDRAW對象,NMCUSTOMDRAW結構如下:

  typedef struct tagNMCUSTOMDRAWINFO{ NMHDR hdr; DWORD dwDrawStage; HDC hdc; RECT rc; DWORD dwItemSpec; UINT uItemState; LPARAM lItemlParam;} 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消息繪制出的多色清單框的例子:

  

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

  

  圖12 利用NM_CUSTOMDRAW消息美化界面

  對應代碼如下:

  void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult){ //類型安全轉換 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast (pNMHDR); *pResult = 0; //指定清單項繪制前後發送消息 if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage) { *pResult = CDRF_NOTIFYITEMDRAW; } else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage) { //奇數行 if(pLVCD->nmcd.dwItemSpec % 2) pLVCD->clrTextBk = RGB(255, 255, 128); //偶數行 else pLVCD->clrTextBk = RGB(128, 255, 255); //繼續 *pResult = CDRF_DODEFAULT; }}

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

   3.4 使用MFC類的虛函數機制

  修改Windows界面,除了從Windows消息機制下功夫,也可以從MFC類下功夫,這應該得益于類的虛函數機制。為了防止諸如“面向對象技術”等術語在此泛濫,以下僅舉一段代碼作為例子:

  void CView::OnPaint(){ // standard paint routine CPaintDC dc(this); OnPrepareDC(&dc); OnDraw(&dc);}

  這是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畫出來的。限于篇幅,在此不再附以例程。

  本文為白喬原創,曾經在《電腦愛好者》合訂本上發表。

本文轉自

http://www.vcer.net/showTip.jsp?tipid=1046595482643

繼續閱讀