前一阵子写一个控件,感觉接口有些地方使用返回值控制不大方便使用,所以打算给控件增加上几个事件,结果在网上搜了很久,也看不到合适的方法,网上说的一些东西,在我的VC环境中看的时候,根本就没有办法那么操作(包括VC6和VC2010)。于是找了一个示例程序,反反复复试验了好多次,终于自己的控件能够通过事件回调了,现在把我的试验方法记录下来。(网上说的更多是在建立控件工程时增加事件,如果对于一个建立工程时没有时间支持的控件,根本没有提到的)。
我的试验中提到的内容,不知道是否都是必要的,我也没有在这方面再去尝试。
(1)修改控件类的继承列表,如果需要有事件支持,需要继承以下的类:
IDispatchImpl IProvideClassInfo2Impl IConnectionPointContainerImpl |
继承这些类,有可能还需要在 BEGIN_COM_MAP 和END_COM_MAP加入相应的宏。
加入连接点支持,需要使用 BEGIN_CONNECTION_POINT_MAP、CONNECTION_POINT_ENTRY和END_CONNECTION_POINT_MAP加入连接点的宏
(2)IDL中声明事件支持,在Library一节中,CoClass上面,如下方式说明:
importlib("stdole32.tlb"); { properties: methods: [id(1), helpstring("method OnOK")] HRESULT OnOK(); [id(2), helpstring("method OnError")] HRESULT OnError(LONG err,BSTR msg); }; |
(3)实现代理类,在其中实现触发事件的代码,例如:
template <class T> class CProxy_IMyCtrlEvents : public IConnectionPointImpl<T, &DIID__IMyCtrlEvents, CComDynamicUnkArray> { //Warning this class may be recreated by the wizard. public: HRESULT __stdcall Fire_OnOK() { CComVariant varResult; T* pT = static_cast<T*>(this); int nConnectionIndex; 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) { VariantClear(&varResult); DISPPARAMS disp = { NULL, NULL, 0, 0 }; pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL); } } return varResult.scode; } HRESULT __stdcall Fire_OnError(LONG err,BSTR msg) { CComVariant varResult; T* pT = static_cast<T*>(this); int nConnectionIndex; 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) { VariantClear(&varResult); //DISPPARAMS disp = { NULL, NULL, 0, 0 }; DISPPARAMS dispparams; memset(&dispparams, 0, sizeof(dispparams)); dispparams.cArgs=2; // 设置参数个数 dispparams.rgvarg=new VARIANTARG[1]; // 初始化参数数组 dispparams.rgvarg[0].vt=VT_UI4; // 第一个参数的类型 dispparams.rgvarg[0].uintVal=(UINT)err; dispparams.rgvarg[1].vt=VT_BSTR; // 第一个参数的类型 dispparams.rgvarg[1].bstrVal=(BSTR)msg; pDispatch->Invoke(0x2, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, &varResult, NULL, NULL); } } return varResult.scode; } }; |
我们可以看到,触发事件的FireEvent实现中,特殊之处在于,要把参数进行赋值,再调用定义的事件,0x1、0x2就是定义的事件。
实现之后,还需要在控件类定义处加入代理类,如(1)中需继承的其他类。
(4)有了这些之后,就可以在需要的地方触发事件,例如,在出现某种错误或情况时触发onError事件
(5)请注意,不要在控件所起的线程中触发事件,如果需要的话,请在线程中需要触发事件的地方给控件窗口(仅限控件有窗口的情况)发送自定义的消息,由控件窗口接收到消息后触发事件。要使用线程消息,需要在控件类定义时,在BEGIN_MSG_MAP和END_MSG_MAP中定义消息处理函数。
(6)一个题外话,控件类还应该继承 IObjectSafetyImpl,并在COM_MAP中声明宏,这样在浏览器中使用时,就不会弹出“在此页上的ActiveX控件和本页上的其它部分交互可能不安全”这样的提示。当然,要更好的解决安全问题,还需要考虑对控件数字签名等等方式。