EternalWindows
汎用データ転送 / フォーマットの選択

前節では、自作のデータオブジェクトをクリップボードに設定することで、 1つのデータを複数のフォーマットでペーストさせることを実現しました。 つまり、1つのデータをテキストとしてもビットマップとしてもペーストできるようにしたわけですが、 これら両方のフォーマットをサポートするアプリケーションは、 一体どちらのフォーマットでペーストを行えばよいのでしょうか。 たとえば、メモ帳はデータをテキストとしてペーストするしかありませんが、 Wordならばデータをテキストでもビットマップでもペーストしてもよいはずであり、 その選択を時としてユーザーに任せたい場合があるかもしれません。 このような場合、OleUIPasteSpecialを呼び出すことによって、フォーマットを選択するためのダイアログを表示できます。

UINT OleUIPasteSpecial(
  LPOLEUIPASTESPECIAL lpPS
);

lpPSは、OLEUIPASTESPECIAL構造体のアドレスを指定します。 この構造体にはアプリケーションがペーストしたいフォーマットを指定し、 関数が成功した場合は、ユーザーが選択したフォーマットのインデックスが返ります。 後は、このインデックスで表されるフォーマットでデータをペーストすればよいことになります。

OleUIPasteSpecialで表示されるダイアログを次に示します。 事前に、Wordで何らかのテキストをコピーしているものと仮定しています。

Wordで何らかのテキストをコピーした場合は、それをテキスト形式や拡張メタファイル形式でペーストすることができます。 実際にはこれら以外の形式でもペーストできるのですが、今回のアプリケーションはそこまで多くのフォーマットはサポートしていません。 リスト内に表示されている個々のエントリはOLEUIPASTEENTRY構造体で識別することができ、 lpstrFormatNameメンバがリスト項目の名前になり、lpstrResultTextが下のエリアのテキスト名になります。 リスト内に表示される項目は、アプリケーションがサポートしているフォーマットの数ではなく、 実際にペースト可能なフォーマットの数で決定されることに注意してください。 たとえば、メモ帳でテキストをコピーした場合は、上記のリストにテキスト形式だけが表示されます。

今回のプログラムは、データを任意のフォーマットでペーストします。

#include <windows.h>
#include <oledlg.h>

#define ID_PASTE 100

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

BOOL PasteSpecial(HWND hwnd, IDataObject *pDataObject);
void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId);
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)
{
	switch (uMsg) {

	case WM_CREATE: {
		HMENU hmenu;

		OleInitialize(NULL);
		
		hmenu = CreateMenu();
		InitializeMenuItem(hmenu, TEXT("クリップボードから貼り付け(&P)"), ID_PASTE);
		SetMenu(hwnd, hmenu);
		return 0;
	}
	
	case WM_COMMAND:
		if (LOWORD(wParam) == ID_PASTE) {
			IDataObject *pDataObject;

			OleGetClipboard(&pDataObject);
			if (pDataObject == NULL) {
				MessageBox(NULL, TEXT("クリップボードにデータが存在しません。"), NULL, MB_ICONWARNING);
				return 0;
			}

			PasteSpecial(hwnd, pDataObject);
			
			pDataObject->Release();
		}
		return 0;

	case WM_DESTROY:
		OleUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

BOOL PasteSpecial(HWND hwnd, IDataObject *pDataObject)
{
	int               i;
	HDC               hdc;
	STGMEDIUM         medium;
	OLEUIPASTESPECIAL ps;
	OLEUIPASTEENTRY   pasteEntries[3];
	FORMATETC         formatetc[] = {
		{CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL | TYMED_ISTREAM},
		{CF_BITMAP, NULL, DVASPECT_CONTENT, -1, TYMED_GDI},
		{CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF}
	};
	LPWSTR lpszFormatName[] = {L"テキスト形式", L"ビットマップ形式", L"拡張メタファイル形式"};
	LPWSTR lpszResultText[] = {L"テキスト形式", L"ビットマップ形式", L"拡張メタファイル形式"};

	ZeroMemory(&ps, sizeof(OLEUIPASTESPECIAL));
	ps.cbStruct        = sizeof(OLEUIPASTESPECIAL);
	ps.hWndOwner       = hwnd;
	ps.dwFlags         = PSF_SELECTPASTE;
	ps.lpSrcDataObj    = pDataObject;
	ps.arrPasteEntries = pasteEntries;
	ps.cPasteEntries   = 3;
	
	for (i = 0; i < 3; i++) {
		pasteEntries[i].fmtetc          = formatetc[i];
		pasteEntries[i].lpstrFormatName = lpszFormatName[i];
		pasteEntries[i].lpstrResultText = lpszResultText[i];
		pasteEntries[i].dwFlags         = OLEUIPASTE_PASTEONLY;
	}

	if (OleUIPasteSpecial(&ps) != OLEUI_OK)
		return FALSE;
	
	pDataObject->GetData(&formatetc[ps.nSelectedIndex], &medium);
	
	hdc = GetDC(hwnd);

	InvalidateRect(hwnd, NULL, TRUE);
	UpdateWindow(hwnd);

	if (ps.nSelectedIndex == 0) {
		char *p;

		p = (char *)GlobalLock(medium.hGlobal);
		TextOutA(hdc, 0, 0, p, lstrlenA(p));
		GlobalUnlock(medium.hGlobal);
	}
	else if (ps.nSelectedIndex == 1) {
		HDC     hdcMem;
		HBITMAP hbmpMemPrev;
		BITMAP  bm;

		hdcMem = CreateCompatibleDC(hdc);
		hbmpMemPrev = (HBITMAP)SelectObject(hdcMem, medium.hBitmap);
		
		GetObject(medium.hBitmap, sizeof(BITMAP), &bm);
		BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
		
		SelectObject(hdcMem, hbmpMemPrev);
		DeleteDC(hdcMem);
	}
	else if (ps.nSelectedIndex == 2) {
		const int     HIMETRIC_INCH = 2540;
		int           cx, cy;
		RECT          rc;
		ENHMETAHEADER header;
		
		GetEnhMetaFileHeader(medium.hEnhMetaFile, sizeof(ENHMETAHEADER), &header);
		cx = header.rclFrame.right * GetDeviceCaps(hdc, LOGPIXELSX) / HIMETRIC_INCH;
		cy = header.rclFrame.bottom * GetDeviceCaps(hdc, LOGPIXELSY) / HIMETRIC_INCH;

		SetRect(&rc, 0, 0, cx, cy);
		PlayEnhMetaFile(hdc, medium.hEnhMetaFile, &rc);		
	}
	else
		;
	
	ReleaseDC(hwnd, hdc);
	ReleaseStgMedium(&medium);

	return TRUE;
}

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

「クリップボードから貼り付け」というメニュー項目を選択すると、 PasteSpecialという自作関数が呼ばれることになります。 この関数は、OleUIPasteSpecialの呼び出してダイアログを表示し、 選択されたフォーマットに応じてペースト処理を行います。 次に、OleUIPasteSpecialの呼び出しに関わる部分を示します。

FORMATETC         formatetc[] = {
	{CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL | TYMED_ISTREAM},
	{CF_BITMAP, NULL, DVASPECT_CONTENT, -1, TYMED_GDI},
	{CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF}
};
LPWSTR lpszFormatName[] = {L"テキスト形式", L"ビットマップ形式", L"拡張メタファイル形式"};
LPWSTR lpszResultText[] = {L"テキスト形式", L"ビットマップ形式", L"拡張メタファイル形式"};

ZeroMemory(&ps, sizeof(OLEUIPASTESPECIAL));
ps.cbStruct        = sizeof(OLEUIPASTESPECIAL);
ps.hWndOwner       = hwnd;
ps.dwFlags         = PSF_SELECTPASTE;
ps.lpSrcDataObj    = pDataObject;
ps.arrPasteEntries = pasteEntries;
ps.cPasteEntries   = 3;

for (i = 0; i < 3; i++) {
	pasteEntries[i].fmtetc          = formatetc[i];
	pasteEntries[i].lpstrFormatName = lpszFormatName[i];
	pasteEntries[i].lpstrResultText = lpszResultText[i];
	pasteEntries[i].dwFlags         = OLEUIPASTE_PASTEONLY;
}

if (OleUIPasteSpecial(&ps) != OLEUI_OK)
	return FALSE;

pDataObject->GetData(&formatetc[ps.nSelectedIndex], &medium);

OLEUIPASTESPECIAL構造体のlpSrcDataObjには、データを維持しているIDataObjectを指定します。 通常は、OleGetClipboardで取得したIDataObjectを指定することになるでしょう。 arrPasteEntriesは、フォーマットの情報を格納しているOLEUIPASTEENTRY構造体の配列を指定します。 この構造体のfmtetcには、アプリケーションがサポートするFORMATETC構造体を指定することができ、 今回はそれぞれ、CF_TEXT、CF_BITMAP、CF_ENHMETAFILEで初期化されています。 ただし、常にこの3つでペーストが可能というわけではなく、 実際にIDataObjectがサポートするフォーマットだけがダイアログのリスト内に表示されます。 OleUIPasteSpecialの呼び出しに成功したら、OLEUIPASTESPECIAL構造体のnSelectedIndexに選択されたフォーマットのインデックスが格納されます。 このインデックスに関連するFORMATETC構造体をIDataObject::GetDataに指定すれば、 そのフォーマットのデータを取得できたことになります。

データを取得すれば、後はフォーマットに応じたペーストを行えばよいことになります。 nSelectedIndexが0である場合はデータがテキスト形式であることを意味するため、 TextOutを呼び出してテキストを描画するようにしています。 nSelectedIndexが1である場合はデータがビットマップ形式であることを意味するため、 メモリデバイスコンテキストにビットマップを割り当て、 そのメモリデバイスコンテキストを通常のデバイスコンテキストにコピーすればよいでしょう。 nSelectedIndexが1である場合はデータが拡張メタファイル形式であることを意味するため、 PlayEnhMetaFileで再生することになります。 メタファイルのサイズはGetEnhMetaFileHeaderで取得することができますが、 これはHIMETRIC単位になっているため、デバイス単位に変換するようにします。


戻る