EternalWindows
MSHTML / イベントの受信

今回は、特定のエレメントに発生したイベントを検出する方法について説明します。 イベントというのはエレメント上でユーザーが行った操作を通知するものであり、 たとえば、「エレメントがクリックされた」、「エレメント上でマウスカーソルが移動した」というようなものがあります。 次に、エレメントのクリックを検出するコードを示します。

var.vt = VT_DISPATCH;
var.pdispVal = new CEventHandler(pWindow2);
pElement->put_onclick(var);

IHTMLElement::put_onclickにVARIANT構造体を指定すれば、 エレメントがクリックされた際にpdispValへ通知が送られます。 pdispValに指定しているCEventHandlerはIDispatchを継承しているクラスであり、 通知の際にはCEventHandler::Invokeが呼ばれることになります。 コンストラクタの第1引数に指定しているpWindow2の型はIHTMLWindow2であり、 これはIHTMLDocument2::get_parentWindowで取得することができます。 何故、IHTMLWindow2が必要になるかについては後で説明します。 なお、イベントを検出するための処理は、IHTMLElement2::attachEventでも行うことができます。

今回のプログラムは、ボタンがクリックされた場合にメッセージボックスを表示します。

#include <windows.h>
#include <exdisp.h>
#include <mshtml.h>
#include <oleacc.h>

#pragma comment (lib, "oleacc.lib")

class CEventHandler : public IDispatch
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP GetTypeInfoCount(UINT *pctinfo);
	STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
	STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
	STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);

	CEventHandler(IHTMLWindow2 *pWindow2);

public:
	LONG         m_cRef;
	IHTMLWindow2 *m_pWindow2;
};

BOOL PutEventHandler(IHTMLDocument3 *pDocument3);
BOOL GetDocumentFromIE(IHTMLDocument3 **pp);
BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	IHTMLDocument3 *pDocument3;

	CoInitialize(NULL);

	if (!GetDocumentFromIE(&pDocument3)) {
		CoUninitialize();
		return 0;
	}
	
	PutEventHandler(pDocument3);

	CoUninitialize();
	
	return 0;
}

BOOL PutEventHandler(IHTMLDocument3 *pDocument3)
{
	BSTR           bstrId;
	VARIANT        var;
	IHTMLElement   *pElement;
	IHTMLDocument2 *pDocument2;
	IHTMLWindow2   *pWindow2;
	
	bstrId = SysAllocString(L"sample");
	pDocument3->getElementById(bstrId, &pElement);
	if (pElement == NULL) {
		SysFreeString(bstrId);
		return FALSE;
	}

	pDocument3->QueryInterface(IID_PPV_ARGS(&pDocument2));
	pDocument2->get_parentWindow(&pWindow2);

	var.vt = VT_DISPATCH;
	var.pdispVal = new CEventHandler(pWindow2);
	pElement->put_onclick(var);
	
	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	SysFreeString(bstrId);
	var.pdispVal->Release();
	pWindow2->Release();
	pDocument2->Release();
	pElement->Release();

	return TRUE;
}

BOOL GetDocumentFromIE(IHTMLDocument3 **pp)
{
	HWND    hwnd;
	UINT    uMsg;
	LRESULT lResult;
	HRESULT hr;
	
	EnumChildWindows(FindWindow(TEXT("IEFrame"), NULL), EnumChildProc, (LPARAM)&hwnd);
	if (hwnd == NULL)
		return FALSE;

	uMsg = RegisterWindowMessage(TEXT("WM_HTML_GETOBJECT"));
	if (!SendMessageTimeout(hwnd, uMsg, 0, 0, SMTO_ABORTIFHUNG, 1000, (LPDWORD)&lResult))
		return FALSE;

	hr = ObjectFromLresult(lResult, IID_IHTMLDocument3, 0, (void **)pp);
	if (FAILED(hr))
		return FALSE;

	return TRUE;
}

BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
{
	TCHAR szClassName[256];

	GetClassName(hwnd, szClassName, sizeof(szClassName) / sizeof(TCHAR));
	if (lstrcmp(szClassName, TEXT("Internet Explorer_Server")) == 0) {
		*((HWND *)lParam) = hwnd;
		return FALSE;
	}
	else
		return TRUE;
}

CEventHandler::CEventHandler(IHTMLWindow2 *pWindow2)
{
	m_cRef = 1;
	m_pWindow2 = pWindow2;
}

STDMETHODIMP CEventHandler::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IDispatch))
		*ppvObject = static_cast<IDispatch *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CEventHandler::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CEventHandler::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0) {	
		delete this;
		return 0;
	}

	return m_cRef;
}

STDMETHODIMP CEventHandler::GetTypeInfoCount(UINT *pctinfo)
{
	*pctinfo = 0;
	
	return S_OK;
}

STDMETHODIMP CEventHandler::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
	return E_NOTIMPL;
}

STDMETHODIMP CEventHandler::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
	return E_NOTIMPL;
}

STDMETHODIMP CEventHandler::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
	BSTR          bstr;
	IHTMLEventObj *pEventObj;
	IHTMLElement  *pElement;
	
	m_pWindow2->get_event(&pEventObj);
	pEventObj->get_srcElement(&pElement);
	pElement->get_tagName(&bstr);
	
	MessageBoxW(NULL, bstr, L"ボタンが押されました。", MB_OK);
	
	SysFreeString(bstr);
	pElement->Release();
	pEventObj->Release();
	
	return S_OK;
}

プログラムを実行すると終了を示すメッセージボックスが表示されますが、 これに応答する前にボタンをクリックしてください。 そうすると、ボタンがクリックされた旨を示すメッセージボックスを確認できます。 CEventHandlerというクラスはIDispatchを継承していますが、 IDispatchの中で呼ばれるメソッドはInvokeだけです。

STDMETHODIMP CEventHandler::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
	BSTR          bstr;
	IHTMLEventObj *pEventObj;
	IHTMLElement  *pElement;
	
	m_pWindow2->get_event(&pEventObj);
	pEventObj->get_srcElement(&pElement);
	pElement->get_tagName(&bstr);
	
	MessageBoxW(NULL, bstr, L"ボタンが押されました。", MB_OK);
	
	SysFreeString(bstr);
	pElement->Release();
	pEventObj->Release();
	
	return S_OK;
}

今回の場合、Invokeが呼ばれたことはボタンがクリックされたことを意味しますが、 それではそのボタンのエレメントを取得するにはどうすればよいのでしょうか。 これにはまず、IHTMLWindow2::get_eventでIHTMLEventObjを取得し、 続いてIHTMLEventObj::get_srcElementを呼び出します。 これにより、イベントの発生したエレメントを表すIHTMLElementを取得できます。 取得したIHTMLElementが本当にボタンであるかを確認するために、 get_tagNameを呼び出してタグ名を取得しています。 ボタンは<input>で表され、表示される結果はこれと同一であるはずです。 なお、IHTMLEventObjにはイベントが発生した際のカーソルの位置を取得するメソッドなどが含まれています。

明示的にイベントを発生させる

イベントというのは基本的にユーザー入力によって発生するものですが、 アプリケーション内から明示的に発生させることもできます。 たとえば、エレメントのクリックは次のようになります。

pElement->click();

これにより、エレメントがクリックされたことになります。 今回の場合であればCEventHandler::Invokeが呼ばれることになり、 Invokeが制御を返すとclickも制御を返します。

大幅に処理が複雑になりますが、IHTMLElement3::fireEventでもイベントを発生させることができます。

void FireEvent(IHTMLDocument3 *pDocument3, IHTMLElement *pElement)
{
	BSTR           bstrEventName;
	VARIANT        var;
	VARIANT_BOOL   varBool;
	IHTMLDocument4 *pDocument4;
	IHTMLEventObj  *pEventObject;
	IHTMLElement3  *pElement3;
	
	pDocument3->QueryInterface(IID_PPV_ARGS(&pDocument4));
	pDocument4->createEventObject(NULL, &pEventObject);

	pElement->QueryInterface(IID_PPV_ARGS(&pElement3));
	bstrEventName = SysAllocString(L"onclick");
	var.vt = VT_DISPATCH;
	var.pdispVal = pEventObject;
	pElement3->fireEvent(bstrEventName, &var, &varBool);

	SysFreeString(bstrEventName);
	pElement3->Release();
	pDocument4->Release();
	// pEventObject->Release(); 呼び出すとエラーが発生する
}

IHTMLElement3::fireEventを呼び出すにはIHTMLEventObjが必要になるため、 IHTMLDocument4::createEventObjectでこれを作成します。 fireEventの第1引数は発生するイベントの名前であり、クリックの場合はonclickになります。 第2引数のVARIANT構造体には、IHTMLEventObjを指定しておくようにします。 メソッドが成功した場合は、第3引数にVARIANT_TRUEが返ります。



戻る