創(chuàng)造你自己的控件
介紹
作為一個程序員有許多普通的windows控件可以用在應(yīng)用程序的外觀上。許多的控件的從列表到按鈕再到進程條都是可以現(xiàn)成的用。盡管如此,在如此多的控件中我們還是會碰到那些標準的控件不夠用的時候。歡迎進入子分類控件的藝術(shù)。
子分類一個windows控件不像子分類一個C++類。子分類一個控件意味這你用你自己的消息處理函數(shù)取代了改控件的一些或者所有的消息處理函數(shù)。你可以有效的截獲改控件的消息并使它按照你的意愿行事,而非windows的默認方式。這可以讓改控件實現(xiàn)大多數(shù)而非全部的你想得到的行為,并且使它表現(xiàn)得很完美。有兩種類型的子分類,局部子分類和全局子分類。局部子分類就是子分類一個實體,全局子分類就是將一個特定類型的控件全部子分成你的類型。
記住一個從CWnd類派生的類對象和一個與它相聯(lián)的窗口(hwnd)的區(qū)別是很重要的。CWnd的派生類對象包含一個成員變量指向hwnd,而且包含那些通過hwnd作為參數(shù)的處理消息的函數(shù)(比如,WM_PAINT, WM_MOUSEMOVE)。當你子分類一個控件通過你的C++對象時,你就是將相應(yīng)的hwnd連接到你的C++對象上并把改控件將激發(fā)的消息回調(diào)函數(shù)改成你的。
子分類是很容易的。首先,你創(chuàng)建一個處理了你感興趣的所以消息的類,然后將該類來子分一個已經(jīng)存在的控件使它按照你的新類的行事。某中方面上改控件已經(jīng)變成了你所擁有的了。在這個例子中我們將子分一個按鈕控件并且使它做一些它從來都沒能夠做的事。
一個新類
子分一個控件我們需要創(chuàng)建一個新類改類應(yīng)該處理了所有我們感興趣的消息。由于我們很懶,最好是使我們處理的消息最少,而且最好的方式是從你將要子分的控件派生你的新類,我們這里選擇的是CButton。
我們設(shè)想的是使按鈕在鼠標每次經(jīng)過時顯示出高亮的黃色。奇怪的事已經(jīng)產(chǎn)生了。首先我們通過向?qū)?chuàng)建一個從CButton派生的新類CMyButton。
通過MFC框架派生CButton類有許多優(yōu)點,最大的就是我們不需要實際的為我們的類添加一行控件的代碼。假如我們愿意我們可以通過我們的新類在下一步子分一個按鈕控件,盡管有些煩瑣。這是因為MFC實現(xiàn)了所有缺省的消息處理函數(shù),所以我們可以簡單的選擇一個我們感興趣的消息忽略其他的。
However for this example we have loftier plans for our control - making it bright yellow.
無論怎樣在這個例子中我們將使我們的控件表面高亮黃色顯示。
為了檢查鼠標是否是經(jīng)過了控件我們將設(shè)置一個布爾變量m_bOverControl為TRUE當鼠標進入控件邊界時,并且通過定時器實時的檢查跟蹤鼠標什么時候離開了控件。不幸的是我們沒有平臺提供的OnMouseEnter 和 OnMouseLeave函數(shù)可用,我們將通過OnMouseMove。加入我么在某個時候發(fā)現(xiàn)鼠標不再在控件上,我們將關(guān)閉定時器并重畫該控件。
通過類向?qū)砑?span>WM_MOUSEMOVE和WM_TIMER消息處理函數(shù)OnMouseMove和OnTimer 。
類向?qū)砑尤缦麓a到你的新button類:
BEGIN_MESSAGE_MAP(CMyButton, CButton)
//{{AFX_MSG_MAP(CMyButton)
ON_WM_MOUSEMOVE()
ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CMyButton message handlers
void CMyButton::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CButton::OnMouseMove(nFlags, point);
}
void CMyButton::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
CButton::OnTimer(nIDEvent);
}
消息映像的入口將消息映射成函數(shù)。ON_WM_MOUSEMOVE映射成函數(shù)OnMouseMove,ON_WM_TIMER映射成OnTimer。這些宏在MFC源代碼中定義,但是它們不需要閱讀。這個練習僅僅知道它們這樣處理就可以了。
我們定義了兩個布爾變量m_bOverControl和m_nTimer,一個UNIT變量,在構(gòu)造函數(shù)里面初始化它們,我們的消息處理函數(shù)如下:
IDvoid CMyButton::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bOverControl) // Cursor has just moved over control
{
TRACE0("Entering control\n");
m_bOverControl = TRUE; // Set flag telling us the mouse is in
Invalidate(); // Force a redraw
SetTimer(m_nTimerID, 100, NULL); // Keep checking back every 1/10 sec
}
CButton::OnMouseMove(nFlags, point); // drop through to default handler
}
void CMyButton::OnTimer(UINT nIDEvent)
{
// Where is the mouse?
CPoint p(GetMessagePos());
ScreenToClient(&p);
// Get the bounds of the control (just the client area)
CRect rect;
GetClientRect(rect);
// Check the mouse is inside the control
if (!rect.PtInRect(p))
{
TRACE0("Leaving control\n");
// if not then stop looking...
m_bOverControl = FALSE;
KillTimer(m_nTimerID);
// ...and redraw the control
Invalidate();
}
// drop through to default handler
CButton::OnTimer(nIDEvent);
}
我們的新類最后將要做的就是繪制,這里我們不是要處理一個消息而是重載一CWnd的虛方法DrawItem。這個方法僅僅是在自繪控件時調(diào)用,而且沒有一個缺省的實現(xiàn)可以讓你調(diào)用。(可以通過ASSERT'試試)這個方法被設(shè)計成僅被重載以及被派生類使用。
通過向?qū)砑右粋€DrawItem
方法并且添加如下代碼:
Collapse
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
CRect rect = lpDrawItemStruct->rcItem;
UINT state = lpDrawItemStruct->itemState;
CString strText;
GetWindowText(strText);
// draw the control edges (DrawFrameControl is handy!)
if (state & ODS_SELECTED)
pDC->DrawFrameControl(rect, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
else
pDC->DrawFrameControl(rect, DFC_BUTTON, DFCS_BUTTONPUSH);
// Deflate the drawing rect by the size of the button's edges
rect.DeflateRect( CSize(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE)));
// Fill the interior color if necessary
if (m_bOverControl)
pDC->FillSolidRect(rect, RGB(255, 255, 0)); // yellow
// Draw the text
if (!strText.IsEmpty())
{
CSize Extent = pDC->GetTextExtent(strText);
CPoint pt( rect.CenterPoint().x - Extent.cx/2,
rect.CenterPoint().y - Extent.cy/2 );
if (state & ODS_SELECTED)
pt.Offset(1,1);
int nMode = pDC->SetBkMode(TRANSPARENT);
if (state & ODS_DISABLED)
pDC->DrawState(pt, Extent, strText, DSS_DISABLED, TRUE, 0, (HBRUSH)NULL);
else
pDC->TextOut(pt.x, pt.y, strText);
pDC->SetBkMode(nMode);
}
}
所有的都已經(jīng)做好了-進缺最后一步。DrawItem方法需要被繪制控件可以自繪。這可以通過在對話框編輯器里選中相應(yīng)的選項實現(xiàn)-但是更好的方式是通過類自己設(shè)置樣式使得該類成為真正替代CButton的“drop-in”。為了實現(xiàn)這點我們需要重寫最后一個方法:PreSubclassWindow
.
這個方法被SubclassWindow
調(diào)用,并依次被CWnd::Create
或者DDX_Control
調(diào)用,這意味著加入你動態(tài)或者通過對話框模板創(chuàng)建一個新類的對象,PreSubclassWindow仍然會被調(diào)用。PreSubclassWindow將在你子分的控件已經(jīng)產(chǎn)生但是還為顯示之前調(diào)用。換句話說這就是控件的一個完美的初始化時刻。
需要重點注意的一點是:假如你的控件是通過對話框編輯器創(chuàng)建,那么你子分的控件將不會有WM_CREATE消息,因此我們不能通過使用OnCreate
來初始化,因為它根本不會被調(diào)用。
通過向?qū)е剌dPreSubclassWindow并添加如下代碼:
void CMyButton::PreSubclassWindow()
{
CButton::PreSubclassWindow();
ModifyStyle(0, BS_OWNERDRAW); // make the button owner drawn
}
祝賀你-你現(xiàn)在已經(jīng)有一個CButton的派生類了!
子類
通過DDX在創(chuàng)建的時候子分一個窗口
在這個例子中我們要子分的控件是在對話框上放置的:
我們讓正常的對話框創(chuàng)建流程創(chuàng)建一個帶有控件的對話框,然后通過DDX和我們的新類子分改控件。為了做到這點,我們僅僅需要通過向?qū)砑右粋€控件變量使其成為對話框類的成員(在這里它的ID是IDC_BUTTON1
),類名為CMyButton。
向?qū)г谀愕膶υ捒虺蓡T方法DoDataExchange中產(chǎn)生了一個DDX_Control調(diào)用。DDX_Control會調(diào)用SubclassWindow使得該按鈕使用CMyButton代替常規(guī)的CButton的處理方法。此按鈕已經(jīng)被劫持而且會按照你的設(shè)想形式了。
子分類一個窗口但是不被向?qū)ёR別
加入你添加一個窗口類到你的工程并且希望通過這個類的一個對象子分一個窗口,但是向?qū)Р⒉辉试S你的新類作為一個類型選項,這時你也許需要重新構(gòu)建類向?qū)募恕?/span>
先備份工程中的.clw文件,然后刪除之,接著在Visual Studio上按CTRL+W。你將會看到一個提示你哪些文件將被包含到類掃描過程中。確信你的新類文件在其中。
現(xiàn)在你的新類將可以作為一個類選項了,如果不是,你仍然可以通過類向?qū)Мa(chǎn)生一個控件(比如說CButton),然后在頭文件中手動修改其類名(比如CMyButton)。
子分一個存在的窗口
使用DDX很簡單,但是它不能幫助我們子分一個已經(jīng)存在的控件。比如說,你想子分一個組合框中的編輯框控件。你需要在你子分編輯框控件之前已經(jīng)創(chuàng)建了組合框(因此它的子編輯框窗口也就創(chuàng)建了)。
在這種情況下你可以使用非常好用的SubclassDlgItem 或者 SubclassWindow方法。這兩個方法允許你動態(tài)的子分一個窗口-換句話說,可以連接一個你的新窗口對象到一個已經(jīng)存在的窗口。
例如,假設(shè)我們包含ID為IDC_BUTTON1
的按鈕的對話框。那個按鈕已經(jīng)被創(chuàng)建了,我們希望關(guān)聯(lián)一個類型為CMyButton的對象到那個按鈕上從而使該按鈕按照我們希望的方式響應(yīng)。
為了做這些我們需要有一個已經(jīng)存在的新類對象,一個對話框或者視圖的成員變量是最好的。
CMyButton m_btnMyButton;
接著在對話框中調(diào)用OnInitDialog
(或者任何恰當?shù)牡胤剑?/span>
m_btnMyButton.SubclassDlgItem(IDC_BUTTON1, this);
另一方面,假設(shè)你已經(jīng)有一個希望子分類的指向窗口的指針或者一個從CView
或其他CWnd
派生的類中動態(tài)創(chuàng)建的控件,而你不希望使用SubclassDlgItem,那么可以簡單的調(diào)用:
CWnd* pWnd = GetDlgItem(IDC_BUTTON1); // or use some other method to get
// a pointer to the window you wish
// to subclass
ASSERT( pWnd && pWnd->GetSafeHwnd() );
m_btnMyButton.SubclassWindow(pWnd->GetSafeHwnd());
繪制按鈕非常簡單,除了不能將按鈕的樣式設(shè)為flat或者兩端對齊的文本外其他任何你想要的樣式都可以。當你編譯運行改程序時你會看見一個簡單的按鈕當你的鼠標經(jīng)過它時會呈現(xiàn)出亮黃色。
注意到我們僅僅真正的重載了自繪的方法,以及鼠標移動消息的處理函數(shù),這意味這該控件仍然是下按式的按鈕。給對話框類添加一個單擊的處理函數(shù)你會發(fā)現(xiàn)它仍然可以被調(diào)用。
結(jié)尾
子分類并不難-你僅僅需要仔細選擇你需要子分的類,而且要意識到你需要處理的消息函數(shù)。仔細研究你需要子分的控件—學習相關(guān)消息的處理函數(shù)和該類實現(xiàn)的虛成員方法。一旦你深入一個控件并且掌握了它的內(nèi)部工作機制,你會發(fā)現(xiàn)一切都是那么容易!
posted on 2008-03-17 21:33 弱水一瓢 閱讀(287) 評論(0) 編輯 收藏 引用 所屬分類: MFC