添加事件(ATL 教程,第 5 部分)
在此步骤中,你将向 ATL 控件添加一个 ClickIn
和一个 ClickOut
事件。 如果用户在多边形内部单击,则会触发 ClickIn
事件;如果用户在外部单击,则会触发 ClickOut
。 用于添加事件的任务如下所述:
添加
ClickIn
和ClickOut
方法生成类型库
实现连接点接口
添加 ClickIn 和 ClickOut 方法
在步骤 2中创建 ATL 控件时,你已选中“连接点”复选框。 这在 Polygon.idl 文件中创建了 _IPolyCtlEvents
接口。 请注意,接口名称以下划线开头。 这是一种约定,指示该接口是一个内部接口。 因此,允许浏览 COM 对象的程序可以选择不向用户显示该接口。 另请注意,选择“连接点”在 Polygon.idl 文件中添加了以下行,以指示 _IPolyCtlEvents
是默认源接口:
[default, source] dispinterface _IPolyCtlEvents;
source 属性指示控件是通知的源,因此它会在容器上调用此接口。
现在将 ClickIn
和 ClickOut
方法添加到 _IPolyCtlEvents
接口。
添加 ClickIn 和 ClickOut 方法
在“解决方案资源管理器”中打开 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);
ClickIn
和 ClickOut
方法将已单击的点的 x 和 y 坐标用作参数。
生成类型库
现在请生成类型库,因为项目将使用它来获取所需的信息,以便为控件构造连接点接口和连接点容器接口。
生成类型库
重新生成项目。
- 或者 -
在“解决方案资源管理器”中右键单击 Polygon.idl 文件,然后在快捷菜单中单击“编译”。
这会创建 Polygon.tlb 文件,即类型库。 Polygon.tlb 文件在“解决方案资源管理器”中不可见,因为它是一个二进制文件,无法直接查看或编辑。
实现连接点接口
为控件实现连接点接口和连接点容器接口。 在 COM 中,事件是通过连接点机制实现的。 为了从 COM 对象接收事件,容器将与 COM 对象实现的连接点建立建议连接。 由于一个 COM 对象可能有多个连接点,因此 COM 对象还会实现一个连接点容器接口。 通过此接口,容器可以确定支持哪些连接点。
实现连接点的接口名为 IConnectionPoint
,实现连接点容器的接口名为 IConnectionPointContainer
。
为帮助实现 IConnectionPoint
,你将使用“实现连接点向导”。 此向导通过读取类型库并为每个可触发的事件实现一个函数来生成 IConnectionPoint
接口。
实现连接点
在“解决方案资源管理器”中打开 _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_ClickIn
和 Fire_ClickOut
这两个方法,这些方法采用两个坐标参数。 要从控件触发事件时,需要调用这些方法。
在选择“连接点”选项的情况下创建控件时,系统已经为你生成了 _IPolyCtlEvents_CP.h 文件。 它还将 CProxy_PolyEvents
和 IConnectionPointContainerImpl
添加到了控件的多重继承列表中,并通过向 COM 映射添加相应的条目公开了 IConnectionPointContainer
。
你已实现用于支持事件的代码。 现在,请添加一些代码以便在适当的时候触发事件。 请记住,当用户在控件中单击鼠标左键时,将触发 ClickIn
或 ClickOut
事件。 若要确定用户何时单击了按钮,请添加 WM_LBUTTONDOWN
消息的处理程序。
添加 WM_LBUTTONDOWN 消息的处理程序
在“类视图”中右键单击
CPolyCtl
类,然后在快捷菜单中单击“属性”。在“属性”窗口中单击“消息”图标,然后在左侧列表中单击
WM_LBUTTONDOWN
。从出现的下拉列表中,单击“<Add> OnLButtonDown”。
OnLButtonDown
处理程序声明将添加到 PolyCtl.h,处理程序实现将添加到 PolyCtl.cpp。
接下来,修改处理程序。
修改 OnLButtonDown 方法
在 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
。
接下来,添加一个属性页。