添加事件(ATL 教程,第 5 部分)

在此步骤中,你将向 ATL 控件添加一个 ClickIn 和一个 ClickOut 事件。 如果用户在多边形内部单击,则会触发 ClickIn 事件;如果用户在外部单击,则会触发 ClickOut。 用于添加事件的任务如下所述:

  • 添加 ClickInClickOut 方法

  • 生成类型库

  • 实现连接点接口

添加 ClickIn 和 ClickOut 方法

在步骤 2中创建 ATL 控件时,你已选中“连接点”复选框。 这在 Polygon.idl 文件中创建了 _IPolyCtlEvents 接口。 请注意,接口名称以下划线开头。 这是一种约定,指示该接口是一个内部接口。 因此,允许浏览 COM 对象的程序可以选择不向用户显示该接口。 另请注意,选择“连接点”在 Polygon.idl 文件中添加了以下行,以指示 _IPolyCtlEvents 是默认源接口:

[default, source] dispinterface _IPolyCtlEvents;

source 属性指示控件是通知的源,因此它会在容器上调用此接口。

现在将 ClickInClickOut 方法添加到 _IPolyCtlEvents 接口。

添加 ClickIn 和 ClickOut 方法

  1. 在“解决方案资源管理器”中打开 Polygon.idl,在 PolygonLib 库的 dispInterface_IPolyCtlEvents 声明中的 methods: 下添加以下代码

    [id(1), helpstring("method ClickIn")] void ClickIn([in] LONG x,[in] LONG y);
    [id(2), helpstring("method ClickOut")] void ClickOut([in] LONG x,[in] LONG y);
    

ClickInClickOut 方法将已单击的点的 x 和 y 坐标用作参数。

生成类型库

现在请生成类型库,因为项目将使用它来获取所需的信息,以便为控件构造连接点接口和连接点容器接口。

生成类型库

  1. 重新生成项目。

    - 或者 -

  2. 在“解决方案资源管理器”中右键单击 Polygon.idl 文件,然后在快捷菜单中单击“编译”

这会创建 Polygon.tlb 文件,即类型库。 Polygon.tlb 文件在“解决方案资源管理器”中不可见,因为它是一个二进制文件,无法直接查看或编辑

实现连接点接口

为控件实现连接点接口和连接点容器接口。 在 COM 中,事件是通过连接点机制实现的。 为了从 COM 对象接收事件,容器将与 COM 对象实现的连接点建立建议连接。 由于一个 COM 对象可能有多个连接点,因此 COM 对象还会实现一个连接点容器接口。 通过此接口,容器可以确定支持哪些连接点。

实现连接点的接口名为 IConnectionPoint,实现连接点容器的接口名为 IConnectionPointContainer

为帮助实现 IConnectionPoint,你将使用“实现连接点向导”。 此向导通过读取类型库并为每个可触发的事件实现一个函数来生成 IConnectionPoint 接口。

实现连接点

  1. 在“解决方案资源管理器”中打开 _IPolyCtlEvents_CP.h,在 CProxy_IPolyCtlEvents 类中的 public: 语句下添加以下代码

    VOID Fire_ClickIn(LONG x, LONG y)
    {
        T* pT = static_cast<T*>(this);
        int nConnectionIndex;
        CComVariant* pvars = new CComVariant[2];
        int nConnections = m_vec.GetSize();
    
        for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
        {
            pT->Lock();
            CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
            pT->Unlock();
            IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
            if (pDispatch != NULL)
            {
                pvars[1].vt = VT_I4;
                pvars[1].lVal = x;
                pvars[0].vt = VT_I4;
                pvars[0].lVal = y;
                DISPPARAMS disp = { pvars, NULL, 2, 0 };
                pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL);
            }
        }
        delete[] pvars;
    
    }
    VOID Fire_ClickOut(LONG x, LONG y)
    {
        T* pT = static_cast<T*>(this);
        int nConnectionIndex;
        CComVariant* pvars = new CComVariant[2];
        int nConnections = m_vec.GetSize();
    
        for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
        {
            pT->Lock();
            CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
            pT->Unlock();
            IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
            if (pDispatch != NULL)
            {
                pvars[1].vt = VT_I4;
                pvars[1].lVal = x;
                pvars[0].vt = VT_I4;
                pvars[0].lVal = y;
                DISPPARAMS disp = { pvars, NULL, 2, 0 };
                pDispatch->Invoke(0x2, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL);
            }
        }
        delete[] pvars;
    
    }
    

你将看到,此文件包含一个派生自 IConnectionPointImpl 的名为 CProxy_IPolyCtlEvents 的类。 _IPolyCtlEvents_CP.h 现在定义了 Fire_ClickInFire_ClickOut 这两个方法,这些方法采用两个坐标参数。 要从控件触发事件时,需要调用这些方法。

在选择“连接点”选项的情况下创建控件时,系统已经为你生成了 _IPolyCtlEvents_CP.h 文件。 它还将 CProxy_PolyEventsIConnectionPointContainerImpl 添加到了控件的多重继承列表中,并通过向 COM 映射添加相应的条目公开了 IConnectionPointContainer

你已实现用于支持事件的代码。 现在,请添加一些代码以便在适当的时候触发事件。 请记住,当用户在控件中单击鼠标左键时,将触发 ClickInClickOut 事件。 若要确定用户何时单击了按钮,请添加 WM_LBUTTONDOWN 消息的处理程序。

添加 WM_LBUTTONDOWN 消息的处理程序

  1. 在“类视图”中右键单击 CPolyCtl 类,然后在快捷菜单中单击“属性”

  2. 在“属性”窗口中单击“消息”图标,然后在左侧列表中单击 WM_LBUTTONDOWN

  3. 从出现的下拉列表中,单击“<Add> OnLButtonDown”OnLButtonDown 处理程序声明将添加到 PolyCtl.h,处理程序实现将添加到 PolyCtl.cpp。

接下来,修改处理程序。

修改 OnLButtonDown 方法

  1. 在 PolyCtl.cpp 中更改包含 OnLButtonDown 方法的代码(删除向导添加的任何代码),使其如下所示:

    LRESULT CPolyCtl::OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, 
       BOOL& /*bHandled*/)
    {
       HRGN hRgn;
       WORD xPos = LOWORD(lParam);  // horizontal position of cursor
       WORD yPos = HIWORD(lParam);  // vertical position of cursor
    
       CalcPoints(m_rcPos);
    
       // Create a region from our list of points
       hRgn = CreatePolygonRgn(&m_arrPoint[0], m_nSides, WINDING);
    
       // If the clicked point is in our polygon then fire the ClickIn
       //  event otherwise we fire the ClickOut event
       if (PtInRegion(hRgn, xPos, yPos))
          Fire_ClickIn(xPos, yPos);
       else
          Fire_ClickOut(xPos, yPos);
    
       // Delete the region that we created
       DeleteObject(hRgn);
       return 0;
    }
    

此代码利用 OnDraw 函数中计算的点来创建一个区域,用于通过 PtInRegion 调用来检测用户的鼠标单击事件。

uMsg 参数是正在处理的 Windows 消息的 ID。 这样,你便可以通过一个函数来处理一系列消息。 wParam 和 lParam 参数是正在处理的消息的标准值。 使用参数 bHandled 可以指定函数是否处理了消息。 默认情况下,该值设置为 TRUE 以指示函数处理了消息,但你可以将其设置为 FALSE。 这会导致 ATL 继续查找另一个要将消息发送到的消息处理程序函数。

生成和测试控件

现在尝试触发事件。 生成控件并再次启动 ActiveX 控件测试容器。 这一次请查看事件日志窗口。 若要将事件路由到输出窗口,请在“选项”菜单中单击“日志记录”,然后选择“记录到输出窗口”。 插入控件并尝试在窗口中单击。 请注意,如果在已填充的多边形内部单击,则会触发 ClickIn,而在其外部单击则会触发 ClickOut

接下来,添加一个属性页。

返回步骤 4 | 继续执行步骤 6

另请参阅

教程