|
|
| ::Главная страница :: С++/Си :: Статьи |
Подключение к событиям объектной модели DHTML при использовании WebBrowser-control
РАССЫЛКА САЙТА RSDN.RU
Демонстрационное приложение - event.zip (181 Kb)
В последнее время путешествуя по форумам и группам новостей все чаще можно встретить вопросы, касающиеся ActiveX-элемента WebBrowser. К сожалению зачастую эти вопросы теряются в общем ворохе сообщений, а материалов касающихся использования функциональности Internet Exploler катастрофически мало. В то же время у этих технологий находиться все больше поклонников. Уж слишком заманчиво выглядит возможность написать собственный броузер, приложив минимум усилий.
Например, текущая версия продукта, в разработке которой я принимаю участие, использует эти технологии для отображения информации из документно-ориентированной базы данных. При этом, как показывает практика, интерфейс web-представлений в данном случае, гораздо дружественнее пользователю, чем использование множества диалоговых форм, а чтобы его сменить достаточно подправить пару шаблонов в ресурсах.
Предлагаемая вашему вниманию статья посвящена одному из аспектов написания подобных программ. В ней будет рассмотрено как можно подключиться с событиям объектной модели броузера, а значит позволить вашему приложению использовать те преимущества, которые дает DHTML. Я попытаюсь рассказать вам как непосредственно обрабатывать события, возбуждаемые объектной моделью броузера в процессе работы пользователя со страницей.
Как и большинство ActiveX элементов WebBrowser является источником событий подключаемых через стандартный механизм Connection Point. К числу таких событий относятся OnBeforeNavigate, OnDocumentComplete и т.п. Несомненно они важны для управления приложением в целом, однако их возможностей явно недостаточно, если мы захотим более тесно познакомиться с DOM DHTML, например, узнать о перемещении мыши над элементами страницы или о нажатии клавиши на клавиатуре или вообще быть в курсе всех событий, которые можно использовать в сценариях DHTML.
При использовании объектной модели DHTML из сценариев возможно создавать собственные обработчики событий простым присваиванием наблюдаемого элемента соответствующим свойствам, например:
<SCRIPT LANGUAGE="jscript">
function mousedownhandler()
{
// функция обработчик
}
function afterPageLoads()
{
someElement.onmousedown = mousedownhandler;
}
</SCRIPT>
Возникает вопрос. А можно ли получить нечто подобное из клиента на С++? Конечно, причем похожим образом. Достаточно создать свою функцию обработки и зарегистрировать ее.
Теперь поподробнее. Для этого нужно проделать не так уж и много. Необходимо реализовать простой Com-объект для каждой функции обработчика. Этот объект должен реализовывать всего два стандартных интерфейса IUnknown и IDispatch. Далее ссылка на этот объект присваивается соответствующему свойству элемента, событие которого мы хотим наблюдать. При возникновении события броузер просто вызовет IDispatch::Invoke нашего объекта со значением DISPID = DISPID_VALUE (=0).
Замечу, что это не единственный способ заставить приложения реагировать на события. Например, можно заставить работать механизм window.external. Тогда соответствующий скрипт будет выглядеть например, так:
function mousedownhandler()
{
// функция обработки
window.external.onmousedown;
}
Думаю идея понятна. Однако этот способ удобен если мы сами формируем страницу. Сегодня мы пойдем первым путем.
Итак, настало время применить все вышеизложенное на практике. Конечно, можно руками написать COM-объекты для каждой функции, однако представьте, что число обработчиков переваливает за десяток, а каждый раз нужно реализовывать по сути одно и тоже. Думаю, понятно, к чему я клоню :) Самое время вспомнить о шаблонах С++. Итак, напишем простенький шаблонный класс. Чтобы не зависеть от конкретной библиотеки реализуем пару IUnknown, IDispatch вручную. Реализация IUnknown вполне стандартна, а из IDispatch необходимо реализовать только функцию Invoke.
template <class T> 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<void**>(this);
if (IsEqualGUID(riid, IID_IDispatch))
*ppvObject = reinterpret_cast<void**>(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<T>* pFO = new CHtmlEventObject<T>;
pFO->m_pT = pT;
pFO->m_pFunc = pFunc;
pFO->m_id = id;
return reinterpret_cast<LPDISPATCH>(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<CEventView>::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 текущего документа. Посредством этого интерфейса можно получить подробную информацию о произошедшем событии, например, элемент, послуживший источником событий, состояние клавиш, местоположение курсора мыши и состояние ее кнопок.
Вот его краткое описание из MSDN:
Методы IHTMLEventObj
| get_altKey | Состояние клавиши Alt |
| get_button | Возвращает информацию о нажатых кнопках мыши |
| get_cancelBubble | Возвращает будет ли продолжена обработка события вверх по иерархии обработчиков |
| get_clientX | Возвращает горизонтальную позицию курсора мыши относительно клиентской области окна |
| get_clientY | Возвращает вертикальную позицию курсора мыши относительно клиентской области окна |
| get_ctrlKey | Состояние клавиши Ctrl |
| get_fromElement | Возвращает указатель на интерфейс IHTMLElement позволяющий получить доступ к элементу с которого "ушел" курсор мыши при событиях onmouseover или onmouseout. |
| get_keyCode | Возвращает код нажатой клавиши |
| get_offsetX | Возвращает горизонтальную позицию курсора относительно контейнера элемента |
| get_offsetY | Возвращает позицию курсора относительно контейнера элемента |
| get_qualifier | Возвращает идентификатор события |
| get_reason | Возвращает состояние передачи данных для объекта источника данных |
| get_returnValue | Возвращаемое значение события или диалога |
| get_screenX | Горизонтальная координата относительно координат экрана |
| get_screenY | Вертикальная координата относительно координат экрана |
| get_shiftKey | Состояние клавиши Shift |
| get_srcElement | Возвращает указатель на интерфейс IHTMLElement послуживший источником событий |
| get_srcFilter | Возвращает объект фильтр возбудивший событие onfilterchange |
| get_toElement | Возвращает указатель на интерфейс IHTMLElement позволяющий получить доступ к элементу с на который "пришел" курсор мыши при событиях onmouseover или onmouseout |
| get_type | Возвращает строковое название события |
| get_x | Возвращает горизонтальную позицию мыши относительно родительского объекта в иерархии, позиционированного с помощью атрибутов CSS |
| get_y | Возвращает вертикальную позицию мыши относительно родительского объекта в иерархии, позиционированного с помощью атрибутов CSS |
| put_cancelBubble | Задать будет ли продолжена обработка события вверх по иерархии обработчиков |
| put_keyCode | Задать код нажатой клавиши |
| put_returnValue | Задать возвращаемое событием значение |
Стоит заметить, что интерфейс IHTMLEventObj доступен только на время обработки конкретного события. При этом не все свойства в контексте определенного события имеют смысл. Например, значения возвращаемые функциями get_fromElement и get_toElement доступны только при обработке событий мыши onmouseover и onmouseout.
В следующем примере в обработчике обределяется нажатая клавиша и выводится соответствующее диалоговое окно. Если была нажата клавиша Enter, то дальнейшая обработка отменяется.
void CMyHtmlView::OnKeyDown( DISPID id, VARIANT* pVarResult)
{
HRESULT hr;
LPDISPATCH pDispatch = GetHtmlDocument();
if( pDispatch != NULL )
{
IHTMLDocument2* pHtmlDoc;
hr = pDispatch->;
QueryInterface( __uuidof( IHTMLDocument2 ), (void**)&pHtmlDoc );
IHTMLWindow2* pWindow;
IHTMLEventObj* pEvent;
hr = pHtmlDoc->get_parentWindow(&pWindow);
ASSERT( SUCCEEDED( hr ) );
hr = pWindow->get_event(&pEvent);
ASSERT( SUCCEEDED( hr ) );
// Определяем нажатую клавишу
long nKey;
hr = pEvent->get_keyCode( &nKey );
ASSERT( SUCCEEDED( hr ) );
// Если Enter не хотим обрабатывать дальше
if ( nKey == VK_RETURN)
{
V_VT(pVarResult) = VT_BOOL;
V_BOOL(pVarResult) = FALSE;
}
pDispatch->Release();
pWindow->Release();
pEvent->Release();
pHtmlDoc->Release();
CString sMes;
sMes.Format("CEventView::OnKeyDown(DISPID = %d)\nKeyCode: %d", id, nKey);
AfxMessageBox(sMes);
}
}
Чтобы собрать воедино все фрагменты приведу небольшой пример (event.zip). Запустите его и выберите команду меню Event\OnKeydown. Теперь понажимайте клавиши внутри страницы. И посмотрите, что из этого получится. В этом примере регистрируется только один обработчик, но я думаю дочитав эту статью вы без труда сможете реализовать любой другой.
В заключение хочется заметить, что в этой статье я затронул только один небольшой аспект использования элемента WebBrowser. Если Вас заинтересует данная тема, пишите, продолжим.
|
Тематические
ссылки
|
| Ваша ссылка | Ваша ссылка |
|
Обмен кнопками, ведение статистики, реклама. |
|||