Замечу, что это не единственный способ заставить приложения реагировать на события. Например, можно заставить работать механизм window.external. Тогда соответствующий скрипт будет выглядеть например, так:
function mousedownhandler() {
// функция обработки window.external.onmousedown;
}
Думаю идея понятна. Однако этот способ удобен если мы сами формируем страницу. Сегодня мы пойдем первым путем.
Пишем шаблоный класс
Итак, настало время применить все вышеизложенное на практике. Конечно, можно руками написать COM-объекты для каждой функции, однако представьте, что число обработчиков переваливает за десяток, а каждый раз нужно реализовывать по сути одно и тоже. Думаю, понятно, к чему я клоню :) Самое время вспомнить о шаблонах C++. Итак, напишем простенький шаблонный класс. Чтобы не зависеть от конкретной библиотеки реализуем пару IUnknown, IDispatch вручную. Реализация IUnknown вполне стандартна, а из IDispatch необходимо реализовать только функцию Invoke.
template
class CHtmlEventObject : public IDispatch {
typedef void (T::*EVENTFUNCTIONCALLBACK)(DISPID id, VARIANT* pVarResult);
public:
CHtmlEventObject() { m_cRef = 0; }
~CHtmlEventObject() {}
HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject) {
*ppvObject = NULL;
if (IsEqualGUID(riid, IID_IUnknown)) *ppvObject = reinterpret_cast(this);
if (IsEqualGUID(riid, IID_IDispatch)) *ppvObject = reinterpret_cast(this);
if (*ppvObject) {
((IUnknown*)*ppvObject)->AddRef();
return S_OK;
} else return E_NOINTERFACE;
}
DWORD __stdcall AddRef() {
return InterlockedIncrement(&m_cRef);
}
DWORD __stdcall Release() {
if (InterlockedDecrement(&m_cRef) == 0) {
delete this;
return 0;
}
return m_cRef;
}
STDMETHOD(GetTypeInfoCount)(unsigned int FAR* pctinfo) {
return E_NOTIMPL;
}
STDMETHOD(GetTypeInfo)(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo) {
return E_NOTIMPL;
}
STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames,
LCID lcid, DISPID FAR* rgDispId) {
return S_OK;
}
STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult,
EXCEPINFO * pExcepInfo, UINT * puArgErr) {
if (DISPID_VALUE == dispIdMember) (m_pT->*m_pFunc)(m_id, pVarResult);
else TRACE(_T("Invoke dispid = %d\n"), dispIdMember);
return S_OK;
}
public:
static LPDISPATCH CreateHandler(T* pT,
EVENTFUNCTIONCALLBACK pFunc, DISPID id) {
CHtmlEventObject* pFO = new CHtmlEventObject;
pFO->m_pT = pT;
pFO->m_pFunc = pFunc;
pFO->m_id = id;
return reinterpret_cast(pFO);
}
protected:
T* m_pT;
EVENTFUNCTIONCALLBACK m_pFunc;
DISPID m_id;
long m_cRef;
};
Как применять этот класс? Проще простого.
Шаг 1.Создаем свою функцию обработчик по прототипу onevent(dispid id, VARIANT* pVarResult). В принципе ее можно разместить где угодно. Я предпочитаю создавать ее в классе представления, наследнике CHtmlView. При этом все обработчики сосредоточены в одном месте и не нужно беспокоится о взаимодействии с классом документа.
Шаг 2.Регистрируем ее в качестве обработчика интересующего нас события. Для этого через вызов CHtmlEventObject::CreateObject создаем экземпляр нашего COM-объекта. Передаем в него адрес функции обработчика и собственный идентификатор события. После этого передаем ссылку на него интересующему нас элементу.
// Создаем объект-обработчик
LPDISPATCH dispFO = CHtmlEventObject::CreateHandler(this, OnKeyDown, 1);
VARIANT vIn;
V_VT(&vIn) = VT_DISPATCH;
V_DISPATCH(&vIn) = dispFO;
// устанавливаем обработчик document.onkeydown
hr = pHtmlDoc->put_onkeydown(vIn);
Здесь есть одна тонкость. Зарегистрировать обработчик можно только тогда, когда документ уже загружен, иначе GetHtmlDocument() вернет NULL. Для этого можно отслеживать событие OnDocumentComplete. Ну вот собственно и все.
Получаем информацию о событии
Рассмотрим еще раз прототип функции обработчика
OnEvent(DISPID id, VARIANT* pVarResult);
В качестве id передается значение которое мы указали при регистрации обработчика. Это может пригодиться если мы захотим реализовать обработку нескольких событий в одной функции. Для этого нужно зарегистрировать одну и ту же функцию с разными DISPID, тогда по приходу событий мы сможет их различать.
pVarResult нужно, если не требуется обработки по умолчанию. При этом достаточно в pVarResult вернуть VARIANT_FALSE.
Итак, когда вызывается наш обработчик никакой дополнительной информации о событии в функцию не передается. А как же тогда поподробнее узнать, что произошло? Для этого необходимо воспользоваться интерфейсом IHTMLEventObj, доступным через объект window текущего документа. Посредством этого интерфейса можно получить подробную информацию о произошедшем событии, например, элемент, послуживший источником событий, состояние клавиш, местоположение курсора мыши и состояние ее кнопок.
Читать дальше