EternalWindows
Silverlight / イベント処理

前節では、Silverlightをホストするための最低限の内容を取り上げたため、 今回はIXcpControlHostを使用して他にできることを取り上げます。 Silverlight内で新しいファイルが必要になった場合は、IXcpControlHost::DownloadUrlが呼ばれます。

STDMETHODIMP CXcpControlHost::DownloadUrl(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback, IStream** ppStream)
{
	return S_FALSE;
}

このメソッドでS_FALSEを返した場合は、Silverlightが実際に第1引数のファイルを探しにいきます。 通常、これは望ましい動作であると言えますが、 第3引数を通じてホスト側がファイルの中身を明示的に返すことも可能になっています。

STDMETHODIMP CXcpControlHost::DownloadUrl(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback, IStream** ppStream)
{
	if (lstrcmpW(PathFindFileName(bstrUrl), L"sample.xaml") == 0) {
		ULONG         uWritten;
		LARGE_INTEGER li;
		WCHAR         szXAML[] = 
			L"<Canvas "
				L"xmlns=\"http://schemas.microsoft.com/client/2007\" "
				L"xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">"
				L"<Rectangle Width=\"200\" Height=\"200\" Fill=\"Red\" MouseLeftButtonDown=\"RectangleClicked\"/>"
			L"</Canvas>";
		
		CreateStreamOnHGlobal(NULL, TRUE, ppStream);
		(*ppStream)->Write((char *)szXAML, lstrlenW(szXAML) * sizeof(WCHAR), &uWritten);
		
		li.HighPart = 0;
		li.LowPart = 0;
		(*ppStream)->Seek(li, STREAM_SEEK_SET, NULL);

		return S_OK;
	}

	return S_FALSE;
}

bstrUrlは、GetBaseUrlで返したディレクトリのフルパスとファイル名が連結された形になっているため、 フルパスからファイル名の部分を取得するPathFindFileNameを呼び出しています。 取得したファイル名が目的のファイルであれば、 CreateStreamOnHGlobalで1つのストリームを作成し、 それに対してXAMLファイルの中身を書き込みます。 これにより、実際にファイルシステム上にXAMLファイルを用意しておく必要がなくなります。 IStream::Writeの第2引数には第1引数のサイズを指定しますが、NULL文字は含まれないようにしておきます。 IStream::Seekは、シークポインタがストリームの先頭を指すために呼び出します。 戻り値としてS_OKを返すことにより、Silverlightは第3引数からファイルの中身を取得することになります。

上記のXAMLファイルでは、MouseLeftButtonDownのイベントハンドラとして、RectangleClickedを指定しています。 このような場合、ホストアプリケーションは長方形がクリックされたことを検出しなければなりませんが、 RectangleClickedに限らず、何らかのイベントが発生した場合はIXcpControlHost::InvokeHandlerが呼ばれます。 このメソッドの第1引数にはイベントハンドラの名前が格納され、 これがRectangleClickedであれば、長方形がクリックされたことであると分かります。 今回はクリックされた際にCtrlキーが押されている場合は長方形を青にし、 Ctrlキーが押されていない場合は長方形を緑にしますが、 このためにはオブジェクトについての詳しい知識が求められます。 まず、InvokeHandlerにはvarParam1とvarParam2という2つのVARIANT構造体が渡されます。 varParam1は発生したイベントを表すオブジェクトを識別し、 今回の場合はMouseButtonEventArgsになります。 一方、varParam2はイベント発生の対象になったオブジェクトを識別し、 今回の場合はRectangleになります。 オブジェクトのリファレンスは、MSDNライブラリ/Web 開発/Silverlight/一般的なリファレンス以下に存在し MouseButtonEventArgsオブジェクトの情報は次のURLから確認することができます。

http://msdn.microsoft.com/ja-jp/library/bb979694(VS.95).aspx

次に、上記URLの一部を示します。

上図はMouseLeftButtonDownのリファレンスなのですが、パラメーターとしてMouseButtonEventArgsオブジェクトが渡されることが分かります。 そして、このオブジェクトのCtrlプロパティを調べれば、Ctrlキーが押されているかを確認できることが分かります。 このような要領でオブジェクトのリファレンスを確認すれば、そのオブジェクトの使い方が分かるようになります。

それでは、InvokeHandlerの実装を確認します。

STDMETHODIMP CXcpControlHost::InvokeHandler(BSTR bstrName, VARIANT varParam1, VARIANT varParam2, VARIANT* pvarResult)
{
	IDispatch *pMouseButtonEventArgs = varParam1.pdispVal;
	IDispatch *pRectangle = varParam2.pdispVal;
	IDispatch *pSolidColorBrush;
	BOOL      bPressCtrl;
	VARIANT   var, varResult;

	if (lstrcmpW(bstrName, L"RectangleClicked") == 0) {
		VariantInit(&varResult);
		Invoke(pMouseButtonEventArgs, L"Ctrl", DISPATCH_PROPERTYGET, NULL, 0, &varResult);
		bPressCtrl = varResult.boolVal; // VARIANT_TRUE/VARIANT_FALSEの形で返らないことに注意

		VariantInit(&varResult);
		Invoke(pRectangle, L"Fill", DISPATCH_PROPERTYGET, NULL, 0, &varResult);
		pSolidColorBrush = varResult.pdispVal;

		var.vt = VT_I4;
		var.lVal = bPressCtrl ? 0xff0000ff : 0xff00ff00;
		Invoke(pSolidColorBrush, L"color", DISPATCH_PROPERTYPUT, &var, 1, &varResult);
		pSolidColorBrush->Release();
	}

	return S_OK;
}

bstrNameは、イベントハンドラの名前が格納されます。 今回の場合はRectangleClickedしか格納されないはずですが、一応確認するようにしています。 varParam1.pdispValにはMouseButtonEventArgsのオブジェクトが格納されているため、 これを分かりやすくpMouseButtonEventArgsという変数で識別するようにしています。 pRectangleについても同じ要領です。 pMouseButtonEventArgs->Invokeによって、特定のプロパティの値を取得できますが、 この呼び出しはInvokeという自作メソッドでラッピングされています。 このため、Invokeの第1引数に対象となるオブジェクトを指定し、第2引数に取得したいプロパティの名前を指定します。 Ctrlキーの押下の有無を取得したら、次はRectangleオブジェクトの情報を取得します。 RectangleオブジェクトはFillプロパティを持っており、 これを呼び出せば長方形のブラシに関するSolidColorBrushオブジェクトを取得できます。 そして、このSolidColorBrushオブジェクトのcolorプロパティに値を設定すれば、 実際にブラシの色が変更されます。 var.lValにはブラシの色を指定しますが、bPressCtrlがTRUEの場合は青色を指定し、 bPressCtrlがFALSEの場合は緑色を指定します。 色の最上位バイトはアルファ値であり、ffを指定しているため完全に不透明になります。

IXcpControlDownloadCallbackについて

IXcpControlHost::DownloadUrlには、IXcpControlDownloadCallbackというインターフェースが渡されますが、 これを使用するとデータのロードを任意のタイミングで行えます。 次に例を示します。

STDMETHODIMP CXcpControlHost::DownloadUrl(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback, IStream** ppStream)
{
	if (lstrcmpW(PathFindFileName(bstrUrl), L"sample.xaml") == 0) {
		CreateStreamOnHGlobal(NULL, TRUE, ppStream);

		pCallback->AddRef();
		(*ppStream)->AddRef();

		m_pCallback = pCallback;
		m_pStream = (*ppStream);

		return S_OK;
	}

	return S_FALSE;
}

pCallbackとppStreamの参照カウントをそれぞれ増加させ、 後で使用できるようにメンバ変数に保存しておきます。 SetTimerの呼び出しからも分かるように、実際にデータをロードするのは3秒後であり、 この時点では処理を行わないということでE_PENDINGを返します。 3秒後に送られるWM_TIMERの処理は、次のようになります。

case WM_TIMER:
	ULONG         uWritten;
	LARGE_INTEGER li;
	WCHAR         szXAML[] = 
		L"<Canvas "
			L"xmlns=\"http://schemas.microsoft.com/client/2007\" "
			L"xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">"
			L"<Rectangle Width=\"200\" Height=\"200\" Fill=\"Red\" MouseLeftButtonDown=\"RectangleClicked\"/>"
		L"</Canvas>";
		
	m_pStream->Write((char *)szXAML, lstrlenW(szXAML) * sizeof(WCHAR), &uWritten);
		
	li.HighPart = 0;
	li.LowPart = 0;
	m_pStream->Seek(li, STREAM_SEEK_SET, NULL);

	m_pCallback->OnUrlDownloaded(S_OK, m_pStream);

	m_pStream->Release();
	m_pCallback->Release();

	KillTimer(hwnd, 1);
	
	return 0;

データをストリームに書き込んだ後に、m_pCallback->OnUrlDownloadedを呼び出すのが重要です。 これにより、データのロードが完了したことを伝えることができます。 タイマはこの時点で不要になるため、KillTimerで破棄します。



戻る