MFC UI 美化

本文專題討論VC中的界面美化,適用於具備中等VC水平的讀者。讀者最好具備如下VC基礎: 程序員

1. 大體瞭解MFC框架的基本運做原理; 編程

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

3. 熟悉OOP理論和技術; app

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

 

1. 美化界面之開題篇

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

程序的功能如何如何強大是一回事,它的用戶界面則是另外一回事。千萬不要忽視程序的用戶界面,由於它是給用戶最初最直接的印象,醜陋的界面、不友好的風格確定會影響用戶對軟件程序的使用。 函數

「受之以魚,不若授之以漁」,本教程並不會向你推薦《瑞星殺毒軟件》精美界面的具體實現,而只是向你推薦一些經常使用的美化方法。 工具

 

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 區域特性—包括定義它的點

使用CDC和圖形對象類,在Windows裏繪圖還算是很簡單的。

經過如下代碼自行繪製的假按鈕:

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!");   
    }  
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。

②在普通窗口中,直接建立一個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 );

設置控件的擴展屬性,例如:設置列表控件屬性帶有表格線。

相關實現代碼以下:

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消息,經過消息映射咱們徹底能夠定義控件的界面。

實現代碼也很簡單:

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爲對話框加載了一副位圖背景:

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 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;   
    }

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。

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屬性:

值得一提的是,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()實現以下:

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

一樣別忘了指定列表框的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消息美化界面

對應代碼以下:

void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)   
    {   
        //類型安全轉換   
        NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(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畫出來的。限於篇幅,在此再也不附以例程。

相關文章
相關標籤/搜索