http://blog.csdn.net/xiaoqiqixiao/article/details/574542

今天在csdn上看到一朋友问如何响应动态添加的控件的事件,搜索资料,发现对于一般的应用来说,使用ON_EVENT_RANGE就足以满足需要了,不过,还是希望能够更灵活的实现动态的响应,所以又经过对MFC源码一番探查,发现还是可以的,而且应该也不是很复杂的,下面分别来说说这两种方法

例程为Tdax,对话框程序,使用了Microsoft UpDown Control 6.0控件作为控件,添加了MouseDown事件

第一种方法挺简单的:

1.

缺省的映射定义为

BEGIN_EVENTSINK_MAP(CTdaxDlg, CDialog)

    //{{AFX_EVENTSINK_MAP(CTdaxDlg)

    ON_EVENT(CTdaxDlg, IDC_UPDOWN1, -605 /* MouseDown */, OnMouseDownUpdown1, VTS_I2 VTS_I2 VTS_I4 VTS_I4)

    //}}AFX_EVENTSINK_MAP

END_EVENTSINK_MAP()

现改为

BEGIN_EVENTSINK_MAP(CTdaxDlg, CDialog)

    //{{AFX_EVENTSINK_MAP(CTdaxDlg)

    //}}AFX_EVENTSINK_MAP

    ON_EVENT_RANGE(CTdaxDlg, IDC_UPDOWN1, IDC_UPDOWN1+10, -605 /* MouseDown */, OnMouseDownUpdown1, VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4)

END_EVENTSINK_MAP()

就是说,可以映射至少11个控件(请注意,这里用了至少,也就是说可以少于11个,甚至于1个也无所谓)

a.这里建议把ON_EVENT_RANGE从

    //{{AFX_EVENTSINK_MAP(CTdaxDlg)

    ON_EVENT_RANGE(CTdaxDlg, IDC_UPDOWN1, IDC_UPDOWN1+10, -605 /* MouseDown */, OnMouseDownUpdown1, VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4)

    //}}AFX_EVENTSINK_MAP

对中拖出来,见上面例子,不拖出来不会对程序产生影响,但可能会影响后续的IDE操作,试试便知。

b.最后的参数类型中要在前面加上VTS_I4,即从VTS_I2 VTS_I2 VTS_I4 VTS_I4变为VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4,当然事件处理函数也将做相应调整,下面马上讲到。

2.

修改框架建立的事件处理函数

    void OnMouseDownUpdown1(short Button, short Shift, long x, long y);

改为

    BOOL OnMouseDownUpdown1(UINT nID, short Button, short Shift, long x, long y);

返回类型为void,应该也无所谓的,不过msdn上说是BOOL,咱就BOOL吧,但UINT nID这个参数是一定要加上的

3.做一些于本案无关的工作

a.在对话框编辑器中删掉原来的ID为IDC_UPDOWN1的控件

b.加一个成员变量CObList m_oaUpdowns;

c.在OnInitDialog中,加上

    UINT nID = IDC_UPDOWN1;

    CRect rect(10, 10, 20, 30);

    for(int i=0; i<11; i++){

        CUpDown* pupdown = new CUpDown;

        rect.OffsetRect(0, 20);

        pupdown->Create(NULL, WS_CHILD | WS_VISIBLE, rect, this, nID + i);

        m_oaUpdowns.Add(pupdown);

    }

d.在OnDestroy中,加上

    for(int i=0; i<m_oaUpdowns.GetSize(); i++){

        delete m_oaUpdowns[i];

    }

e.在OnMouseDownUpdown1中,加上

    CString str;

    str.Format("%d", nID);

    MessageBox(str);

用来指示是否收到事件

4.编译一下,可以发现,绝对OK

下面,我们来看更多灵活的方法:

观察MFC宏

BEGIN_EVENTSINK_MAP,END_EVENTSINK_MAP和ON_EVENT,可以发现MFC将控件ID,事件ID和事件处理函数等等信息都放在AFX_EVENTSINKMAP_ENTRY这个结构,每个映射一个这种结构,所有的结构组成一数组放在AFX_EVENTSINKMAP结构中,本来以为动态修改这两个结构信息,不就可以实现动态映射控件事件了,可惜发现MFC定义了

const AFX_EVENTSINKMAP_ENTRY theClass::_eventsinkEntries[] = 

{

...

}

可恶的const,只好另想它途了。

继续观察MFC源码,发现它中间会有一个

BOOL CCmdTarget::OnEvent(UINT idCtrl, AFX_EVENT* pEvent,

    AFX_CMDHANDLERINFO* pHandlerInfo)

{

...

}

函数中有如下代码

            VARIANT var;

            AfxVariantInit(&var);

            DISPPARAMS dispparams;

            dispparams.rgvarg = NULL;

            if (bRange)

            {

                memcpy(&dispparams, pEvent->m_pDispParams, sizeof(DISPPARAMS));

                dispparams.rgvarg = new VARIANT[++dispparams.cArgs];

                memcpy(dispparams.rgvarg, pEvent->m_pDispParams->rgvarg,

                    sizeof(VARIANT) * (dispparams.cArgs-1));

                VARIANT* pvarID = &dispparams.rgvarg[dispparams.cArgs-1];

                V_VT(pvarID) = VT_I4;

                V_I4(pvarID) = idCtrl;

            }

            hResult = CallMemberFunc(&pEntry->dispEntry, DISPATCH_METHOD, &var,

                (bRange ? &dispparams : pEvent->m_pDispParams), &uArgError);

            ASSERT(FAILED(hResult) || (V_VT(&var) == VT_BOOL));

            bHandled = V_BOOL(&var);

            if (bRange)

                delete [] dispparams.rgvarg;

            break;

好了,切入口找到了,就用这个CallMemberFunc了,唯一的问题就是重新组织这个函数所需的参数了。

开始:

1.为避免干扰,干脆注释掉OnInitDialog上一例中添加的代码,再加入如下代码:

    UINT nID = 10004;

    CRect rect(10, 10, 20, 30);

    for(int i=0; i<11; i++){

        CUpDown* pupdown = new CUpDown;

        rect.OffsetRect(0, 20);

        pupdown->Create(NULL, WS_CHILD | WS_VISIBLE, rect, this, nID + i);

        m_oaUpdowns.Add(pupdown);

    }

其实就改了一个nID,之所以改nID,是为了表示这个ID是可以你自己动态确定的,当然这里省事,将这些ID连在了一起,其实分开也是没问题的。

2.注释掉事件映射

BEGIN_EVENTSINK_MAP(CTdaxDlg, CDialog)

    //{{AFX_EVENTSINK_MAP(CTdaxDlg)

    //}}AFX_EVENTSINK_MAP

//    ON_EVENT_RANGE(CTdaxDlg, IDC_UPDOWN1, IDC_UPDOWN1+10, -605 /* MouseDown */, OnMouseDownUpdown1, VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4)

END_EVENTSINK_MAP()

3.重载对话框的OnCmdMsg,加入代码如下:

    if(nCode == CN_EVENT && nID >= 10004 && nID <= 10014){

        AFX_EVENT* pEvent = (AFX_EVENT*)pExtra;

        if(pEvent != NULL && pEvent->m_dispid == -605){//根据dispid来判断是否正确的事件

            AFX_DISPMAP_ENTRY e;

            e.lpszName = _T("");

            e.lDispID = -605;    //dispID,事件的dispID

            e.vt = VT_BOOL;        //返回类型

            e.pfn = (AFX_PMSG)OnMouseDownUpdown1;//事件处理函数

            e.pfnSet = (AFX_PMSG)0;

            e.nPropOffset = 0;

            e.flags = afxDispCustom;

            //下面是参数类型,如果事件处理函数不加控件ID的话,应该改为VTS_I2 VTS_I2 VTS_I4 VTS_I4

            e.lpszParams = VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4;

            UINT uArgError = (UINT)-1;    // no error yet

            VARIANT var;

            AfxVariantInit(&var);

            //如果需要每个控件的ID信息,应该为框架生成的事件处理函数的参数最前面加上1个UINT nID参数,代码如下:

            DISPPARAMS dispparams;

            dispparams.rgvarg = NULL;

            memcpy(&dispparams, pEvent->m_pDispParams, sizeof(DISPPARAMS));

            dispparams.rgvarg = new VARIANT[++dispparams.cArgs];

            memcpy(dispparams.rgvarg, pEvent->m_pDispParams->rgvarg,

                sizeof(VARIANT) * (dispparams.cArgs-1));

            VARIANT* pvarID = &dispparams.rgvarg[dispparams.cArgs-1];

            pvarID->vt = VT_I4;

            pvarID->intVal = nID;

            CallMemberFunc(&e, DISPATCH_METHOD, &var,

                    &dispparams, &uArgError);

            delete []dispparams.rgvarg;

            //如果不需要控件的ID信息,代码如下,不过函数就不用加上第1个参数(控件ID)了:

/*            CallMemberFunc(&e, DISPATCH_METHOD, &var,

                    (pEvent->m_pDispParams), &uArgError);*/

        }

        return TRUE;

    }

    return CDialog::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

4.编译运行,OK了。

例程为Tdax,对话框程序,使用了Microsoft UpDown Control 6.0控件作为控件,添加了MouseDown事件

第一种方法挺简单的:

1.

缺省的映射定义为

BEGIN_EVENTSINK_MAP(CTdaxDlg, CDialog)

    //{{AFX_EVENTSINK_MAP(CTdaxDlg)

    ON_EVENT(CTdaxDlg, IDC_UPDOWN1, -605 /* MouseDown */, OnMouseDownUpdown1, VTS_I2 VTS_I2 VTS_I4 VTS_I4)

    //}}AFX_EVENTSINK_MAP

END_EVENTSINK_MAP()

现改为

BEGIN_EVENTSINK_MAP(CTdaxDlg, CDialog)

    //{{AFX_EVENTSINK_MAP(CTdaxDlg)

    //}}AFX_EVENTSINK_MAP

    ON_EVENT_RANGE(CTdaxDlg, IDC_UPDOWN1, IDC_UPDOWN1+10, -605 /* MouseDown */, OnMouseDownUpdown1, VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4)

END_EVENTSINK_MAP()

就是说,可以映射至少11个控件(请注意,这里用了至少,也就是说可以少于11个,甚至于1个也无所谓)

a.这里建议把ON_EVENT_RANGE从

    //{{AFX_EVENTSINK_MAP(CTdaxDlg)

    ON_EVENT_RANGE(CTdaxDlg, IDC_UPDOWN1, IDC_UPDOWN1+10, -605 /* MouseDown */, OnMouseDownUpdown1, VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4)

    //}}AFX_EVENTSINK_MAP

对中拖出来,见上面例子,不拖出来不会对程序产生影响,但可能会影响后续的IDE操作,试试便知。

b.最后的参数类型中要在前面加上VTS_I4,即从VTS_I2 VTS_I2 VTS_I4 VTS_I4变为VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4,当然事件处理函数也将做相应调整,下面马上讲到。

2.

修改框架建立的事件处理函数

    void OnMouseDownUpdown1(short Button, short Shift, long x, long y);

改为

    BOOL OnMouseDownUpdown1(UINT nID, short Button, short Shift, long x, long y);

返回类型为void,应该也无所谓的,不过msdn上说是BOOL,咱就BOOL吧,但UINT nID这个参数是一定要加上的

3.做一些于本案无关的工作

a.在对话框编辑器中删掉原来的ID为IDC_UPDOWN1的控件

b.加一个成员变量CObList m_oaUpdowns;

c.在OnInitDialog中,加上

    UINT nID = IDC_UPDOWN1;

    CRect rect(10, 10, 20, 30);

    for(int i=0; i<11; i++){

        CUpDown* pupdown = new CUpDown;

        rect.OffsetRect(0, 20);

        pupdown->Create(NULL, WS_CHILD | WS_VISIBLE, rect, this, nID + i);

        m_oaUpdowns.Add(pupdown);

    }

d.在OnDestroy中,加上

    for(int i=0; i<m_oaUpdowns.GetSize(); i++){

        delete m_oaUpdowns[i];

    }

e.在OnMouseDownUpdown1中,加上

    CString str;

    str.Format("%d", nID);

    MessageBox(str);

用来指示是否收到事件

4.编译一下,可以发现,绝对OK

下面,我们来看更多灵活的方法:

观察MFC宏

BEGIN_EVENTSINK_MAP,END_EVENTSINK_MAP和ON_EVENT,可以发现MFC将控件ID,事件ID和事件处理函数等等信息都放在AFX_EVENTSINKMAP_ENTRY这个结构,每个映射一个这种结构,所有的结构组成一数组放在AFX_EVENTSINKMAP结构中,本来以为动态修改这两个结构信息,不就可以实现动态映射控件事件了,可惜发现MFC定义了

const AFX_EVENTSINKMAP_ENTRY theClass::_eventsinkEntries[] = 

{

...

}

可恶的const,只好另想它途了。

继续观察MFC源码,发现它中间会有一个

BOOL CCmdTarget::OnEvent(UINT idCtrl, AFX_EVENT* pEvent,

    AFX_CMDHANDLERINFO* pHandlerInfo)

{

...

}

函数中有如下代码

            VARIANT var;

            AfxVariantInit(&var);

            DISPPARAMS dispparams;

            dispparams.rgvarg = NULL;

            if (bRange)

            {

                memcpy(&dispparams, pEvent->m_pDispParams, sizeof(DISPPARAMS));

                dispparams.rgvarg = new VARIANT[++dispparams.cArgs];

                memcpy(dispparams.rgvarg, pEvent->m_pDispParams->rgvarg,

                    sizeof(VARIANT) * (dispparams.cArgs-1));

                VARIANT* pvarID = &dispparams.rgvarg[dispparams.cArgs-1];

                V_VT(pvarID) = VT_I4;

                V_I4(pvarID) = idCtrl;

            }

            hResult = CallMemberFunc(&pEntry->dispEntry, DISPATCH_METHOD, &var,

                (bRange ? &dispparams : pEvent->m_pDispParams), &uArgError);

            ASSERT(FAILED(hResult) || (V_VT(&var) == VT_BOOL));

            bHandled = V_BOOL(&var);

            if (bRange)

                delete [] dispparams.rgvarg;

            break;

好了,切入口找到了,就用这个CallMemberFunc了,唯一的问题就是重新组织这个函数所需的参数了。

开始:

1.为避免干扰,干脆注释掉OnInitDialog上一例中添加的代码,再加入如下代码:

    UINT nID = 10004;

    CRect rect(10, 10, 20, 30);

    for(int i=0; i<11; i++){

        CUpDown* pupdown = new CUpDown;

        rect.OffsetRect(0, 20);

        pupdown->Create(NULL, WS_CHILD | WS_VISIBLE, rect, this, nID + i);

        m_oaUpdowns.Add(pupdown);

    }

其实就改了一个nID,之所以改nID,是为了表示这个ID是可以你自己动态确定的,当然这里省事,将这些ID连在了一起,其实分开也是没问题的。

2.注释掉事件映射

BEGIN_EVENTSINK_MAP(CTdaxDlg, CDialog)

    //{{AFX_EVENTSINK_MAP(CTdaxDlg)

    //}}AFX_EVENTSINK_MAP

//    ON_EVENT_RANGE(CTdaxDlg, IDC_UPDOWN1, IDC_UPDOWN1+10, -605 /* MouseDown */, OnMouseDownUpdown1, VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4)

END_EVENTSINK_MAP()

3.重载对话框的OnCmdMsg,加入代码如下:

    if(nCode == CN_EVENT && nID >= 10004 && nID <= 10014){

        AFX_EVENT* pEvent = (AFX_EVENT*)pExtra;

        if(pEvent != NULL && pEvent->m_dispid == -605){//根据dispid来判断是否正确的事件

            AFX_DISPMAP_ENTRY e;

            e.lpszName = _T("");

            e.lDispID = -605;    //dispID,事件的dispID

            e.vt = VT_BOOL;        //返回类型

            e.pfn = (AFX_PMSG)OnMouseDownUpdown1;//事件处理函数

            e.pfnSet = (AFX_PMSG)0;

            e.nPropOffset = 0;

            e.flags = afxDispCustom;

            //下面是参数类型,如果事件处理函数不加控件ID的话,应该改为VTS_I2 VTS_I2 VTS_I4 VTS_I4

            e.lpszParams = VTS_I4 VTS_I2 VTS_I2 VTS_I4 VTS_I4;

            UINT uArgError = (UINT)-1;    // no error yet

            VARIANT var;

            AfxVariantInit(&var);

            //如果需要每个控件的ID信息,应该为框架生成的事件处理函数的参数最前面加上1个UINT nID参数,代码如下:

            DISPPARAMS dispparams;

            dispparams.rgvarg = NULL;

            memcpy(&dispparams, pEvent->m_pDispParams, sizeof(DISPPARAMS));

            dispparams.rgvarg = new VARIANT[++dispparams.cArgs];

            memcpy(dispparams.rgvarg, pEvent->m_pDispParams->rgvarg,

                sizeof(VARIANT) * (dispparams.cArgs-1));

            VARIANT* pvarID = &dispparams.rgvarg[dispparams.cArgs-1];

            pvarID->vt = VT_I4;

            pvarID->intVal = nID;

            CallMemberFunc(&e, DISPATCH_METHOD, &var,

                    &dispparams, &uArgError);

            delete []dispparams.rgvarg;

            //如果不需要控件的ID信息,代码如下,不过函数就不用加上第1个参数(控件ID)了:

/*            CallMemberFunc(&e, DISPATCH_METHOD, &var,

                    (pEvent->m_pDispParams), &uArgError);*/

        }

        return TRUE;

    }

    return CDialog::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

4.编译运行,OK了。

【VS开发】动态添加的ActiveX控件如何响应事件的更多相关文章

  1. {VS2010C#}{WinForm}{ActiveX}VS2010C#开发基于WinForm的ActiveX控件

    在VS2010中使用C#开发基于WinForm的ActiveX控件 常见的一些ActiveX大部分是使用VB.Delphi.C++开发,使用C#开发ActiveX要解决下面三个问题: 使.NET组件可 ...

  2. 让动态创建的ActiveX控件响应Windows消息

    当我们通过 CWnd::CreateControl() 动态创建 ActiveX   控件时, Windows 消息并不会被发送给我 们的由   CWnd 派生得控件类.例如,即使我们为 WM_KIL ...

  3. C#构架之基础学习----动态添加窗体和 控件

    仿照窗体应用程序编写: 任务一:生成一个Form类的窗体对象frm using System.Windows.Forms;         //using指令使用Form对象创建所需的命名空间 //如 ...

  4. 【VS开发】windows注册ActiveX控件

    ActiveX控件是一个动态链接库,是作为基于COM服务器进行操作的,并且可以嵌入在包容器宿主应用程序中,ActiveX控件的前身就是OLE控件.由于ActiveX控件与开发平台无关,因此,在一种编程 ...

  5. C#开发COM+组件和ActiveX控件

    using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices ...

  6. vs2012开发基于MFC的ActiveX控件

    1.新建工程 2.一直点击下一步,直到出现一下界面,注意红色标注选项,点击完成. 3.进入工程的属性界面,设置工程属性 4.添加对话框资源及其他控件,添加对话框类, 5.设置对话框属性 6.设置Dia ...

  7. js动态加载activeX控件在IE11与低版本IE中的差异

    由于IE11更加遵循W3C规范,所以IE11与低版本IE在加载activeX时有差别. 1.IE11中动态加载activeX的顺序 var objectTag = document.createEle ...

  8. ActiveX控件的Events事件

    http://labview360.com/article/info.asp?TID=10152&FID=165 Active X函式库 对使用LabVIEW作为开发环境的开发人员来说,如果能 ...

  9. Delphi不注册COM直接使用ActiveX控件并绑定事件

    文笔不行,直接上源码: 主窗口: unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System ...

随机推荐

  1. 再提供一种解决Nginx文件类型错误解析漏洞的方法

    [文章作者:张宴 本文版本:v1.2 最后修改:2010.05.24 转载请注明原文链接:http://blog.zyan.cc/nginx_0day/] 注:2010年5月23日14:00前阅读本文 ...

  2. windows2012 下面php7.2 安装mongodb4.0.4的扩展以及操作mongodb的方法

    php连接mongodb驱动 的下载页面http://pecl.php.net/package/mongodb 数据插入: $manager = new MongoDB\Driver\Manager( ...

  3. MySQL内联和外联查询

    内连: 内连接是通过在查询中设置连接条件的方式,来移除查询结果集中某些数据行后的交叉连接.简单来说,就是利用条件表达式来消除交叉连接的某些数据行. 在MySQL FROM 子句中使用关键字 INNER ...

  4. 31.整数中1出现的次数(从1到n整数中1出现的次数)

    题目描述 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1.10.11.12.13因此共出现6次,但是对于后面问题他就没辙了. ...

  5. 【LOJ3156】「NOI2019」回家路线

    [题目链接] [点击打开链接] [题目概括] 现在有\(n\)个站点,\(m\)条火车路线,每一条货车路线都有一个起点站点.终点站点.开始时间和到站时间. 对于一直在起点\(1\)的人,终点是\(n\ ...

  6. 智能指针之shared_ptr基本概述

    1.shared_ptr允许有多个指针指向同一个对象,unique_ptr独占所指向的对象. 2.类似于vector,智能指针也是模板.创建智能指针: shared_ptr<string> ...

  7. Linux-expect脚本-1

    expect是基于tcl演变而来的,所以很多语法和tcl类似,基本的语法如下所示: 首行加上/usr/bin/expect spawn: 后面加上需要执行的shell命令,比如说spawn sudo ...

  8. java中 在一个异常处理中什么语句块是可多个的

    MM们 异常处理一般格式:捕获异常:try{//代码块}catch(异常类型,例如:Exception e){//需要抛出的异常,例如:e.printStackTrace();}catch(异常类型) ...

  9. 【Java基础】谈谈集合.List

    摘自:https://www.cnblogs.com/54chensongxia/p/11722828.html 目录 1. ArrayList 1.1 ArrayList的构造 1.2 add方法 ...

  10. web服务基础

    Web服务基础 用户访问网站的基本流程 我们每天都会用web客户端上网,浏览器就是一个web客户端,例如谷歌浏览器,以及火狐浏览器等. 当我们输入www.oldboyedu.com/时候,很快就能看到 ...