1.1 常見控件列表
Windows標(biāo)準(zhǔn)控件即普通控件,撰寫此文時,筆者每天面對的Word就帶了一臉的控件,當(dāng)然你肯定也熟悉:字體選擇下拉框、工具欄、滾動條、狀態(tài)欄,如此等等。
常見的Windows標(biāo)準(zhǔn)控件在VC里就有:
圖2 控件集窗口
通常這個控件集窗口在你的對話框設(shè)計界面的附近總能找到,如果找不到,在VC工具欄的任何空白處點擊右鍵,在彈出菜單的Controls菜單項前面打上勾即可,如圖所示:
圖3 顯示控件集窗口
你也許已經(jīng)看到了,我們圖1所示的FlashPlayer中有3個控件是Windows標(biāo)準(zhǔn)控件,好,我們先系統(tǒng)地學(xué)習(xí)一下怎么使用這3個控件。
1.2 引入控件
1. 準(zhǔn)備對話框
對話框相當(dāng)于控件的容器,我們當(dāng)然要先準(zhǔn)備一個對話框。好辦,直接創(chuàng)建一個基于對話框的工程就是了:
圖4 準(zhǔn)備對話框Step 1
注意,在Step 2中要確認(rèn)“3D Controls”和“ActiveX Controls”前面打上勾,如圖所示:
圖5 準(zhǔn)備對話框Step 2
這樣你的程序就支持三維控件和我們后面即將使用到的ActiveX控件了。如果去掉了勾,或者你面對的正是你師兄當(dāng)年準(zhǔn)備論文的舊的project,它好像并不支持ActiveX控件,那該怎么辦呢(好多VC網(wǎng)友總是帶著那張哭喪臉的表情問我這種問題)?沒事,在主程序文件的InitInstance()函數(shù)頭部加上以下語句即可:
BOOL CFlashPlayerApp::InitInstance()
{ AfxEnableControlContainer();
#ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL
#else Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
//…
}
對話框準(zhǔn)備好了,缺省情況下,它總會自動帶上3個控件:2個按鈕,“確定”與“取消”和一個“TODO: 在這里設(shè)置對話控制” 文本標(biāo)簽。
2. 準(zhǔn)備文本編輯框
文本標(biāo)簽我們并不需要,那么就直接刪除之。但我們需要一個文本編輯框,用以顯示Flash文件路徑。引入新的Windows標(biāo)準(zhǔn)控件很簡單,只要簡單地從控件集窗口選擇相應(yīng)的控件拖拽至對話框即可。好,我們拖來一個文本編輯框:
圖6 引入文本編輯框控件
1.3 設(shè)置控件屬性
引入來的控件,可以通過鼠標(biāo)簡單的拖拽調(diào)整其位置和大小,當(dāng)然,你還可以通過屬性對話框來設(shè)計這個控件。在控件上右擊鼠標(biāo),彈出菜單:
圖7 控件的屬性窗口
這個菜單即將覆蓋本教程的大部分內(nèi)容,菜單最下面的三行分別為:
ClassWizard… 為控件創(chuàng)建類及變量
Events… 為控件映射事件
Properties 設(shè)置控件的屬性
前兩項后面的內(nèi)容將要討論到,我們現(xiàn)在只關(guān)心Properties(屬性),點擊它即可彈出文本編輯框的屬性界面:
圖8 文本編輯框的屬性界面
我們按照以下的步驟進(jìn)行控件的屬性設(shè)置:
1. 在“General(普通)”標(biāo)簽頁里將文本編輯框的ID修改成IDC_FLASH_FILE,而不是缺省的IDC_STATIC;
2. 在“Styles(樣式)”標(biāo)簽頁里將文本編輯框的Read-only打勾,這樣我們的文本編輯框就顯示成灰色,并且只讀。也就是說,它的,內(nèi)容只能由程序更改,用戶不能手動輸入;
3. 同理,在兩個按鈕的“General(普通)”標(biāo)簽頁里,設(shè)置它們的Caption(標(biāo)題),分別為“瀏覽…”和“退出”;
4. 在“瀏覽…”按鈕的“General(普通)”標(biāo)簽頁里,將它的ID設(shè)置成ID_BROWSER;
需要指出的是,每個控件都具有ID,就相當(dāng)于每個人都具有一個身份證號。除了IDC_STATIC(它的值是-1),每個對話框的控件的ID都必須唯一,不能重復(fù)。一些特定的ID代表特定的含義,如:IDOK對應(yīng)于“確定”按鈕,IDCANCEL對應(yīng)于“取消”按鈕,IDC_STATIC則對應(yīng)于一個匿名控件。MFC認(rèn)識這些特定的ID,并賦值于指定的行為,例如:你可以不需要編寫任何代碼,就可以使用“確定”按鈕關(guān)閉對話框,就是這個原因。
以上內(nèi)容指引你如何修改控件的屬性,包括它的ID、標(biāo)題以及樣式。這種修改都是所見即所得的,運行程序,你就會欣喜地發(fā)現(xiàn),一切確實都改過來了。
1.4 映射控件變量
好了,你現(xiàn)在有了一個文本編輯框,可是如何使用它,你還是一無所知。那么,我們開始學(xué)習(xí)如何將控件映射成一個變量,這個過程即“映射控件變量”,或曰“綁定控件變量”。
映射控件變量是VC的一個很好的功能,有了它,你就可以象使用一個變量一樣控制控件。映射成什么類型的變量,這依賴于你的控件。一般來說,一個控件可以映射成一個值變量(Value),也可以映射成一個控件對象(Control)。如:一個文本編輯框既可以映射成一個CString值,也可以映射成一個CEdit對象,CString是個字符串,而CEdit則是MFC為文本編輯框?qū)iT準(zhǔn)備的控件類。
1.4.1 控件 -> 值變量
我們從簡單的入手,先將我們的文件路徑文本編輯框映射成一個普通的值變量,按照以下操作慢慢來:
1. 在編輯框上打開右鍵菜單(如圖7所示),選擇ClassWizard…,彈出以下界面:
圖9 ClassWizard窗口
有點暈。先不管別的,因為我們要映射變量,我們就選擇“Member Variables”標(biāo)簽頁, 這兒可以看到當(dāng)前對話框中的所有控件,包括我們的文本框和其它兩個按鈕;
2. 找到我們的IDC_FLASH_FILE,雙擊之(或者點擊“Add Variable…”按鈕),就彈出了映射控件變量窗口:
圖10 添加控件值變量
該界面分別要求輸入變量名、類別和變量類型,不用爭了,我們選擇Value和CString,將IDC_FLASH_FILE映射成CString m_sFilePath。
大功告成!可以同時觀察VC程序代碼中的變化:
class CFlashPlayerDlg : public CDialog
{ // … // Dialog Data
//{{AFX_DATA(CFlashPlayerDlg)
enum { IDD = IDD_FLASHPLAYER_DIALOG };
CString m_sFilePath;
//}}AFX_DATA }
CFlashPlayerDlg::CFlashPlayerDlg(CWnd* pParent /*=NULL*/) : CDialog(CFlashPlayerDlg::IDD, pParent)
{ //{{AFX_DATA_INIT(CFlashPlayerDlg)
m_sFilePath = _T("");
//}}AFX_DATA_INIT
// …
}
void CFlashPlayerDlg::DoDataExchange(CDataExchange* pDX)
{ CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CFlashPlayerDlg)
DDX_Text(pDX, IDC_FLASH_FILE, m_sFilePath);
//}}AFX_DATA_MAP
}
下面我們先試試這個CString m_sFilePath的使用,如:
BOOL CFlashPlayerDlg::OnInitDialog()
{ CDialog::OnInitDialog(); m_sFilePath = "空即是色"; // …
}
遺憾的是,以上的代碼是沒用的!與Visual Basic不同,在VC的世界里,映射的值變量與實際控件的內(nèi)容并不保持同步,欲使用映射的值變量就必須額外地使用到UpdateData()函數(shù),它的函數(shù)原型如下:
CWnd::UpdateData BOOL UpdateData( BOOL bSaveAndValidate = TRUE );
簡單地說,UpdateData(TRUE)讀取對話框中各控件的內(nèi)容,并及時反映到其映射值中去;UpdateData(FALSE)則恰恰相反,它將當(dāng)前映射值反映到控件中去,如:在文本框里面顯示你剛剛設(shè)置的字符串“空即是色”。
我們現(xiàn)在是要通過變量修改控件,因此,我們采用UpdateData(FALSE),以上的代碼修改成:
BOOL CFlashPlayerDlg::OnInitDialog()
{ CDialog::OnInitDialog();
m_sFilePath = "請點擊“瀏覽”按鈕選擇文件!";
UpdateData(FALSE);
// …
}
現(xiàn)在的運行界面就可以看到m_sFilePath的內(nèi)容了:
圖11 使用映射變量修改文本編輯框的內(nèi)容
UpdateData ()如何知道哪些控件與哪些變量映射,靠的是DoDataExchange(),讀者沒必要對DoDataExchange()了解更多,但注意不要隨便手動修改DoDataExchange()中的代碼。
1.4.2 控件 -> 控件對象
與值變量不同,如果映射變量是一個控件對象,那么就沒有必要調(diào)用UpdateData ()來完成同步。將一個控件映射成控件對象也很簡單,依2.4.1的步驟而行,同樣彈出添加變量窗口:
圖12 添加控件對象變量
注意在Category(類別)一欄選擇Control,變量類型別無選擇,選擇CEdit,點擊OK。這樣我們的IDC_FLASH_FILE即有了2個映射變量:
圖13 同時為控件綁定值變量和對象變量
同樣地,以上的映射亦體現(xiàn)在代碼里:
void CFlashPlayerDlg::DoDataExchange(CDataExchange* pDX)
{ CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CFlashPlayerDlg)
DDX_Control(pDX, IDC_FLASH_FILE, m_FilePathEdit);
DDX_Text(pDX, IDC_FLASH_FILE, m_sFilePath);
//}}AFX_DATA_MAP
}
DDX_Control標(biāo)明控件IDC_FLASH_FILE與m_FilePathEdit映射,接下來,我們就可以通過使用CEdit類來控制文本編輯框控件,如圖所示:
圖14 使用CEdit變量修改控件內(nèi)容
這時候,使用如下代碼同樣可以達(dá)到設(shè)置編輯框內(nèi)容的效果:
m_FilePathEdit.SetWindowText("請點擊“瀏覽”按鈕選擇文件!");
相比而言,使用控件對象,可以最大限度地控制控件;但是,誰都知道,使用一個對象的步驟比操縱一個變量要麻煩得多。
刪除控件變量并不需要任何技巧,在ClassWizard窗口選定該變量,點擊“Delete Variable”即可。為了配合后續(xù)教程,我們先刪掉這個CEdit m_FilePathEdit,在2.6節(jié),我們將要把這個文本編輯框映射成我們自定義的類型。
1.5 響應(yīng)控件事件
控件都是一個個的窗口,所以控件都有事件。最簡單的,按鈕被鼠標(biāo)按下時,將產(chǎn)生BN_CLICKED事件。
那好,就響應(yīng)“瀏覽”按鈕的BN_CLICKED事件:
1. 彈出右鍵菜單(如圖7所示),選擇“Events…”,彈出如下窗口:
圖15 控件事件窗口
窗口主要分成3部分:左側(cè)為當(dāng)前控件的所有消息列表,右上側(cè)為已響應(yīng)的消息,右下側(cè)為當(dāng)前窗口所有的控件列表(包括窗口本身)。注意到我們的“瀏覽”按鈕現(xiàn)在還沒有任何已響應(yīng)的消息。
2. 雙擊左側(cè)的“BN_CLICKED”(或者點擊右邊的“Add Handler”按鈕),即彈出如下窗口,提示為響應(yīng)函數(shù)取個名字:
圖16 為事件響應(yīng)函數(shù)命名
響應(yīng)函數(shù)的名字一般皆以O(shè)n打頭,缺省的名字OnBrowser就蠻好,點擊“OK”。
可以看到,現(xiàn)在我們的事件窗口已經(jīng)包含了這個BN_CLICKED:
圖17 已添加事件響應(yīng)
3. 選擇BN_CLICKED事件點擊Edit Existing(也可以在上一步即點擊Add and Edit),即可觀察到代碼中已增加以下內(nèi)容:
BEGIN_MESSAGE_MAP(CFlashPlayerDlg, CDialog)
//{{AFX_MSG_MAP(CFlashPlayerDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(ID_BROWSER, OnBrowser)
//}}AFX_MSG_MAP END_MESSAGE_MAP()
void CFlashPlayerDlg::OnBrowser()
{
// TODO: Add your control notification handler code here
}
4. 我們現(xiàn)在就可以修改這個OnBrowser()的代碼,以實現(xiàn)我們需要的操作,既然我們希望它彈出一個選擇文件的對話框,那么我們就這么寫:
void CFlashPlayerDlg::OnBrowser()
{ //文件對話框
CFileDialog FileDialog(true, ".swf", "", OFN_EXPLORER, "Flash動畫文件(*.swf)|*.swf|所有文件(*.*)|*.*||", this); //顯示對話框
if(FileDialog.DoModal() == IDOK)
{ m_sFilePath = FileDialog.GetPathName();
UpdateData(FALSE);
}
}
呵呵,是不是又注意到UpdateData(FALSE)了?沒錯,將變量賦值了,就應(yīng)該調(diào)用UpdateData(FALSE)將值反映出來?,F(xiàn)在我們的運行界面可以選擇文件了:
圖18 事件響應(yīng)函數(shù)OnBrowser()的實現(xiàn)
添加事件處理還有一種方法,就是使用我們前面熟悉到的ClassWizard窗口,打開“Message Maps(消息映射)”標(biāo)簽頁,即可管理我們的控件消息:
圖19 使用ClassWizard窗口管理消息映射
1.6 控件子類化
盡管Windows系統(tǒng)提供了各種豐富的控件,但總會有美中不足的時候。你是否注意到了?我們現(xiàn)在的文本框遠(yuǎn)沒有FlashPlayer成品中的好看。FlashPlayer中的文本框黑底綠字,那叫一個酷!
那么我們就準(zhǔn)備在原有文本編輯框的基礎(chǔ)上再做一些定制,解決的辦法是控件的子類化。
子類化是應(yīng)用于窗口的高級技術(shù),當(dāng)然就可以應(yīng)用于控件。子類化其原理就在于它修改了窗口(控件)的類內(nèi)存塊,這樣既有窗口(控件)就不知不覺地修改了自己的模樣和行為。
得益于控件的變量映射機(jī)制,在VC里,子類化控件并不是一件很難的事情。具體操作就是:為控件準(zhǔn)備一個新類,例如CCoolEdit。一般這些類都從某個原有的MFC類(如:CEdit)繼承而來,這樣它就可以很方便地繼承基類的模樣和行為;接下來的工作,就是我們將控件映射成這個新的CCoolEdit,完全如同CEdit一樣。
就這么簡單,我們仔細(xì)走走看:
1. 準(zhǔn)備子類;
已經(jīng)有很好的CEdit類,既然我們要改造它,那就直接繼承它:
圖20 子類化CEdit
注意Class type選擇MFC Class,我們將這個新的子類命名為CCoolEdit。
2. 映射控件對象;
參照圖12,我們現(xiàn)在再打開“添加控件變量”窗口,打開變量類型列表,會發(fā)現(xiàn)我們的CCoolEdit已經(jīng)赫然在列了。不錯,我們就映射一個CCoolEdit變量m_CoolEdit。
圖21 添加CCoolEdit對象變量
現(xiàn)在我們操作的控件即為CCoolEdit類型,實際上,誰都清楚,除了名字,我們的CCoolEdit 與CEdit類沒有什么兩樣;這時候,運行程序,界面并不會發(fā)生任何變化。
既然我們需要FlashPlayer最終版本的那樣酷的文本輸入框,那我們繼續(xù)下一步。
3. 定制子類CCoolEdit;
多查點資料,我們就可以知道:在控件顯示之前,每一個控件都會向父對話框發(fā)送一個WM_CTLCOLOR消息要求獲取繪制所需要的顏色(更多內(nèi)容可以查閱《電腦愛好者合訂本》2003年(上)附錄分冊《VC++界面美化編程》篇)。只要響應(yīng)WM_CTLCOLOR消息,我們就可以修改控件的顏色。那好,我們響應(yīng)WM_CTLCOLOR消息試試看。首先在類視圖選中CCoolEdit,彈出右鍵菜單:
圖22 添加消息處理函數(shù)
選擇Add Windows Message Handler…,即彈出以下窗口:
圖23 響應(yīng)控件的WM_CTLCOLOR消息
復(fù)習(xí)2.5節(jié)的內(nèi)容,雙擊WM_CTLCOLOR,添加事件響應(yīng)函數(shù)CCoolEdit::CtlColor()。接下來,我們可以在CtlColor()里完成界面的定制:
HBRUSH CCoolEdit::CtlColor(CDC* pDC, UINT nCtlColor)
{ // TODO: Change any attributes of the DC here
//設(shè)置前景色
pDC->SetTextColor(RGB(0, 255, 0));
//設(shè)置字體背景色
pDC->SetBkColor(RGB(0, 0, 0));
//設(shè)置背景色
return (HBRUSH)::GetStockObject(BLACK_BRUSH);
// TODO: Return a non-NULL brush if the parent's handler should not be called
}
就這么多了,運行程序,以下是運行結(jié)果:
圖24 CCoolEdit的運行結(jié)果
呵呵,怎么樣?這個CCoolEdit你以后還可以使用到其它場合哦!什么都不用做,只要正常地將你的文本輸入框映射成CCoolEdit就行了。