zoukankan      html  css  js  c++  java
  • 【VS开发】动态添加的ActiveX控件如何响应事件

    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了。
  • 相关阅读:
    [LeetCode] 19. Remove Nth Node From End of List 移除链表倒数第N个节点
    [LeetCode] 27. Remove Element 移除元素
    [LeetCode] 240. Search a 2D Matrix II 搜索一个二维矩阵 II
    [LeetCode] 74. Search a 2D Matrix 搜索一个二维矩阵
    [LeetCode] 452. Minimum Number of Arrows to Burst Balloons 最少箭数爆气球
    [LeetCode] 312. Burst Balloons 爆气球
    [LeetCode] 257. Binary Tree Paths 二叉树路径
    [LeetCode] 24. Swap Nodes in Pairs 成对交换节点
    [LeetCode] 680. Valid Palindrome II 验证回文字符串 II
    [LeetCode] 234. Palindrome Linked List 回文链表
  • 原文地址:https://www.cnblogs.com/huty/p/8518421.html
Copyright © 2011-2022 走看看