摘要作为一个程序员,我们经常会在程序中用到Windows通用控件。比如按钮控件,进度条控件等等。但是有时我们需要给控件更多的特色,这就需要做控件的子类化(subclassing). 子类化一个Windows控件与子类化一个C++类不同,子类化一个控件要求你把一个窗口的一些或所有的消息映射都替换成自己的函数来响应,这样你就有效的阻止了控件去做系统默认的行为,而按自己的想法去做。子类化有两种类型:实例子类化(instance subclassing)和全局子类化(global subclassing)。实例子类化是子类化一个窗口中的单一实例,全局子类化是把整个窗口子类化为一个特殊的类型。这里我们仅讨论单一实例子类化。 记住 子类化过程很简单,首先创建一个类映射窗口的所有消息,然后把控件用作为这个类的实例。例如,下面的例子中我们做一个按钮的子类化。 新类为了子类化一个控件,我们需要创建一个新类,并映射所有我们感兴趣的消息。为了简便,我们一般都从控件标准类中派生自己的新类,这里与按钮控件对应的标准类为CButton。 下面假定我们要实现的效果是,当鼠标悬停在按钮上方时,按钮显示为黄色。首先我们使用ClassWizard创建一个
在MFC框架中从 这里我们要为按钮设计的功能是,鼠标悬停时变为黄色。 为了检查鼠标是否悬停于按钮上,我们设置一个成员变量m_bOverControl ,TRUE表示鼠标悬停,然后设置一个周期(使用定时器)跟踪鼠标是否已离开控件,这是因为,系统并没有 使用ClassWizard加入WM_MOUSEMOVE和WM_TIMER的消息映射,响应函数分别是
ClassWizard将在你的按钮类文件中加入下面的代码: BEGIN_MESSAGE_MAP(CMyButton, CButton)//{{AFX_MSG_MAP(CMyButton) ON_WM_MOUSEMOVE() ON_WM_TIMER()//}}AFX_MSG_MAP END_MESSAGE_MAP()/////////////////////////////////////////////////////////////////////////////// CMyButton message handlersvoid 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); } 消息映射的入口(即 假设我们已经声明了两个变量m_bOverControl和m_nTimerID,类型分别是BOOL和UINT, 并且在类的构造函数中把它们初始化,我们的消息处理应使用下面的代码: void CMyButton::OnMouseMove(UINT nFlags, CPoint point) {if (!m_bOverControl)// Cursor has just moved over control { TRACE0("Entering controln"); 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 controlif (!rect.PtInRect(p)) { TRACE0("Leaving controln");// if not then stop looking... m_bOverControl = FALSE; KillTimer(m_nTimerID);// ...and redraw the control Invalidate(); }// drop through to default handler CButton::OnTimer(nIDEvent); } 最后我们来画出我们需要的效果,我们不再进行消息映射,而是重载
使用ClassWizard重载 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 necessaryif (m_bOverControl) pDC->FillSolidRect(rect, RGB(255,255,0));// yellow// Draw the textif (!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); } } 接下来,我们剩下最后一步。为控件设置owner drawn风格。我们可以在对话框的资源编辑器中,右键单击按钮控件,选择“属性”,然后在Style中选中owner drawn风格。但是有一种更好的方法,使得使用新建类子类化的按钮自动的设置owner drawn风格。为了完成这个功能,我们重载最后一个函数: 这个函数将在子类化窗口时被调用,次序是在 一个重点要注意的地方是: 如果你是用对话框资源创建一个控件,那么你要子类化的控件将不会响应WM_CREATE消息,所以我们不能在 使用ClassWizard重载 void CMyButton::PreSubclassWindow() { CButton::PreSubclassWindow(); ModifyStyle(0, BS_OWNERDRAW);// make the button owner drawn } 祝贺 - 你的 子类化在创建时使用DDX子类化在这个例子中,我们使用对话框编辑器在对话框中加入了一个新的按钮:
然后,使用ClassWizard为你的按钮控件添加成员变量,变量类型选择我们刚刚建立的类 ClassWizard g会在对话框的 使用没有在ClassWizard中注册的类子类化窗口如果你在工程中加入了一个新的窗口类,并且希望使用这个新类类型子类化你的窗口,但是ClassWizard中并没有提供新类的选项,那么你需要重新生成class wizard文件。 先备份以下工程中的.clw文件,然后删除它。接下来在Visual Studio中按Ctrl+W。你将看到一个提示框,要求你加入ClassWizard中包含类的文件,确认选择的文件中包含了新类的文件(soarlove注:一般情况下,选择“add all”即可。 现在你的新类已经可以供选择。如果不想这样做,你还有一个通用的方法,就是在选择类型的时候使用通用的类(比如 子类化一个存在的窗口使用DDX固然简单,但是不能帮助我们实现一个已存在窗口的子类化。比如你想在combobox中子类化一个Edit控件,那么在你子类化Edit控件之前,你需要先创建combobox控件。 这种情况下,我们使用 比如,假设有一个对话框中包含了一个按钮ID 为了做到这些,我们需要有一个新类型的实例,最后的方法是在对话框或视的头文件中加入成员函数。 CMyButton m_btnMyButton; 然后在对话框的 m_btnMyButton.SubclassDlgItem(IDC_BUTTON1,this);
假设你已经有了一个窗口的指针,或者你工作在一个 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());
画按钮是非常简单的,不需要考虑按钮的风格(比如flat风格),也不需要考虑适应文字,仅仅需要考虑你画的范围。如果你编译运行提供的演示代码,那么你将看到,当鼠标悬停于按钮上方时,按钮变为黄色。
注意,实际上我们只重载了画的函数,并截取了鼠标移动的函数。其余的功能都还是使默认响应的。 结论子类化并不难 - 你只要认真的选择你要子类化的类并且知道你要映射那些消息。要熟悉你要子类化的类,了解提供的消息和类中的虚函数。 |
|