EternalWindows
OLE描画 / ビューオブジェクト

OLEでは、オブジェクトからイメージ(画像)を取得するための標準的なインターフェースを定義しています。 このインターフェースはIViewObjectと呼ばれ、 クライアントが指定したデバイスコンテキストにイメージを描画するメソッドを提供します。 オブジェクトからIViewObjectを取得することができれば、 IViewObject::Drawを通じてオブジェクトのイメージを描画できるはずです。

HRESULT IViewObject::Draw(
  DWORD dwDrawAspect,
  LONG lindex,
  void *pvAspect,
  DVTARGETDEVICE *ptd,
  HDC hdcTargetDev,
  HDC hdcDraw,
  LPCRECTL lprcBounds,
  LPCRECTL lprcWBounds,
  BOOL (*pfnContinue)( ULONG_PTR dwContinue),
  ULONG_PTR dwContinue
);

dwDrawAspectは、オブジェクトをどのように描画するかを表す定数を指定します。 DVASPECT_CONTENTを指定すれば通常の形式となり、DVASPECT_THUMBNAILを指定すればサムネイル型式になります。 lindexは、dwDrawAspectの値によって意味が異なるようですが、基本的に-1を指定することが多いようです。 pvAspectは、描画を最適化するためのDVASPECTINFO構造体のアドレスを指定します。 不要な場合は、NULLで問題ありません。 ptdは、オブジェクトの描画先となるデバイス情報を格納したDVTARGETDEVICE構造体のアドレスを指定します。 NULLを指定した場合は、描画先がディスプレイであると解釈されます。 hdcTargetDevは、描画デバイスのデバイスコンテキストのハンドルを指定します。 ptdにNULLを指定した場合は、NULLで問題ありません。 hdcDrawは、描画先となるデバイスコンテキストのハンドルを指定します。 このデバイスコンテキストにオブジェクトのイメージが描画されることになります。 lprcBoundsは、描画先の位置を格納したRECTL構造体のアドレスを指定します。 このサイズよりオブジェクトのサイズが小さい場合は、オブジェクトが拡大されて描画されます。 lprcWBoundsは、hdcDrawにメタファイルのデバイスコンテキストを指定した場合に指定します。 pfnContinueは、オブジェクトの描画中に一定間隔で呼び出してほしいコールバック関数のアドレスを指定します。 この関数でFALSEを返すようにすると、オブジェクトを描画を中止することができます。 通知が不要な場合はNULLを指定します。 dwContinueは、pfnContinueのコールバック関数に渡したい値を指定します。 pfnContinueにNULLを指定した場合は、0で問題ありません。

IViewObject::Drawの引数は複雑ですが、幸いにもこれをラッピングしたOleDrawという関数が用意されています。 この関数を呼び出せば、少ない引数でオブジェクトのイメージを描画することができます。

HRESULT OleDraw(
  LPUNKNOWN pUnknown,
  DWORD dwAspect,
  HDC hdcDraw,
  LPCRECT lprcBounds
);

pUnknownは、IViewObjectを実装しているオブジェクトのアドレスを指定します。 dwAspectは、オブジェクトをどのように描画するかを表す定数を指定します。 hdcDrawは、描画先となるデバイスコンテキストのハンドルを指定します。 lprcBoundsは、描画先の位置を格納したRECTL構造体のアドレスを指定します。 ちなみに、OleDrawの引数をIViewObject::Drawに適応すると次のようになります。

pUnknown->QueryInterface(IID_PPV_ARGS(&pViewObject));
pViewObject->Draw(dwAspect, -1, NULL, NULL, NULL, hdcDraw, lprcBounds, NULL, NULL, 0);
pViewObject->Release();

OleDrawはIViewObjectを取得する処理もラッピングするため、 アプリケーションがIViewObjectを持っていない場合は、 OleDrawによる描画が簡単であるといえます。

今回のプログラムは、htmlファイルの中身をウインドウに描画します。 MSHTMLのインターフェースを使用する関係上、mshtml.hをインクルードしています。

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

BOOL GetObjectFromFile(LPWSTR lpszFilePath, IOleObject **ppOleObject);
void DPtoHIMETRIC(LPSIZEL lpSizel);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static IOleObject *pOleObject = NULL;

	switch (uMsg) {

	case WM_CREATE:
		CoInitializeEx(NULL, COINIT_MULTITHREADED);
		GetObjectFromFile(L"C:\\sample.html", &pOleObject);
		return 0;

	case WM_PAINT: {
		HDC         hdc;
		RECT        rc;
		PAINTSTRUCT ps;
		SIZEL       sizel;

		hdc = BeginPaint(hwnd, &ps);
		
		SetRect(&rc, 0, 0, 300, 300);
		sizel.cx = rc.right - rc.left;
		sizel.cy = rc.bottom - rc.top;
		DPtoHIMETRIC(&sizel);
		pOleObject->SetExtent(DVASPECT_CONTENT, &sizel);

		OleDraw(pOleObject, DVASPECT_CONTENT, hdc, &rc);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		if (pOleObject != NULL)
			pOleObject->Release();
		CoUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL GetObjectFromFile(LPWSTR lpszFilePath, IOleObject **ppOleObject)
{
	HRESULT        hr;
	BSTR           bstrState;
	IPersistFile   *pPersistFile;
	IHTMLDocument2 *pDocument2;
	
	hr = CoCreateInstance(CLSID_HTMLDocument, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDocument2));
	if (FAILED(hr))
		return FALSE;
	
	pDocument2->QueryInterface(IID_PPV_ARGS(&pPersistFile));
	hr = pPersistFile->Load(lpszFilePath, STGM_READ);
	if (FAILED(hr)) {
		pPersistFile->Release();
		pDocument2->Release();
		return FALSE;
	}
	
	for (;;) {
		Sleep(10);
		pDocument2->get_readyState(&bstrState);
		if (lstrcmpW(bstrState, L"complete") == 0) {
			SysFreeString(bstrState);
			break;
		}
		SysFreeString(bstrState);
	}

	hr = pDocument2->QueryInterface(IID_PPV_ARGS(ppOleObject));

	pPersistFile->Release();
	pDocument2->Release();

	return SUCCEEDED(hr);
}

void DPtoHIMETRIC(LPSIZEL lpSizel)
{
	HDC hdc;
	const int HIMETRIC_INCH = 2540;
	
	hdc = GetDC(NULL);
	lpSizel->cx = MulDiv(lpSizel->cx, HIMETRIC_INCH, GetDeviceCaps(hdc, LOGPIXELSX));
	lpSizel->cy = MulDiv(lpSizel->cy, HIMETRIC_INCH, GetDeviceCaps(hdc, LOGPIXELSY));
	ReleaseDC(NULL, hdc);
}

IViewObjectを使用するためには、当然ながらIViewObjectを実装したオブジェクトが必要になります。 CLSID_HTMLDocumentで識別できるオブジェクトはHTMLドキュメントになりますが、 このオブジェクトはIViewObjectを実装しているため、今回はこれを対象にします。 IPersistFile::LoadでHTMLファイルをロードさせたら、 ロードが完了するまでをループ文で待機するようにします。 これを成功させるためには、CoInitializeExにCOINIT_MULTITHREADEDを指定しておく必要があります。 ロードが完了したら、オブジェクトをIOleObjectとして返すようにしていますが、 これはIViewObjectでなくなくてもよいのでしょうか。 確かにIViewObjectでもよいのですが、重要なのはオブジェクトがIViewObjectを実装しているかどうかであり、 最終的にOleDrawで描画を行うつもりなのであれば、IViewObjectでオブジェクトを識別する必要はありません。

オブジェクトの描画は、WM_PAINTで行われています。

case WM_PAINT: {
	HDC         hdc;
	RECT        rc;
	PAINTSTRUCT ps;
	SIZEL       sizel;

	hdc = BeginPaint(hwnd, &ps);
	
	SetRect(&rc, 0, 0, 300, 300);
	sizel.cx = rc.right - rc.left;
	sizel.cy = rc.bottom - rc.top;
	DPtoHIMETRIC(&sizel);
	pOleObject->SetExtent(DVASPECT_CONTENT, &sizel);

	OleDraw(pOleObject, DVASPECT_CONTENT, hdc, &rc);

	EndPaint(hwnd, &ps);

	return 0;
}

OleDrawを呼び出すためには、第3引数のデバイスコンテキストのどの位置にオブジェクトを描画するかを決めなければなりません。 よって、事前にRECT構造体を初期化することになるのですが、 これに併せて描画元の範囲も決定しておく必要があります。 つまり、オブジェクトのどこまでの範囲を描画対象にするかを決定するのです。 これはIOleObject::SetExtentで可能であり、 第2引数に目的のサイズを格納したSIZEL構造体を指定します。 IOleObject::SetExtentはサイズをHIMETRIC単位で要求するため、 DPtoHIMETRICという自作関数で変換を行っています。


戻る