子类化一个Windows控件与子类化一个C++类不同,子类化一个控件要求你把一个窗口的一些或所有的消息映射都替换成自己的函数来响应,这样你就有效的阻止了控件去做系统默认的行为,而按自己的想法去做。子类化有两种类型: 实例子类化(instance subclassing)和全局子类化(global subclassing)。实例子类化是子类化一个窗口中的单一实例,全局子类化是把整个窗口子类化为一个特殊的类型。这里我们仅讨论单一实例子类化。记住CWnd派生类对象与窗口本身(一个HWND)的差别是很重要的。你的C++ CWnd-派生类对象包含了一个指向HWND的成员函数,并且包含了当处理消息时HWND消息泵的响应函数(比如WM_PAINT, WM_MOUSEMOVE)。但你用一个C++对象子类化一个窗口时,你就把HWND与C++对象关联起来,并且设置了处理消息时把自定义的回调函数提供给HWND消息使用。子类化过程很简单,首先创建一个类映射窗口的所有消息,然后把控件用作为这个类的实例。例如,下面的例子中我们做一个按钮的子类化。新类
为了子类化一个控件,我们需要创建一个新类,并映射所有我们感兴趣的消息。为了简便,我们一般都从控件标准类中派生自己的新类,这里与按钮控件对应的标准类为CButton。下面假定我们要实现的效果是,当鼠标悬停在按钮上方时,按钮显示为黄色。首先我们使用ClassWizard创建一个CButton的派生类,叫做CMyButton。在MFC框架中从CButton派生自己的类有许多好处,最大的好处是我们不用手工添加任何一行代码就可以创建了一个拥有全部默认功能的Windows控件。因为MFC实现了所有的默认的消息映射,因此我们可以挑选我们感兴趣的消息自己处理,而不用去管其他消息。这里我们要为按钮设计的功能是,鼠标悬停时变为黄色。
为了检查鼠标是否悬停于按钮上,我们设置一个成员变量m_bOverControl ,TRUE表示鼠标悬停,然后设置一个周期(使用定时器)跟踪鼠标是否已离开控件,这是因为,系统并没有OnMouseEnter和 OnMouseLeave函数供我们调用,因此我们必须使用OnMouseMove。如果,在一个时间点上,发现鼠标已离开按钮,我们关闭定时器并重画控件。使用ClassWizard加入WM_MOUSEMOVE和WM_TIMER的消息映射,响应函数分别是OnMouseMove和OnTimer。 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);
}消息映射的入口(即BEGIN_MESSAGE_MAP) 建立了窗口消息与响应函数的对应关系。ON_WM_MOUSEMOVE把WM_MOUSEMOVE消息与OnMouseMove函数建立响应的关系, ON_WM_TIMER m把WM_TIMER消息与OnTimer函数建立了响应的关系。这些宏定义在MFC的源文件中,我们不需要去看,只要按照约定来做就可以了。假设我们已经声明了两个变量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 control
    if (!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);
}
最后我们来画出我们需要的效果,我们不再进行消息映射,而是重载CWnd::DrawItem虚函数。只有当控件设置owner-drawn风格时这个函数才能被调用,并且这个函数没有默认的实现代码,虚函数的设计只为了在派生类中进行实现。使用ClassWizard重载DrawItem函数,并加入下面的代码 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);
    }
}
接下来,我们剩下最后一步。为控件 设置owner drawn风格。我们可以在对话框的资源编辑器中,右键单击按钮控件,选择“属性”,然后在Style中选中owner drawn风格。但是有一种更好的方法,使得使用新建类子类化的按钮自动的设置owner drawn风格。为了完成这个功能,我们重载最后一个函数: PreSubclassWindow。这个函数将在子类化窗口时被调用,次序是在CWnd::Create或DDX_Control之后,这就是说,无论是动态的创建窗口实例还是使用对话框模板创建,这个函数都将被调用。PreSubclassWindow在窗口子类化创建后和窗口被显示前被调用,换句话说,这是我们来做窗口初始化的一个最好时机。一个重点要注意的地方是:
如果你是用对话框资源创建一个控件,那么你要子类化的控件将不会响应WM_CREATE消息,所以我们不能在OnCreate函数中做初始化的工作,因为它并不是在所有的情况下都被调用。使用ClassWizard重载PreSubclassWindow函数并加入下面的代码 void CMyButton::PreSubclassWindow()
{
    CButton::PreSubclassWindow();    ModifyStyle(0, BS_OWNERDRAW); // make the button owner drawn
}祝贺 - 你的Cbutton派生类已经完成。 子类化
在创建时使用DDX子类化
在这个例子中,我们使用对话框编辑器在对话框中加入了一个新的按钮:然后,使用ClassWizard为你的按钮控件添加成员变量,变量类型选择我们刚刚建立的类CMyButtonClassWizard g会在对话框的DoDataExchange函数中创建一个DDX_Control调用。DDX_Control启动了子类化过程,使得按钮控件使用CMyButton类进行消息映射,而不是使用通常的CButton。使用没有在ClassWizard中注册的类子类化窗口
如果你在工程中加入了一个新的窗口类,并且希望使用这个新类类型子类化你的窗口,但是ClassWizard中并没有提供新类的选项,那么你需要重新生成class wizard文件。 先备份以下工程中的.clw文件,然后删除它。接下来在Visual Studio中按Ctrl+W。你将看到一个提示框,要求你加入ClassWizard中包含类的文件,确认选择的文件中包含了新类的文件(soarlove注:一般情况下,选择“add all”即可。现在你的新类已经可以供选择。如果不想这样做,你还有一个通用的方法,就是在选择类型的时候使用通用的类(比如CButton),然后在头文件中手工把通用类(CButton)改为你的新类(CMyButton)。子类化一个存在的窗口
使用DDX固然简单,但是不能帮助我们实现一个已存在窗口的子类化。比如你想在combobox中子类化一个Edit控件,那么在你子类化Edit控件之前,你需要先创建combobox控件。这种情况下,我们使用SubclassDlgItem或者SubclassWindow函数。这两个函数允许你动态的子类化一个窗口,换句话说,把一个新的窗口实例与已经存在的窗口建立关联。比如,假设有一个对话框中包含了一个按钮ID IDC_BUTTON1。这个按钮已经被创建,我们想用一个CMyButton的实例来与之关联,以使得按钮符合我们需要的行为。为了做到这些,我们需要有一个新类型的实例,最后的方法是在对话框或视的头文件中加入成员函数。
CMyButton m_btnMyButton;
然后在对话框的OnInitDialog (或任何适当的地方) 中调用: m_btnMyButton.SubclassDlgItem(IDC_BUTTON1, this);
假设你已经有了一个窗口的指针,或者你工作在一个CView或其他CWnd派生类中里面的控件被动态的创建,或者你不想使用SubclassDlgItem函数,那么你可以使用下面的方法: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风格),也不需要考虑适应文字,仅仅需要考虑你画的范围。如果你编译运行提供的演示代码,那么你将看到,当鼠标悬停于按钮上方时,按钮变为黄色。注意,实际上我们只重载了画的函数,并截取了鼠标移动的函数。其余的功能都还是使默认响应的。 结论
子类化并不难 - 你只要认真的选择你要子类化的类并且知道你要映射那些消息。要熟悉你要子类化的类,了解提供的消息和类中的虚函数。

VC控件-子类化控件技术的更多相关文章

  1. Delphi的子类化控件消息, 消息子类化

    所谓的子类化,网上有很多说明,我就说我个人的随意理解,可能有误,请列位看官斟酌理解. 所谓子类化,个人理解就是拦截某个控件的消息以及样式,来进行自己的特定处理以达到特殊的功能需求.这个子类化,可以有子 ...

  2. MFC_1.3 控件子类化 消息反射

    控件子类化 如果想要在默认的控件类中添加一些功能,就需要子类化一个控件类 在类内可以响应控件所有的消息,并且可以添加自己的函数和数据 通过类向导子类化控件的步骤 打开类向导,创建一个 MFC 类,不要 ...

  3. .Net2.0 --Winform结合WebBrowser控件和Socket老技术来实现另类Push~

    原文:.Net2.0 --Winform结合WebBrowser控件和Socket老技术来实现另类Push~ 目前的企业级开发比较流行的是Web2.0技术,但是由于Web技术基于请求--响应的交互模式 ...

  4. Qt 开发 MS VC 控件终极篇

    Qt 开发 MS VC 控件终极篇 1. 使用 MSVC2015 通过项目向导创建 Qt ActiveQt Server 解决方案 项目配置:以下文件需要修改 1. 项目属性页->项目属性-&g ...

  5. ASP.NET 3.5控件和组件开发技术之客户端回发/回调揭密

    本文摘录自<纵向切入ASP.NET 3.5控件和组件开发技术>. 对于服务端控件元素,比如ASP.NET的Button标准服务端控件在提交时可以自动把请求发送到服务端处理,这样的控件我们不 ...

  6. 用Synoptic Panel自定义基于图形的可视化控件--制作一张剧场售票统计报表

    数据可视化的一大特点就是能给报表使用者带来感官上的享受.不再是枯燥的数字,而变成一个一个亮丽的图形.之前业界大神公布过一个统计Car Accidents的报表,这个Power BI Report的特点 ...

  7. Android高手速成--第一部分 个性化控件(View)

    第一部分 个性化控件(View) 主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.Pro ...

  8. Android开源项目第一篇——个性化控件(View)篇

    本文为那些不错的Android开源项目第一篇——个性化控件(View)篇,主要介绍Android上那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Ga ...

  9. Android开发UI之开源项目第一篇——个性化控件(View)篇

    原文:http://blog.csdn.net/java886o/article/details/24355907 本文为那些不错的Android开源项目第一篇——个性化控件(View)篇,主要介绍A ...

随机推荐

  1. Day2-F-A Knight's Journey POJ-2488

    Background The knight is getting bored of seeing the same black and white squares again and again an ...

  2. jenkins构建python项目时,提示python不是内部或外部命令的解决办法

    1.回到 Jenkins 首页,点击 “构建执行状态”或“Build Executor Status” ,右则会列出本机信息. 完美解决!!!

  3. 为什么Fun函数能够执行

    #include<stdio.h> #include<windows.h> void Fun() { printf("Kali-Team\n"); } in ...

  4. centos 搭建 git 服务端和客户端

    centos 搭建git需要设置远程服务端和客户端.远程代码存放在服务端,多个客户端可以共享和维护服务端代码. 一.服务端主机 1.创建ssh,大部分默认已经安装,有ssh就跳过 yum instal ...

  5. ie brower 点击用默认浏览器打开链接

    <script> function GetCurrentJumpUrl(){ var eleLink = document.getElementById('adLink'); if(ele ...

  6. 白手起家Django项目发布下篇_Django项目nginx部署

    上一篇完成了python的安装,接下来安装python的依赖包和项目的依赖包 1.  python-devel 命令:yum -y install python-devel 安装Django1.8.2 ...

  7. redis 模糊查询与删除

    创建一条数据 set  name1  zhangsan 查询 get name1 在创建一条数据 set name2 lisi 查询 get name2 模糊查询 keys name* 查询结果  n ...

  8. Springboot---显示图片/字符串/map集合/list集合

    1.字符串/图片/map集合 @GetMapping("/hello") public String test(Model model){ String message=" ...

  9. 029、Java中的四则运算

    01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...

  10. 015.Delphi插件之QPlugins,FMX插件窗口

    内嵌FMX的插件窗口,效果还是很可以的.退出时,会报错,很诡异啊. 主窗口代码如下 unit Frm_Main; interface uses Winapi.Windows, Winapi.Messa ...