2025-11-28 00:35:46 +09:00

483 lines
15 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
// Sample that demonstrates using the shell drag drop services to get the presentation features
// that shell drag drop supports for both targets and sources. this includes
// 1) drop targets rendering the drag image
// 2) drop target provided drop tips
// 3) drag source populating the drag image information when using a custom data object
// 4) drag source enable drop tips
// 5) use the shell provided IDropSource implementation by calling SHDoDragDrop()
// this handles many of the edge cases for you dealing with different types of targets
//
// This is similar to the following article that covers this for managed apps
// http://blogs.msdn.com/adamroot/pages/shell-style-drag-and-drop-in-net-wpf-and-winforms.aspx
//
// This sample does not demonstrating rendering the drop location, applications should
// consider adding that feature to make it clear where the dropped item will go
//
// DragDropHelpers.h encapsulates a lot of the functionlaity used by this sample
// so look at its implementation to learn more about how that works
#define STRICT_TYPED_ITEMIDS
#include <windows.h>
#include "ShellHelpers.h"
#include "DragDropHelpers.h"
#include <new> // std::nothrow
#include "resource.h"
#include "DataObject.h"
class CDragDropVisualsApp : public CDragDropHelper
{
public:
CDragDropVisualsApp() : _cRef(1), _hdlg(NULL), _psiaDrop(NULL)
{
}
HRESULT DoModal(HWND hwnd)
{
DialogBoxParam(GetModuleHINSTANCE(), MAKEINTRESOURCE(IDD_DIALOG), hwnd, s_DlgProc, (LPARAM)this);
return S_OK;
}
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CDragDropVisualsApp, IDropTarget),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
long cRef = InterlockedDecrement(&_cRef);
if (!cRef)
delete this;
return cRef;
}
private:
~CDragDropVisualsApp()
{
SafeRelease(&_psiaDrop);
}
static INT_PTR CALLBACK s_DlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CDragDropVisualsApp *pcd = (CDragDropVisualsApp*) (LONG_PTR) GetWindowLongPtr(hdlg, DWLP_USER);
if (uMsg == WM_INITDIALOG)
{
pcd = (CDragDropVisualsApp *) lParam;
pcd->_hdlg = hdlg;
SetWindowLongPtr(hdlg, DWLP_USER, (LONG_PTR)pcd);
}
return pcd ? pcd->_DlgProc(uMsg, wParam, lParam) : FALSE;
}
INT_PTR _DlgProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
void _OnInitDlg();
void _OnDestroyDlg();
void _BindUI();
void _OnOpen();
void _BeginDrag(HWND hwndDragBegin);
bool _UseCustomDataObject()
{
return IsDlgButtonChecked(_hdlg, IDC_CUSTOM_DATAOBJECT) ? true : false;
}
HRESULT _GetDataObject(HWND hwndDragBegin, IDataObject **ppdtobj);
HRESULT _CopyShellItemArray(IShellItemArray *psia, IShellItemArray **ppsiaOut);
DWORD _GetDropEffects();
HRESULT _GetSelectedItems(IShellItemArray **ppsia)
{
*ppsia = NULL;
return _psiaDrop ? _psiaDrop->QueryInterface(ppsia) : E_NOINTERFACE;
}
HRESULT _GetFirstItem(REFIID riid, void **ppv)
{
*ppv = NULL;
IShellItemArray *psia;
HRESULT hr = _GetSelectedItems(&psia);
if (SUCCEEDED(hr))
{
hr = GetItemAt(psia, 0, riid, ppv);
psia->Release();
}
return hr;
}
virtual HRESULT OnDrop(IShellItemArray *psia, DWORD /* grfKeyState */)
{
HRESULT hr = _CopyShellItemArray(psia, &_psiaDrop);
if (SUCCEEDED(hr))
{
_BindUI();
}
return hr;
}
long _cRef;
HWND _hdlg;
IShellItemArray *_psiaDrop;
};
void CDragDropVisualsApp::_OnInitDlg()
{
InitializeDragDropHelper(_hdlg, DROPIMAGE_COPY, L"Drop Visuals Sample App");
_BindUI();
}
void CDragDropVisualsApp::_OnDestroyDlg()
{
TerminateDragDropHelper();
// cleanup the allocated HBITMAP
SetItemImageImageInStaticControl(GetDlgItem(_hdlg, IDC_IMAGE), NULL);
}
void CDragDropVisualsApp::_OnOpen()
{
// If multiple items are dropped into our sample, the "Open" button will only open the first item from the array
IShellItem *psi;
HRESULT hr = _GetFirstItem(IID_PPV_ARGS(&psi));
if (SUCCEEDED(hr))
{
hr = ShellExecuteItem(_hdlg, NULL, psi);
psi->Release();
}
}
void CDragDropVisualsApp::_BindUI()
{
IShellItem2 *psi;
HRESULT hr = _GetFirstItem(IID_PPV_ARGS(&psi));
if (SUCCEEDED(hr))
{
SetDlgItemText(_hdlg, IDC_STATIC, L"Start Drag Drop by clicking on icon");
SetItemImageImageInStaticControl(GetDlgItem(_hdlg, IDC_IMAGE), psi);
PWSTR psz;
hr = psi->GetDisplayName(SIGDN_NORMALDISPLAY, &psz);
if (SUCCEEDED(hr))
{
SetDlgItemText(_hdlg, IDC_NAME, psz);
CoTaskMemFree(psz);
}
SFGAOF sfgaof;
hr = psi->GetAttributes(SFGAO_CANCOPY| SFGAO_CANLINK | SFGAO_CANMOVE, &sfgaof);
if (SUCCEEDED(hr))
{
hr = ShellAttributesToString(sfgaof, &psz);
if (SUCCEEDED(hr))
{
SetDlgItemText(_hdlg, IDC_ATTRIBUTES, psz);
CoTaskMemFree(psz);
}
}
psi->Release();
}
else
{
SetItemImageImageInStaticControl(GetDlgItem(_hdlg, IDC_IMAGE), NULL);
SetDlgItemText(_hdlg, IDC_STATIC, L"Drop An Item Here");
SetDlgItemText(_hdlg, IDC_NAME, L"");
SetDlgItemText(_hdlg, IDC_ATTRIBUTES, L"");
}
EnableWindow(GetDlgItem(_hdlg, IDC_CUSTOM_DATAOBJECT), SUCCEEDED(hr) ? TRUE : FALSE);
EnableWindow(GetDlgItem(_hdlg, IDC_OPEN), SUCCEEDED(hr) ? TRUE : FALSE);
EnableWindow(GetDlgItem(_hdlg, IDC_CLEAR), SUCCEEDED(hr) ? TRUE : FALSE);
}
// this inits the SHDRAGIMAGE from a bitmap and the HWND that initiated
// the dragging. this uses the current cursor pos to compute the offsets
// in the image to get the proper positioning of that image relative to the
// drag cursors
void InitializeDragImageFromWindow(HWND hwndDragBegin, HBITMAP hbmp, SHDRAGIMAGE *pdi)
{
ZeroMemory(pdi, sizeof(pdi));
pdi->crColorKey = CLR_NONE; // assume alpha image, no need for color key
pdi->hbmpDragImage = hbmp;
BITMAP bm;
if (GetObject(hbmp, sizeof(bm), &bm))
{
pdi->sizeDragImage.cx = bm.bmWidth;
pdi->sizeDragImage.cy = bm.bmHeight;
}
RECT rc;
POINT ptDrag;
if (GetWindowRect(hwndDragBegin, &rc) && GetCursorPos(&ptDrag))
{
pdi->ptOffset.x = ptDrag.x - rc.left;
pdi->ptOffset.y = ptDrag.y - rc.top;
}
}
// USE_ITEMS_DATAOBJECT demonstrates what happens when using the data object from
// the shell items. that data object supports all of the features needed to get drag
// images, drop tips, etc.
//
// for appliations that create their own data object you need to have support
// for SetData(), QueryGetData() and GetData() of custom formats. this is
// demonstrated in DataObject.cpp
HRESULT CDragDropVisualsApp::_GetDataObject(HWND hwndDragBegin, IDataObject **ppdtobj)
{
HRESULT hr;
if (_UseCustomDataObject())
{
hr = CDataObject_CreateInstance(IID_PPV_ARGS(ppdtobj));
if (SUCCEEDED(hr))
{
IDragSourceHelper2 *pdsh;
if (SUCCEEDED(GetDragDropHelper(IID_PPV_ARGS(&pdsh))))
{
// enable drop tips
pdsh->SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT);
// we need to make a copy of the HBITMAP held by the static control
// as InitializeFromBitmap() takes owership of this
const HBITMAP hbmp = (HBITMAP)CopyImage((HBITMAP) SendMessage(hwndDragBegin, STM_GETIMAGE, IMAGE_BITMAP, 0), IMAGE_BITMAP, 0, 0, 0);
// alternate, load the bitmap from a resource
// HBITMAP hbmp = (HBITMAP)LoadImage(NULL, MAKEINTRESOURCE(OBM_CLOSE), IMAGE_BITMAP, 128, 128, 0);
SHDRAGIMAGE di;
InitializeDragImageFromWindow(hwndDragBegin, hbmp, &di);
// note that InitializeFromBitmap() takes ownership of the hbmp
// so we should not free it by calling DeleteObject()
pdsh->InitializeFromBitmap(&di, *ppdtobj);
pdsh->Release();
}
}
}
else
{
*ppdtobj = NULL;
IShellItemArray *psiaItems;
hr = _GetSelectedItems(&psiaItems);
if (SUCCEEDED(hr))
{
hr = psiaItems->BindToHandler(NULL, BHID_DataObject, IID_PPV_ARGS(ppdtobj));
psiaItems->Release();
}
}
return hr;
}
// The IDataObject passed in by OLE through the CDragDropHelper::Drop function is only valid until the Drop function returns
// This means the the IShellItemArray we are receiving may go bad as well since it is based on the incoming IDataObject
// Here we will create a stream and marshal the IShellItemArray which will create a copied IShellItemArray which does not depend on the IDataObject
HRESULT CDragDropVisualsApp::_CopyShellItemArray(IShellItemArray *psia, IShellItemArray **ppsiaOut)
{
*ppsiaOut = NULL;
IStream *pstm;
HRESULT hr = CoMarshalInterThreadInterfaceInStream(__uuidof(psia), psia, &pstm);
if (SUCCEEDED(hr))
{
hr = CoGetInterfaceAndReleaseStream(pstm, IID_PPV_ARGS(ppsiaOut));
pstm = NULL; // released by CoGetInterfaceAndReleaseStream
}
return hr;
}
DWORD CDragDropVisualsApp::_GetDropEffects()
{
DWORD dwEffect;
if (_UseCustomDataObject())
{
dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK;
}
else
{
dwEffect = DROPEFFECT_NONE;
IShellItemArray *psiaItems;
if (SUCCEEDED(_GetSelectedItems(&psiaItems)))
{
psiaItems->GetAttributes(SIATTRIBFLAGS_AND, DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK, &dwEffect);
psiaItems->Release();
}
}
return dwEffect;
}
// x, y in client coordinates
BOOL CheckForDragBegin(HWND hwnd, int x, int y)
{
int const cxDrag = GetSystemMetrics(SM_CXDRAG);
int const cyDrag = GetSystemMetrics(SM_CYDRAG);
// See if the user moves a certain number of pixels in any direction
RECT rcDragRadius;
SetRect(&rcDragRadius, x - cxDrag, y - cyDrag, x + cxDrag, y + cyDrag);
MapWindowRect(hwnd, NULL, &rcDragRadius); // client -> screen
SetCapture(hwnd);
do
{
// Sleep the thread waiting for mouse input. Prevents pegging the CPU in a
// PeekMessage loop.
MSG msg;
switch (MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_MOUSE, MWMO_INPUTAVAILABLE))
{
case WAIT_OBJECT_0:
{
if (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE))
{
// See if the application wants to process the message...
if (CallMsgFilter(&msg, MSGF_COMMCTRL_BEGINDRAG) == 0)
{
switch (msg.message)
{
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
// Released the mouse without moving outside the
// drag radius, not beginning a drag.
ReleaseCapture();
return FALSE;
case WM_MOUSEMOVE:
if (!PtInRect(&rcDragRadius, msg.pt))
{
// Moved outside the drag radius, beginning a drag.
ReleaseCapture();
return TRUE;
}
break;
default:
TranslateMessage(&msg);
DispatchMessage(&msg);
break;
}
}
}
break;
}
default:
break;
}
// WM_CANCELMODE messages will unset the capture, in that
// case I want to exit this loop
}
while (GetCapture() == hwnd);
return FALSE;
}
void CDragDropVisualsApp::_BeginDrag(HWND hwndDragBegin)
{
POINT pt;
GetCursorPos(&pt);
MapWindowPoints(HWND_DESKTOP, hwndDragBegin, &pt, 1); // screen -> client
if (CheckForDragBegin(hwndDragBegin, pt.x, pt.y))
{
IDataObject *pdtobj;
HRESULT hr = _GetDataObject(hwndDragBegin, &pdtobj);
if (SUCCEEDED(hr))
{
DWORD dwEffectResult;
hr = SHDoDragDrop(_hdlg, pdtobj, NULL, _GetDropEffects(), &dwEffectResult);
pdtobj->Release();
}
}
}
INT_PTR CDragDropVisualsApp::_DlgProc(UINT uMsg, WPARAM wParam, LPARAM /*lParam*/)
{
switch (uMsg)
{
case WM_INITDIALOG:
_OnInitDlg();
break;
case WM_COMMAND:
{
const int idCmd = LOWORD(wParam);
switch (idCmd)
{
case IDOK:
case IDCANCEL:
return EndDialog(_hdlg, idCmd);
case IDC_OPEN:
_OnOpen();
break;
case IDC_CLEAR:
SafeRelease(&_psiaDrop);
_BindUI();
break;
case IDC_IMAGE:
switch (HIWORD(wParam))
{
case STN_CLICKED:
_BeginDrag(GetDlgItem(_hdlg, idCmd));
break;
case STN_DBLCLK:
_OnOpen();
break;
}
break;
}
}
break;
case WM_DESTROY:
_OnDestroyDlg();
break;
default:
return FALSE;
}
return TRUE;
}
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
CDragDropVisualsApp *pdlg = new (std::nothrow) CDragDropVisualsApp();
if (pdlg)
{
pdlg->DoModal(NULL);
pdlg->Release();
}
CoUninitialize();
}
return 0;
}