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

來源:互聯網
上載者:User

本文涉及以下幾個問題:

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的Integration Environment不支援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 對話方塊的最終效果

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.