EternalWindows
汎用データ転送 / フォーマットの列挙

汎用データ転送で使用されるIDataObjectは、1つのデータを複数のフォーマットで表すことができますが、 この性質はクリップボードに大変応用しやすいものです。 クリップボードには様々なフォーマットのデータを格納できるわけですが、 それらをIDataObjectで識別することができれば、 1つのインターフェースで複数のデータを一括管理できることになります。 アプリケーションがOleGetClipboardを呼び出せば、 クリップボードのデータをIDataObjectとして取得することができます。

HRESULT OleGetClipboard(
  LPDATAOBJECT *ppDataObj
);

ppDataObjは、IDataObjectを受け取る変数のアドレスを指定します。

OLEにおけるクリップボード関数は通常のクリップボード関数をベースにしており、 通常のクリップボード関数より機能的に優れているようなことは全くありません。 OleGetClipboardは、内部でOpenClipboardやEnumClipboardFormatsなどを呼び出してIDataObjectを作成するだけであり、 データをIDataObjectという形で取得する必要がないアプリケーションは、 OleGetClipboardを呼び出す意味は全くないといえるでしょう。 IDataObject::GetDataを呼び出した場合は、GetClipboardDataが呼ばれることになります。

特定のオブジェクトからIDataObjectを取得した場合ならともかく、 クリップボードのオブジェクトからIDataObjectを取得した場合は、 そのオブジェクトがどのようなフォーマットをサポートしているかは予測できません。 このような場合はIDataObject::EnumFormatEtcで、 フォーマットを列挙するためのIEnumFORMATETCを取得することになります。

HRESULT IDataObject::EnumFormatEtc(
  DWORD dwDirection,
  IEnumFORMATETC **ppenumFormatEtc
);

dwDirectionは、DATADIR_GETまたはDATADIR_SETを指定します。 DATADIR_GETを指定した場合はIDataObject::GetDataで取得可能なフォーマットを列挙でき、 DATADIR_SETを指定した場合はIDataObject::SetDataで設定可能なフォーマットを列挙できます。 ppenumFormatEtcは、IEnumFORMATETCを受け取る変数のアドレスを指定します。

IEnumFORMATETCを取得したらNextを呼び出すことで、サポートしているフォーマットを列挙することができます。

HRESULT IEnumFORMATETC::Next(
  ULONG celt,
  FORMATETC *rgelt,
  ULONG *pceltFetched
);

celtは、取得したいフォーマットの数を指定します。 rgeltは、フォーマットを受け取るFORMATETC構造体のアドレスを指定します。 pceltFetchedは、返されたフォーマットの数を受け取る変数のアドレスを指定します。

今回のプログラムは、クリップボードに設定されている各データのフォーマットを列挙します。

#include <windows.h>
#include <commctrl.h>

#define ID_OPEN 100

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

void EnumFormats(HWND hwndListView, 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)
{
	static HWND hwndListView = NULL;
	
	switch (uMsg) {
		
	case WM_CREATE: {
		LVCOLUMN             column;
		INITCOMMONCONTROLSEX ic;
		HMENU                hmenu;

		OleInitialize(NULL);
		
		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_LISTVIEW_CLASSES;
		InitCommonControlsEx(&ic);
		
		hwndListView = CreateWindowEx(0, WC_LISTVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | LVS_REPORT, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 300;
		column.pszText = TEXT("フォーマット");
		ListView_InsertColumn(hwndListView, 0, &column);
		
		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 300;
		column.pszText = TEXT("フォーマットの名前");
		ListView_InsertColumn(hwndListView, 1, &column);
		
		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 300;
		column.pszText = TEXT("TYMED");
		ListView_InsertColumn(hwndListView, 2, &column);
		
		hmenu = CreateMenu();
		InitializeMenuItem(hmenu, TEXT("クリップボードを開く(&O)"), ID_OPEN);
		SetMenu(hwnd, hmenu);

		return 0;
	}

	case WM_COMMAND:
		if (LOWORD(wParam) == ID_OPEN) {
			IDataObject *pDataObject;

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

			pDataObject->Release();
		}
		return 0;
	
	case WM_SIZE:
		MoveWindow(hwndListView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

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

	default:
		break;

	}

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

void EnumFormats(HWND hwndListView, IDataObject *pDataObject)
{
	TCHAR          szFormat[256];
	TCHAR          szFormatName[256];
	TCHAR          szMedium[256];
	LVITEM         item;
	FORMATETC      formatetc;
	IEnumFORMATETC *pEnumFromatetc;

	pDataObject->EnumFormatEtc(DATADIR_GET, &pEnumFromatetc);

	while (pEnumFromatetc->Next(1, &formatetc, NULL) == S_OK) {
		szFormatName[0] = '\0';
		GetClipboardFormatName(formatetc.cfFormat, szFormatName, sizeof(szFormatName) / sizeof(TCHAR));

		if (formatetc.cfFormat == CF_TEXT)lstrcpy(szFormat, TEXT("CF_TEXT"));
		else if (formatetc.cfFormat == CF_BITMAP)lstrcpy(szFormat, TEXT("CF_BITMAP"));
		else if (formatetc.cfFormat == CF_METAFILEPICT)lstrcpy(szFormat, TEXT("CF_METAFILEPICT"));
		else if (formatetc.cfFormat == CF_SYLK)lstrcpy(szFormat, TEXT("CF_SYLK"));
		else if (formatetc.cfFormat == CF_DIF)lstrcpy(szFormat, TEXT("CF_DIF"));
		else if (formatetc.cfFormat == CF_TIFF)lstrcpy(szFormat, TEXT("CF_TIFF"));
		else if (formatetc.cfFormat == CF_OEMTEXT)lstrcpy(szFormat, TEXT("CF_OEMTEXT"));
		else if (formatetc.cfFormat == CF_DIB)lstrcpy(szFormat, TEXT("CF_DIB"));
		else if (formatetc.cfFormat == CF_PALETTE)lstrcpy(szFormat, TEXT("CF_PALETTE"));
		else if (formatetc.cfFormat == CF_PENDATA)lstrcpy(szFormat, TEXT("CF_PENDATA"));
		else if (formatetc.cfFormat == CF_RIFF)lstrcpy(szFormat, TEXT("CF_RIFF"));
		else if (formatetc.cfFormat == CF_WAVE)lstrcpy(szFormat, TEXT("CF_WAVE"));
		else if (formatetc.cfFormat == CF_UNICODETEXT)lstrcpy(szFormat, TEXT("CF_UNICODETEXT"));
		else if (formatetc.cfFormat == CF_ENHMETAFILE)lstrcpy(szFormat, TEXT("CF_ENHMETAFILE"));
		else if (formatetc.cfFormat == CF_HDROP)lstrcpy(szFormat, TEXT("CF_HDROP"));
		else if (formatetc.cfFormat == CF_LOCALE)lstrcpy(szFormat, TEXT("CF_LOCALE"));
		else if (formatetc.cfFormat == CF_DIBV5)lstrcpy(szFormat, TEXT("CF_DIBV5"));
		else wsprintf(szFormat, TEXT("%d"), formatetc.cfFormat);

		szMedium[0] = '\0';
		if (formatetc.tymed & TYMED_HGLOBAL)lstrcat(szMedium, TEXT("TYMED_HGLOBAL "));
		if (formatetc.tymed & TYMED_FILE)lstrcat(szMedium, TEXT("TYMED_FILE "));
		if (formatetc.tymed & TYMED_ISTREAM)lstrcat(szMedium, TEXT("TYMED_ISTREAM "));
		if (formatetc.tymed & TYMED_ISTORAGE)lstrcat(szMedium, TEXT("TYMED_ISTORAGE "));
		if (formatetc.tymed & TYMED_GDI)lstrcat(szMedium, TEXT("TYMED_GDI "));
		if (formatetc.tymed & TYMED_MFPICT)lstrcat(szMedium, TEXT("TYMED_MFPICT "));
		if (formatetc.tymed & TYMED_ENHMF)lstrcat(szMedium, TEXT("TYMED_ENHMF "));

		item.mask     = LVIF_TEXT;
		item.iItem    = 0;
		item.iSubItem = 0;
		item.pszText  = szFormat;
		ListView_InsertItem(hwndListView, &item);
		
		item.mask     = LVIF_TEXT;
		item.iItem    = 0;
		item.iSubItem = 1;
		item.pszText  = szFormatName;
		ListView_SetItem(hwndListView, &item);

		item.mask     = LVIF_TEXT;
		item.iItem    = 0;
		item.iSubItem = 2;
		item.pszText  = szMedium;
		ListView_SetItem(hwndListView, &item);
	}

	pEnumFromatetc->Release();
}

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);
}

メニューから「クリップボードを開く」という項目が選択された場合は、 WM_COMMANDが送られることになります。 ここでは、OleGetClipboardを呼び出してIDataObjectを取得し、 EnumFormatsという自作関数を通じてフォーマットを列挙します。 具体的には、IDataObjectからIEnumFORMATETCを取得し、 IEnumFORMATETC::Nextが失敗するまで呼び出し続けます。

クリップボードのフォーマットはCF_XXXといった形で定義されていますが、 中にはこうした定数で表せないフォーマットも存在しています。 これらはRegisterClipboardFormatで作成された独自のフォーマットであり、 この関数にどのような文字列が指定されたかは、GetClipboardFormatNameの結果から分かります。 たとえば、フォーマットの値が49317で、フォーマットの名前が"Rich Text Format"ならば、 "Rich Text Format"をRegisterClipboardFormatに指定することで、49317という値が返るということです。 RegisterClipboardFormatに指定される文字列は一部、ヘッダーファイルに定義されていることもあり、 たとえば"Rich Text Format"は、richedit.hにCF_RTFとして定義されています。 また、ファイルをコピーした際に見かけるShell IDList Arrayという文字列は、 shlobj.hにCFSTR_SHELLIDLISTとして定義されています。


戻る