-
Notifications
You must be signed in to change notification settings - Fork 1
/
DragDropVisuals.cpp
482 lines (423 loc) · 14.6 KB
/
DragDropVisuals.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
// 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;
}