<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

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

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    界面美化 
    摘要
    本文專題討論VC中的界面美化,適用于具有中等VC水平的讀者。讀者最好具有以下VC基礎: 
    1. 大致了解MFC框架的基本運作原理; 
    2. 熟悉Windows消息機制,熟悉MFC的消息映射和反射機制; 
    3. 熟悉OOP理論和技術; 
    本文根據筆者多年的開發經驗,并結合簡單的例子一一展開,希望對讀者有所幫助。 
     
    正文
    1. 美化界面之開題篇
    相信使用過《金山毒霸》、《瑞星殺毒》軟件的讀者應該還記得它們的精美界面: 
     
     
      
    圖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里繪圖還算是很簡單的。觀察以下的畫面: 
     
     圖2 使用CDC繪制出的按鈕
    該畫面通過以下代碼自行繪制的假按鈕: 

    [cpp] view plaincopy
    1. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)  
    2. {  
    3.  //設置背景色  
    4.  //CBrush CUi1View::m_Back  
    5.  m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));  
    6.   
    7.  cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);  
    8.  return CView::PreCreateWindow(cs);  
    9. }  
    10.   
    11. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)   
    12. {  
    13.  if (CView::OnCreate(lpCreateStruct) == -1)  
    14.   return -1;  
    15.   
    16.  //創建字體  
    17.  //CFont CUi1View::m_Font  
    18.  m_Font.CreatePointFont(120, "Impact");  
    19.    
    20.  return 0;  
    21. }  
    22.   
    23. void CUi1View::OnDraw(CDC* pDC)  
    24. {  
    25.  //繪制按鈕框架  
    26.  pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);  
    27.   
    28.  //輸出文字  
    29.  pDC->SetBkMode(TRANSPARENT);  
    30.  pDC->TextOut(120, 120, "Hello, CFan!");  
    31. }  

    呵呵,不好意思,這并不是真的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所示: 
     
     圖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界面的例子: 
     
     
    圖4 使用MFC類的既有函數美化界面
    相關實現代碼如下: 

    [cpp] view plaincopy
    1. BOOL CUi2App::InitInstance()  
    2. {  
    3.     //…  
    4.     //設置對話框背景色和字體顏色  
    5.     SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));   
    6.     //…  
    7. }  
    8.   
    9. BOOL CUi2Dlg::OnInitDialog()  
    10. {  
    11.     //…  
    12.     //設置列表控件屬性帶有表格線  
    13.     DWORD NewStyle = m_List.GetExtendedStyle();  
    14.         NewStyle |= LVS_EX_GRIDLINES;  
    15.     m_List.SetExtendedStyle(NewStyle);  
    16.   
    17.     //設置列表控件字體顏色為紅色  
    18.     m_List.SetTextColor(RGB(255, 0, 0));  
    19.   
    20.     //填充數據  
    21.     m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);  
    22.     m_List.InsertColumn(1, "昵稱", LVCFMT_LEFT, 100);  
    23.   
    24.     m_List.InsertItem(0, "5854165");  
    25.     m_List.SetItemText(0, 1, "白喬");  
    26.   
    27.     m_List.InsertItem(1, "6823864");  
    28.     m_List.SetItemText(1, 1, "Satan");  
    29.     //…  
    30. }  

    嗯,這樣的界面還算不錯吧? 
    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所示: 
    圖5 利用WM_ PAINT消息美化界面 
    實現代碼也很簡單: 

    [cpp] view plaincopy
    1. void CLazyStatic::OnPaint()   
    2. {  
    3.     CPaintDC dc(this); // device context for painting  
    4.       
    5.     //什么都不輸出,僅僅畫一個矩形框  
    6.     CRect rc;  
    7.     GetClientRect(&rc);  
    8.     dc.Rectangle(rc);     
    9. }  

    哈哈,簡單吧?不過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為對話框加載了一副位圖背景: 
     
    圖6 利用WM_ ERASEBKGND消息美化界面
    實現代碼也很簡單: 

    [cpp] view plaincopy
    1. BOOL CUi4Dlg::OnInitDialog()  
    2. {  
    3. //…  
    4.     //加載位圖  
    5.     //CBitmap m_Back;  
    6.     m_Back.LoadBitmap(IDB_BACK);  
    7.     //…  
    8. }  
    9.   
    10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)   
    11. {  
    12.     CDC dc;  
    13.     dc.CreateCompatibleDC(pDC);  
    14.     dc.SelectObject(&m_Back);  
    15.   
    16.     //獲取BITMAP對象  
    17.     BITMAP hb;  
    18.     m_Back.GetBitmap(&hb);  
    19.   
    20.     //獲取窗口大小  
    21.     CRect rt;  
    22.     GetClientRect(&rt);  
    23.     //顯示位圖  
    24.     pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),  
    25.         &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);  
    26.   
    27.     return TRUE;  
    28. }  
    29.   
    30. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)   
    31. {  
    32.     //設置透明背景模式  
    33.     pDC->SetBkMode(TRANSPARENT);  
    34.     //設置背景刷子為空  
    35.     return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);  
    36. }  



    同時別忘了響應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的類型值與含義
    作為一個簡單的例子,觀察以下的代碼: 

    [cpp] view plaincopy
    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.       
    9.     return TRUE;  // return TRUE  unless you set the focus to a control   
    10. }  
    11.   
    12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)   
    13. {  
    14.     HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);  
    15.     if(nCtlColor == CTLCOLOR_STATIC)  
    16.     {  
    17.         //區分靜態控件  
    18.         switch(pWnd->GetDlgCtrlID())  
    19.         {  
    20.             case IDC_STATIC1:  
    21.             {  
    22.                 pDC->SelectObject(&m_Font1);  
    23.                 pDC->SetTextColor(RGB(0, 0, 255));  
    24.                 break;  
    25.             }  
    26.             case IDC_STATIC2:  
    27.             {  
    28.                 pDC->SelectObject(&m_Font2);  
    29.                 pDC->SetTextColor(RGB(255, 0, 0));  
    30.                 break;  
    31.             }  
    32.         }  
    33.     }  
    34.   
    35.     return hbr;  
    36. }  

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

    [cpp] view plaincopy
    1. typedef struct 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是個相應的例子,它修改了按鈕的界面: 
     
    圖8 利用WM_DRAWITEM消息美化界面
    實現代碼如下: 

    [cpp] view plaincopy
    1. BOOL CUi6Dlg::OnInitDialog()  
    2. {  
    3.     //…  
    4.     //創建字體  
    5.     //CFont CUi1View::m_Font  
    6.     m_Font.CreatePointFont(120, "Impact");  
    7.     //…  
    8. }  
    9.   
    10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)   
    11. {  
    12.     if(nIDCtl == IDC_HELLO_CFAN)  
    13.     {  
    14.         //繪制按鈕框架  
    15.   
    16.         UINT uStyle = DFCS_BUTTONPUSH;  
    17.         //是否按下去了?  
    18.         if (lpDrawItemStruct->itemState & ODS_SELECTED)  
    19.             uStyle |= DFCS_PUSHED;  
    20.   
    21.         CDC dc;  
    22.         dc.Attach(lpDrawItemStruct->hDC);  
    23.         dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);  
    24.   
    25.         //輸出文字  
    26.         dc.SelectObject(&m_Font);  
    27.         dc.SetTextColor(RGB(0, 0, 255));  
    28.         dc.SetBkMode(TRANSPARENT);  
    29.   
    30.         CString sText;  
    31.         m_HelloCFan.GetWindowText(sText);  
    32.         dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);  
    33.   
    34.         //是否得到焦點  
    35.         if(lpDrawItemStruct->itemState & ODS_FOCUS)  
    36.         {  
    37.             //畫虛框  
    38.             CRect rtFocus = lpDrawItemStruct->rcItem;  
    39.             rtFocus.DeflateRect(3, 3);  
    40.             dc.DrawFocusRect(&rtFocus);  
    41.         }  
    42.   
    43.         return;  
    44.     }  
    45.     CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);  
    46. }  

    別忘了標記Owner draw屬性: 
     圖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的結構定義如下: 

    [cpp] view plaincopy
    1. typedef struct 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的效果: 
     圖10 利用WM_MEASUREITEM消息美化界面
    相應的OnMeasureItem()實現如下: 

    [cpp] view plaincopy
    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 指定下拉框的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結構如下: 

    [cpp] view plaincopy
    1. typedef struct tagNMHDR  
    2. {   
    3.     HWND hwndFrom;   
    4.     UINT idFrom;   
    5.     UINT code;   
    6. } NMHDR;  

    其中: 
    hwndFrom 發送方控件的窗口句柄 
    idFrom 發送方控件的ID 
    code 通知代碼 
    對于某些控件來說,pNMHDR則會解釋成其它內容更豐富的結構對象的指針,如:對于列表控件來說,pNMHDR常常指向一個NMCUSTOMDRAW對象,NMCUSTOMDRAW結構如下: 

    [cpp] view plaincopy
    1. typedef struct 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消息繪制出的多色列表框的例子: 
     
    圖12 利用NM_CUSTOMDRAW消息美化界面 
    對應代碼如下: 

    [cpp] view plaincopy
    1. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)  
    2. {  
    3.     //類型安全轉換  
    4.     NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);  
    5.     *pResult = 0;  
    6.       
    7.     //指定列表項繪制前后發送消息  
    8.     if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)  
    9.     {  
    10.         *pResult = CDRF_NOTIFYITEMDRAW;  
    11.     }  
    12.     else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)  
    13.     {  
    14.         //奇數行  
    15.         if(pLVCD->nmcd.dwItemSpec % 2)  
    16.             pLVCD->clrTextBk = RGB(255, 255, 128);  
    17.         //偶數行  
    18.         else  
    19.             pLVCD->clrTextBk = RGB(128, 255, 255);  
    20.         //繼續  
    21.         *pResult = CDRF_DODEFAULT;  
    22.     }  
    23. }  

    注意到上例采取了3.1所推薦的第2種實現方法,派生了一個新類CCoolList。 
     
     
    3.4 使用MFC類的虛函數機制

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

    [cpp] view plaincopy
    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畫出來的。限于篇幅,在此不再附以例程。 
     
    參考文獻
    本文為白喬原創,曾經在《電腦愛好者》合訂本上發表。 

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

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全