MFC所提供的組件已經可以完成很多功能了,但有時候我們還需要這些控件按我們自己的意圖去處理。比如EDIT控件,雖然我們可以設置EDIT控件為只能接受數字屬性,但如果我們還需要它可以接收數字意外的字符,比如需要控件只能接收"2004-02-20"這樣的格式的日期字符呢?我們需要自己在WM_CHAR消息裡面來處理輸入的字符。可是,當輸入字符後,Windows會向Edit控件發送WM_CHAR消息,應用程序會調用Windows默認的Edit控件窗口處理函數WndProc來處理該控件。這時我們需要通過子類化將該窗口對象與自己的Edit類連接起來,這樣,該類的的消息處理函數會替代原來的消息處理函數,窗口消息才能通過自己的類進行消息映射,並首先調用自己的類的消息處理函數,采用自己的Edit類來處理WM_CHAR消息。子類化可以通過宏DDX_Control宏進行靜態關聯,以可以通過函數SubclassWindow()或SubclassDlgItem()完成。
現在講一下該日期輸入框控件實現部分,程序運行如圖一:
圖一 程序運行界面
一、要想自己定義該控件的WM_CHAR消息處理函數,必須先先從CEdit類派生出自己的新類CMyEdit,這一步可以通過ClassWizard來完成。這個類主要完成對編輯框類的WM_CHAR和WN_KEYDOWN消息的處理,以達到對輸入格式的控制。編輯框初始時顯示" - - "的時間輸入格式,要求按"year-month-day"的格式輸入日期。所以初始化時設置控件格式,代碼如下:
void CMyEdit::Initial()
{
SetLimitText(10);
SetWindowText(" - - ");
}
二、然後是關鍵的消息處理函數,因為我們需要過濾字符類(包括數字和Backspace鍵)和控制類兩種擊鍵消息(主要包括對Delete的處理)。當用戶輸入或者刪除字符並更新窗口後,要保證"-"在字符串的第5和第8個位置,主要思路是在字符顯示前通過添加" "來修整編輯框中的字符串,使顯示時的字符串達到需要的要求。主要處理的函數如下:
字符消息處理:
void CMyEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call
int oldpos=LOWord(GetSel());
CString str;
GetWindowText(str);
if ( nChar>=''0'' && nChar<=''9'' )
{
if ( oldpos<4 || ( oldpos>4 && oldpos<7) || oldpos>7)
{
str.Delete(oldpos,1);
SetWindowText(str);
SetSel(FormatPos(oldpos,oldpos));
CEdit::OnChar(nChar, nRepCnt, nFlags);
if ( LOWORD(GetSel())==4 || LOWord(GetSel())==7)
{
oldpos=LOWord(GetSel());
SetSel(FormatPos(oldpos+1,oldpos+1));
}
}
else
if ( oldpos==4 || oldpos==7 )
{
oldpos+=1;
SetSel(FormatPos(oldpos,oldpos));
str.Delete(oldpos,1);
SetWindowText(str);
SetSel(FormatPos(oldpos,oldpos));
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
}
else
if ( nChar==VK_BACK )
{
if ( (oldpos>0 && oldpos<5) || ( oldpos>5 && oldpos<8) || oldpos>8)
{
str.Insert(oldpos,'' '');
SetWindowText(str);
SetSel(FormatPos(oldpos,oldpos));
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
else
if ( oldpos==5 || oldpos==8 )
{
SetSel(FormatPos(oldpos-1,oldpos-1));
}
}
}
擊鍵消息處理:
void CMyEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call
CString str;
int oldpos=LOWord(GetSel());
GetWindowText(str);
if ( nChar==VK_DELETE )
{
if ( oldpos<4 || ( oldpos>4 && oldpos<7) || oldpos>7)
{
CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
GetWindowText(str);
if ( oldpos<7 )
str.Insert(str.Find(''-'',oldpos),'' '');
SetWindowText(str);
SetSel(FormatPos(oldpos,oldpos));
}
else
if ( oldpos==4 || oldpos==7 )
return ;
}
else
CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}
三、在對話框類中添加變量 CMyEdit,m_MyEdit,在初始化函數中添加動態子類化函數 :
m_MyEdit.SubclassDlgItem(IDC_EDIT,this);
為了演示一些其他問題,我添加了兩個按鈕子類化和反子類化。相關代碼如下::
子類化:
void CAdEditDlg::OnBtnsub()
{
m_MyEdit.SubclassWindow(GetDlgItem(IDC_EDIT)->m_hWnd);
GetDlgItem(IDC_BTNUNSUB)->EnableWindow(true);
GetDlgItem(IDC_BTNSUB)->EnableWindow(false);
m_MyEdit.SetFocus();
}
反子類化:
void CAdEditDlg::OnBtnunsub()
{
m_MyEdit.UnsubclassWindow();
GetDlgItem(IDC_BTNUNSUB)->EnableWindow(false);
GetDlgItem(IDC_BTNSUB)->EnableWindow(true);
GetDlgItem(IDC_EDIT)->SetFocus();
}
附加說明:
1、子類化函數的參數說明:
BOOL SubclassDlgItem( UINT nID, CWnd* pParent);
將一個 Windows 控件與 CWnd 或 CWnd 派生類的對象連接,然後使它通過 CWnd 或 CWnd 派生類的消息映射轉發消息。其中nID為該控件的ID,pParent為控件的父窗口。
BOOL SubclassWindow( HWND hWnd );
作用同SubclassDlgItem,只是該函數通過創後的句柄來完成子類化操作。hWnd為需要子類化的窗口句柄 HWND
UnsubclassWindow();
反子類化,該函數使窗口與子類化所連接的類脫離,使用該控件窗口默認的消息處理函數WndProc來處理。函數返回取消子類化的窗口句柄。
2、如果使采用ClassWizard將編輯框與CMyEdit變量映射後,ClassWizard已經通過DDX_Control宏完成了子類化的過程,如果此時再在對話框的初始化函數中進行子類化的時候,將會發生錯誤。本文發表於http://bianceng.cn(編程入門網)
3、反子類化後,m_MyEdit對象已經與窗口分離,此時不能通過m_MyEdit來處理該窗口需要消息類完成的操作,比如SetFocus(),否則,也會發生錯誤。
注:部分地方參考了《MS VC++ 6.0 MFC類庫參考手冊》
結束語
本文簡單的講了一下如何動態的使控件子類化,從而使控件完成自己需要的功能。這是我前段時間做一套管理軟件時所碰到的問題,雖然比較簡單,但我想對於初級讀者來說,還使又一定幫助的。很希望和大家探討一些更深層次的問題。