天天看點

VC++建立個性的對話框之MFC篇

轉自http://hi.baidu.com/scutsolo/blog/item/c5366c51cb4fdc888d543001.html

本文涉及以下幾個問題:

1. 修改對話框的背景顔色

2. 用位圖作為對話框的背景

3. 改變靜态控件文字和背景顔色

4. 改變編輯框文字和背景顔色

5. 改變CheckBox的文字和背景顔色

6. 改變RadioBox的文字和背景顔色

7. 改變按鈕的背景顔色和文字顔色

8. 在對話框中使用Picture控件

9. 修改Picture控件顯示的位圖

10.使用LoadImage從資源裝入位圖

想使自己的軟體與衆不同就要給軟體加點“色”,一個顔色搭配協調的視窗要比windows千篇一律的灰底黑字更能吸引别人的眼球。設想如果html浏覽器顯示的網頁都是白底黑字,還會有那麼多的mm喜歡上網嗎?可能網際網路的人氣将下降一半。做個出色的界面對于老手來說可能不在話下,但是對于新手來說還是無從下手,使用BCGControlBar和Xtreme Toolkit是個很好的選擇,不過對于一個小程式使用這麼大的庫未免有頭重腳輕的感覺。其實不使用這些龐然大物一樣可以做個很“色”的界面,本文就結合CSDN論壇上經常被問起的問題,介紹幾個給對話框上色的方法。本文的方法都是針對MFC程式的,其他方法請參看“建立有個性的對話框之ATL/WTL篇”。

第一步:改變對話框的背景顔色

如何改變對話框的背景顔色這個問題常常出現在論壇上,可見大家對Windows預設的灰色對話框是多麼不滿。MFC程式修改對話框的背景和文字顔色最簡單的方法就是調用SetDialogBkColor函數,SetDialogBkColor是CWinApp類的成員函數,以下是該函數的原型:

void CWinApp::SetDialogBkColor(COLORREF clrCtlBk, COLORREF clrCtlText);

請注意,SetDialogBkColor函數并不是對Windows的某個API的封裝,他是MFC架構的一部分,是以不使用MFC的程式也就不能享受這種友善。這個函數的使用很簡單,在程式的CWinApp派生類的InitInstance函數中添加一行代碼就行了:

SetDialogBkColor(RGB(188,197,230),RGB(13,125,188));

圖.1 就是運作效果:

圖.1 SetDialogBkColor效果圖

使用SetDialogBkColor也有局限的地方,那就是所有的控件文字顔色都一樣,不能針對不同的控件設定不同的文字顔色,還有就是不能設定Edit控件的顔色。不使用SetDialogBkColor函數,直接編寫代碼控制對話框的背景顔色和控件文字顔色也不是很困難的事情,并且這種方法能夠提供更靈活的顔色設定方案,比如對不同類型的控件使用不同的文字顔色,使用高亮度的背景顔色突出某個控件等等,最重要的是能夠控制Edit控件的文字和背景顔色,下面就介紹這種方法。

首先是改變對話框的背景顔色。當Windows系統需要重畫某個視窗客戶區的背景的時候,就會向該視窗發送WM_ERASEBKGND 消息,視窗的處理過程響應這個消息重新畫視窗的背景,這個過程稱之為“自畫”。改變對話框的背景顔色的原理很簡單,就是響應這個消息,用自定義的顔色填充對話框的客戶區背景,代替對話框視窗預設的背景填充動作。許多新手經常問:“為什麼在class wizard中找不到對話框的WM_ERASEBKGND消息,是不是對話框沒有這個消息”?其實對話框也是視窗,它也有WM_ERASEBKGND消息,隻是MFC的class wizard使用的dialog過濾器将其過濾掉了(隻是在message視窗的顯示中過濾了,并不是真的不響應這個消息),為的是代碼編寫過程中突出對話框專有的消息和控件事件。如圖.2 所示,隻要在class wizard中的“class info” table标簽下将消息過濾器改成Windows就可以在對話框的消息清單中看到WM_ERASEBKGND了。

圖.2 修改消息過濾器

現在通過class wizard添加WM_ERASEBKGND的消息響應函數,并如下所示修改這個函數:

BOOL CCustDlgDlg::OnEraseBkgnd(CDC* pDC)

{

CRect rcClient;

GetClientRect(&rcClient);

pDC->FillRect(&rcClient,&m_brBkgnd);

return TRUE;

// return CDialog::OnEraseBkgnd(pDC);

}

m_brBkgnd是個CBrush,在此之前已經初始化過了,關鍵代碼是最後傳回TRUE,而不是預設的調用基類函數,傳回TRUE意在告訴Windows:“我已經畫過背景了,你不要再畫了”。現在來看看運作的效果:

圖.3 重畫背景的效果

使用位圖作為對話框的背景也不難,就是在整個客戶區畫一個位圖背景,

第二步:改變控件的顔色

看起來不如剛才效果好,控件文字的顔色和背景色都沒有改變,這是因為我們還沒有處理WM_CTLCOLOR消息。WM_CTLCOLOR是Windows的控件向其父視窗發送最頻繁的通知消息之一,例如,許多控件發送WM_CTLCOLOR消息給父視窗,讓父視窗提供畫刷來畫自己的背景。MFC的視窗類對這個通知消息特殊對待,如果父視窗沒有處理這個通知消息,MFC的視窗類就根據WM_CTLCOLOR通知消息的來源将這個WM_CTLCOLOR消息發送回控件,讓控件自己處理,這就是所謂的“消息反射”,不僅是WM_CTLCOLOR,MFC對很多通知消息都做了反射,不過我們今天的例子沒有使用“消息反射”,我們在控件的父視窗,也就是對話框視窗處理這個通知消息。還有一點需要說明的是,WM_CTLCOLOR消息是16位的Windows平台的消息,在32位的Windows平台上取而代之的是一系列更明确的通知消息:

WM_CTLCOLORBTN 按鈕控件

WM_CTLCOLORDLG 對話框

WM_CTLCOLOREDIT 編輯控件

WM_CTLCOLORLISTBOX 清單框控件

WM_CTLCOLORSCROLLBAR 滾動條控件

WM_CTLCOLORSTATIC 靜态文本控件

MFC為了相容性考慮,仍舊使用OnCtlColor響應這些消息,但是通過參數nCtlColor來具體的區分他們。在這個函數中,我們可以通過改變pDC參數的屬性來改變控件的繪制,并傳回相應的畫刷句柄給控件,控件使用這個畫刷畫自己的背景。下面是我們修改後的OnCtlColor函數:

HBRUSH CCustDlgDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

pDC->SetTextColor(m_clrText);

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd; //因為CBrush類實作了HBRUSH類型轉換操作符

// return hbr;

}

圖.4 就是這段代碼的效果,在這裡我們不分“青紅皂白”,向所有的控件傳回我們自己的畫刷,看起來不錯,Edit控件的文字顔色也改了,但是好像多行Edit控件有了麻煩,看來需要對多行Edit控件特殊對待。

圖.4 重載OnCtlColor之後的效果

對于多行Edit控件特殊處理,如下所示,上面的問題解決了:

HBRUSH CCustDlgDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

if(pWnd->GetDlgCtrlID() == IDC_EDIT_MULTI_LINE) //IDC_EDIT_MULTI_LINE是多行Edir 控件的ID

{

pDC->SetTextColor(m_clrText);

return hbr;

}

else

{

pDC->SetTextColor(m_clrText);

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

}

上面的代碼解決了IDC_EDIT_MULTI_LINE的問題,但是對每個多行Edit控件都要判斷ID,下面的方法可以一勞永逸地解決多行編輯控件的問題:

HBRUSH CCustDlgDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

TCHAR szClassName[64];

::GetClassName(pWnd->GetSafeHwnd(),szClassName,64);

if(lstrcmpi(szClassName,_T("Edit")) == 0) //是Edit 控件

{

DWORD dwStyle = pWnd->GetStyle();

if((dwStyle & ES_MULTILINE) == ES_MULTILINE) //多行edit 控件

{

pDC->SetTextColor(m_clrText);

return hbr;

}

else

{

pDC->SetTextColor(m_clrText);

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

}

else //不是編輯 控件

{

pDC->SetTextColor(m_clrText);

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

}

下面我們針對每個控件設定特殊的顔色,區分控件可以通過控件的ID,修改控件背景也很簡單,直接傳回相應的畫刷就可以了,下面就是顔色設定的完整代碼:

HBRUSH CCustDlgDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

TCHAR szClassName[64];

::GetClassName(pWnd->GetSafeHwnd(),szClassName,64);

if(lstrcmpi(szClassName,_T("Edit")) == 0) //是Edit 控件

{

DWORD dwStyle = pWnd->GetStyle();

if((dwStyle & ES_MULTILINE) == ES_MULTILINE) //多行edit 控件

{

pDC->SetTextColor(m_clrText);

return hbr;

}

else

{

pDC->SetTextColor(m_clrText);

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

}

else //不是編輯 控件

{

if(pWnd->GetDlgCtrlID() == IDC_STC_REDTEXT)

{

pDC->SetTextColor(RGB(255,0,0));

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

else if(pWnd->GetDlgCtrlID() == IDC_STC_BLUETEXT)

{

pDC->SetTextColor(RGB(0,0,255));

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

else if(pWnd->GetDlgCtrlID() == IDC_STC_BLUETEXTWHITEBACK)

{

pDC->SetTextColor(RGB(0,0,255));

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brControlBkgnd1;

}

else if(pWnd->GetDlgCtrlID() == IDC_CHK_GREEN)

{

pDC->SetTextColor(RGB(0,255,0));

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

else if(pWnd->GetDlgCtrlID() == IDC_RAD_BLUE)

{

pDC->SetTextColor(RGB(0,0,255));

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

else if(pWnd->GetDlgCtrlID() == IDC_CHK_GREEN2)

{

pDC->SetTextColor(RGB(0,255,0));

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brControlBkgnd2;

}

else if(pWnd->GetDlgCtrlID() == IDC_RADIO2)

{

pDC->SetTextColor(RGB(0,0,255));

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brControlBkgnd2;

}

else

{

pDC->SetTextColor(m_clrText);

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_brBkgnd;

}

}

}

現在看看效果:

圖.5 修改OnCtlColor之後的效果

上面的代碼是根據控件ID來設定顔色,還可以根據控件的類型統一設定某種控件的顔色,這就要用到nCtlColor參數,nCtlColor參數用來指明發送這個通知消息的控件的類型,nCtlColor可以是以下取值:

CTLCOLOR_BTN

CTLCOLOR_DLG

CTLCOLOR_EDIT

CTLCOLOR_LISTBOX

CTLCOLOR_MSGBOX

CTLCOLOR_SCROLLBAR

CTLCOLOR_STATIC

第三步:使用位圖作對話框的背景

使用位圖作為對話框的背景也很簡單,就是在OnEraseBkgnd中用位圖填充客戶區,隻是在OnCtlColor中需要注意傳回空畫刷代替原來的畫刷,傳回空畫刷是為了阻止控件繪制自己的背景,進而破壞位圖背景的完整性,但是有時候傳回空畫刷會對其他控件産生不良影響,是以我們隻處理了CTLCOLOR_BTN和CTLCOLOR_STATIC兩種類型的消息:

HBRUSH CBmpBkgndDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

if(nCtlColor == CTLCOLOR_BTN || nCtlColor == CTLCOLOR_STATIC)

{

pDC->SetTextColor(RGB(0,0,255));

pDC->SetBkMode(TRANSPARENT);

return (HBRUSH)m_HollowBrush;

}

pDC->SetTextColor(RGB(0,0,255));

pDC->SetBkMode(TRANSPARENT);

return hbr;

}

下面是使用位圖背景和空畫刷的效果:

圖.6 使用位圖背景的效果

第四步:單獨處理按鈕控件

現在看來按鈕控件還是影響整體效果,WM_CTLCOLORBTN好像對于push button類型的按鈕控件沒有效果,不過push button也是支援自畫的,在使用自畫按鈕之前,我們先來看看控件自畫的原理。Windows的控件都有預設的外觀,但是許多控件有支援“自畫”,也就是讓使用者定制控件的外觀,當給一個控件指定自畫的樣式之後,控件在重畫自己的時候向父視窗發送WM_MEASUREITEM和WM_DRAWITEM消息,父視窗響應這兩個消息,定位控件的大小并繪制控件,進而使控件有定制的外觀。但是每個控件的自畫都由父視窗完成加重了父視窗的負擔,也不利于代碼重用,是以,MFC對這些消息進行了反射處理,就是将消息發還位控件,由控件響應消息,自己繪制,這樣将自畫代碼封裝在控件類中,提高了代碼的重用性。很多MFC的控件類都自己處理這兩個消息,派生類可以重載MeasureItem和DrawItem自己畫控件的外觀,CButton就是這樣的控件類。

現在就來做一個自畫的按鈕類,首先從CButton派生一個類,我們命名為CSMButton,然後重載DrawItem和PreSubclassWindow,重載PreSubclassWindow的原因是在CSMButton子類化按鈕控件之前先給按鈕添加BS_OWNERDRAW樣式,否則按鈕就不會向父視窗發送WM_DRAWITEM消息,MFC的消息反射就不會發生,我們的DrawItem就不會被調用,嗯,後果很嚴重。當然也可以讓CSMButton的使用者自己給按鈕添加BS_OWNERDRAW樣式,但是會讓人覺得沒水準,嗯,後果也很嚴重。接下來添加對WM_CAPTURECHANGED、WM_MOUSEMOVE、WM_SETCURSOR和WM_KILLFOCUS四個消息的響應函數,對這四個消息的響應是為了給按鈕增加更多的功能,比如使按鈕看起來象工具欄的按鈕,改變滑鼠的形狀等等。

關于CSMButton類的使用就像CButton一樣,為按鈕添加變量就行了,示範代碼中包含了這個類的源代碼以及用法,這裡不在贅述。CSMButton類的功能很簡單,但是完成了一個自畫按鈕的架構,大家可以修改代碼實作自己的風格,網上也有很多這樣的類,功能更強大,比如STButton等。現在看看CSMButton的效果:

圖.7 使用自畫按鈕後的效果

第五步:使用Picture Box控件

想要在對話框上顯示位圖,可以使用很複雜的控件或CxImage之類的庫,也可以很簡單地使用Picture Box。Picture Box預設的樣式使Frame,需要手工改成Bitmap,如下圖所示:

圖.8 使用位圖

VC6的內建環境不支援24位位圖的浏覽和編輯,但是并不影響使用,本例使用的位圖都是24位的,為的是省去調色闆的處理,本人比較懶。使用如下代碼就可以更改Picture Box中的位圖:

m_hCat1 = (HBITMAP)::LoadImage(AfxGetResourceHandle(),MAKEINTRESOURCE(IDB_BITMAP1),IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION);

GetDlgItem(IDC_STC_PICTURE)->SendMessage(STM_SETIMAGE,IMAGE_BITMAP, (LPARAM)m_hCat1);

裝載位圖還可以這樣:

m_hCat1 = (HBITMAP)::LoadImage(AfxGetResourceHandle(),(LPCTSTR)IDB_BITMAP1,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION);

這是最終的效果:

圖.9 對話框的最終效果

繼續閱讀