EternalWindows
OLE D&D / ドロップターゲット

ユーザーの視点からすれば、アプリケーションからアプリケーションへのデータの交換には主に2つの方法があります。 1つは、データを一旦クリップボードにコピーして、それを後からペーストする方法であり、 これはOLEクリップボードを使用することで実現できます。 そしてもう1つは、アプリケーションのデータを目的のアプリケーションにD&D(ドラッグアンドドロップ)する方法であり、 これは今回取り上げるOLE D&Dで実現できます。 OLEクリップボードではデータをIDataObjectで表していましたが、OLE D&Dでもこの点は共通しているため、 IDataObjectの知識があればOLE D&Dは理解しやすくなるでしょう。 次に、OLE D&Dを使用する際のイメージを示します。

Wordの挿入タブから図形を選択して、何らかの図形を描画したとします。 そして、その図形をドラッグして今回のアプリケーションのウインドウにドロップすれば、 アプリケーションには図形を表すIDataObjectが渡されることになります。 後はIDataObject::GetDataを呼び出せば実際にデータを取得できますから、 図形をウインドウ上に描画することもできるでしょう。 非OLEのD&D関数であるDragAcceptFilesでは、ファイルの名前しか受け取ることができませんでしたが、 OLE D&DであればどのようなデータでもD&Dの対象にすることができます。

OLE D&Dでは、データを受け取る側をドロップターゲットと呼び、データを渡す側をドロップソースと呼びます。 アプリケーションにドロップターゲットとして機能を持たせたい場合は、 IDropTargetを実装したCOMオブジェクトを作成し、 それをRegisterDragDropに指定することになります。

HRESULT RegisterDragDrop(
  HWND hwnd,
  LPDROPTARGET pDropTarget
);

hwndは、D&Dを受け取るウインドウハンドルを指定します。 pDropTargetは、IDropTargetを実装したオブジェクトのアドレスを指定します。

RegisterDragDropを呼び出した場合は、具体的に何が行われることになるのでしょうか。 確かにいえることは、第1引数のウインドウがOLE D&Dをサポートすることを明示しなければならないということですが、 これにはウインドウプロパティと呼ばれる仕組みが使用されています。 ウインドウプロパティは、ウインドウハンドルと特定のデータを関連付けることができ、 RegisterDragDropはこのデータとして引数のpDropTargetを指定しています。 そして、このデータを識別する文字列として"OleDropTargetInterface"を指定しています。 これにより、OLEはD&Dの際にGetProp(hwnd, "OleDropTargetInterface")を呼び出すことで、IDropTargetを取得することができます。 第1引数のウインドウハンドルについては、カーソルの位置を基にWindowFromPointを呼び出せば取得することができるでしょう。 IDropTargetを取得したOLEは、カーソルがウインドウの中に入った直後にIDropTarget::DragEnterを呼び出し、 カーソルがウインドウの中で移動した場合はIDropTarget::DragOverを呼び出します。 また、カーソルがウインドウの外にした場合はIDropTarget::DragLeaveが呼ばれ、 データが実際にドロップされた場合はIDropTarget::Dropが呼ばれます。

IDropTargetのいくつかのメソッドでは、エフェクトと呼ばれる値を返さなければなりません。 エフェクトというのはドラッグ中におけるカーソルの形であり、 もしアプリケーションがドロップされようとしているデータをサポートしない場合は、 DROPEFFECT_NONEを返さなければなりません。 これにより、カーソルの形はドロップ不可能を示すようになりますから、 ユーザーはこのデータをドロップできないことが分かります。 一般にドロップといえばデータの移動を意味しますが、 Ctrlキーを押した場合はコピーの意味となり、Crtl+Shiftの場合はリンクの意味になることから、 こうしたキーの状態に応じて返すべきエフェクトも変化すべきといえます。

今回のプログラムは、Wordで作成された図形をD&Dで受け取って表示します。

#include <windows.h>

class CDropTarget : public IDropTarget
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
	STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
	STDMETHODIMP DragLeave();
	STDMETHODIMP Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);

	CDropTarget(HWND hwnd);
	~CDropTarget();

private:
	LONG m_cRef;
	HWND m_hwnd;
	BOOL m_bSupportFormat;
};

struct DROPDATA {
	HENHMETAFILE hEnhMetaFile;
	POINT        pt;
};
typedef struct DROPDATA DROPDATA;
typedef struct DROPDATA *LPDROPDATA;

DROPDATA  g_dropData[5] = {0};
int       g_nDropDataCount = 0;
const int g_nDropDataMaxCount = sizeof(g_dropData) / sizeof(DROPDATA);

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 IDropTarget *pDropTarget = NULL;

	switch (uMsg) {

	case WM_CREATE:
		OleInitialize(NULL);
		pDropTarget = new CDropTarget(hwnd);
		RegisterDragDrop(hwnd, pDropTarget);
		return 0;

	case WM_PAINT: {
		HDC           hdc;
		RECT          rc;
		PAINTSTRUCT   ps;
		const int     HIMETRIC_INCH = 2540;
		int           i;
		int           x, y;
		int           cx, cy;
		ENHMETAHEADER header;

		hdc = BeginPaint(hwnd, &ps);

		for (i = 0; i < g_nDropDataCount; i++) {
			GetEnhMetaFileHeader(g_dropData[i].hEnhMetaFile, sizeof(ENHMETAHEADER), &header);
			cx = header.rclFrame.right * GetDeviceCaps(hdc, LOGPIXELSX) / HIMETRIC_INCH;
			cy = header.rclFrame.bottom * GetDeviceCaps(hdc, LOGPIXELSY) / HIMETRIC_INCH;
			x = g_dropData[i].pt.x;
			y = g_dropData[i].pt.y;
			SetRect(&rc, x, y, x + cx, y + cy);
			PlayEnhMetaFile(hdc, g_dropData[i].hEnhMetaFile, &rc);	
		}

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY: {
		int i;

		for (i = 0; i < g_nDropDataCount; i++)
			DeleteEnhMetaFile(g_dropData[i].hEnhMetaFile);
		RevokeDragDrop(hwnd);
		if (pDropTarget != NULL)
			pDropTarget->Release();
		OleUninitialize();
		PostQuitMessage(0);
		return 0;
	}

	default:
		break;

	}

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


// CDropTarget


CDropTarget::CDropTarget(HWND hwnd)
{
	m_cRef = 1;
	m_hwnd = hwnd;
}

CDropTarget::~CDropTarget()
{
}

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

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

	AddRef();

	return S_OK;
}

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

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

	return m_cRef;
}

STDMETHODIMP CDropTarget::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
	HRESULT   hr;
	FORMATETC formatetc;

	*pdwEffect = DROPEFFECT_COPY;

	formatetc.cfFormat = CF_ENHMETAFILE;
	formatetc.ptd      = NULL;
	formatetc.dwAspect = DVASPECT_CONTENT;
	formatetc.lindex   = -1;
	formatetc.tymed    = TYMED_ENHMF;

	hr = pDataObj->QueryGetData(&formatetc);
	if (hr != S_OK) {
		m_bSupportFormat = FALSE;
		*pdwEffect = DROPEFFECT_NONE;
	}
	else
		m_bSupportFormat = TRUE;

	return S_OK; 
}

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
	*pdwEffect = DROPEFFECT_COPY;
	
	if (!m_bSupportFormat)
		*pdwEffect = DROPEFFECT_NONE;

	return S_OK;
}

STDMETHODIMP CDropTarget::DragLeave()
{
	return S_OK;
}

STDMETHODIMP CDropTarget::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
	HRESULT   hr;
	FORMATETC formatetc;
	STGMEDIUM medium;
	
	*pdwEffect = DROPEFFECT_COPY;

	formatetc.cfFormat = CF_ENHMETAFILE;
	formatetc.ptd      = NULL;
	formatetc.dwAspect = DVASPECT_CONTENT;
	formatetc.lindex   = -1;
	formatetc.tymed    = TYMED_ENHMF;

	hr = pDataObj->GetData(&formatetc, &medium);
	if (FAILED(hr)) {
		*pdwEffect = DROPEFFECT_NONE;
		return E_FAIL;
	}

	if (g_nDropDataCount < g_nDropDataMaxCount) {
		g_dropData[g_nDropDataCount].hEnhMetaFile = medium.hEnhMetaFile;
		g_dropData[g_nDropDataCount].pt.x = pt.x;
		g_dropData[g_nDropDataCount].pt.y = pt.y;
		ScreenToClient(m_hwnd, &g_dropData[g_nDropDataCount].pt);
		g_nDropDataCount++;
		InvalidateRect(m_hwnd, NULL, FALSE);
	}
	
	return S_OK;
}

CDropTargetはIDropTargetを実装したオブジェクトであり、WM_CREATEで作成されています。 また、WM_CREATEではRegisterDragDropも呼び出しているので、ウインドウにはいつでもデータをドロップできることになります。 DROPDATA構造体はドロップされた1つのデータを表します。 g_dropDataの要素数が5であることから、ドロップできるデータは5個までとしています。 現在ドロップされたデータの数はg_nDropDataCountに格納され、 WM_PAINTではこの数だけデータが描画されるようになります。

データがドラッグされた状態で、カーソルがウインドウの中に入った場合はIDropTarget::DragEnterが呼ばれます。

STDMETHODIMP CDropTarget::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
	HRESULT   hr;
	FORMATETC formatetc;

	*pdwEffect = DROPEFFECT_COPY;

	formatetc.cfFormat = CF_ENHMETAFILE;
	formatetc.ptd      = NULL;
	formatetc.dwAspect = DVASPECT_CONTENT;
	formatetc.lindex   = -1;
	formatetc.tymed    = TYMED_ENHMF;

	hr = pDataObj->QueryGetData(&formatetc);
	if (hr != S_OK) {
		m_bSupportFormat = FALSE;
		*pdwEffect = DROPEFFECT_NONE;
	}
	else
		m_bSupportFormat = TRUE;

	return S_OK; 
}

pdwEffectは、エフェクトに関する定数を指定することができます。 DROPEFFECT_COPYを指定した場合はカーソルの形がコピーを示すものになりますが、 どういう理由でこの定数を指定しているのでしょうか。 たとえば、Wordのデータを移動するとなった場合は、IDropTarget::Dropが呼ばれた際にWordのデータを削除しなければなりませんが、 これはどうやって実現すればよいか分かりません。 しかし、コピーであればWordと同じデータを表示すればよいだけですから、 コピーならば可能ということでDROPEFFECT_COPYを指定します。 pDataObjはドロップされようとしているデータを識別しており、 QueryGetDataを呼び出せば第1引数のフォーマットをサポートしているかを調べることができます。 今回は、データを拡張メタファイルとして扱う予定であるため、 cfFormatメンバにCF_ENHMETAFILEを指定するようにしています。 フォーマットがサポートされていない場合は、そのことを示す値をメンバ変数に保存し、 pdwEffectにDROPEFFECT_NONEを指定するようにします。 これにより、カーソルの形がドロップ不可を示すようになります。

IDropTarget::DragEnterが呼ばれ、カーソルがウインドウの中で移動された場合はIDropTarget::DragOverが呼ばれます。

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
	*pdwEffect = DROPEFFECT_COPY;
	
	if (!m_bSupportFormat)
		*pdwEffect = DROPEFFECT_NONE;

	return S_OK;
}

今回は特に行うことがないため、エフェクトにDROPEFFECT_COPYを指定するだけで構いません。 ただし、ドロップされようとしているデータをサポートしていない場合は、 DROPEFFECT_NONEを指定するようにします。

カーソルがウインドウの外へ移動した場合は、IDropTarget::DragLeaveが呼ばれます。

STDMETHODIMP CDropTarget::DragLeave()
{
	return S_OK;
}

今回は特に行うことがないため、何もしないようになっています。

データがウインドウへドロップされた場合は、IDropTarget::Dropが呼ばれます。

STDMETHODIMP CDropTarget::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
	HRESULT   hr;
	FORMATETC formatetc;
	STGMEDIUM medium;
	
	*pdwEffect = DROPEFFECT_COPY;

	formatetc.cfFormat = CF_ENHMETAFILE;
	formatetc.ptd      = NULL;
	formatetc.dwAspect = DVASPECT_CONTENT;
	formatetc.lindex   = -1;
	formatetc.tymed    = TYMED_ENHMF;

	hr = pDataObj->GetData(&formatetc, &medium);
	if (FAILED(hr)) {
		*pdwEffect = DROPEFFECT_NONE;
		return E_FAIL;
	}

	if (g_nDropDataCount < g_nDropDataMaxCount) {
		g_dropData[g_nDropDataCount].hEnhMetaFile = medium.hEnhMetaFile;
		g_dropData[g_nDropDataCount].pt.x = pt.x;
		g_dropData[g_nDropDataCount].pt.y = pt.y;
		ScreenToClient(m_hwnd, &g_dropData[g_nDropDataCount].pt);
		g_nDropDataCount++;
		InvalidateRect(m_hwnd, NULL, FALSE);
	}
	
	return S_OK;
}

ドロップされたデータを取得するために、IDataObject::GetDataを呼び出すようにします。 cfFormatメンバにCF_ENHMETAFILEを指定しているため、データは拡張メタファイルとして返ることになります。 ドロップできる最大数を超えていない場合は、データとドロップされた位置をg_dropDataに保存するようにし、 データの数を1つカウントします。 InvalidateRectを呼び出すことでWM_PAINTが生成され、ドロップされたデータが描画されることになります。


戻る