#pragma once #include #include using namespace std; class CEnumFormatEtc : public IEnumFORMATETC { public: CEnumFormatEtc(const vector & vFormatEtc) { m_nRefCount = 0; m_nIndex = 0; m_vFormatEtc = vFormatEtc; } protected: vector m_vFormatEtc; int m_nRefCount; int m_nIndex; public: // IUnknown members STDMETHOD(QueryInterface) (REFIID refiid, void FAR * FAR * ppvObject) { *ppvObject = (refiid == IID_IUnknown || refiid == IID_IEnumFORMATETC) ? this : nullptr; if (*ppvObject != nullptr) ((LPUNKNOWN)*ppvObject)->AddRef(); return *ppvObject == nullptr ? E_NOINTERFACE : S_OK; } STDMETHOD_(ULONG, AddRef) (void) { return ++m_nRefCount; } STDMETHOD_(ULONG, Release) (void) { int nRefCount = --m_nRefCount; if (nRefCount == 0) delete this; return nRefCount; } // IEnumFORMATETC members STDMETHOD(Next) (ULONG celt, LPFORMATETC lpFormatEtc, ULONG FAR * pceltFetched) { if (pceltFetched != nullptr) *pceltFetched = 0; ULONG cReturn = celt; if (celt <= 0 || lpFormatEtc == nullptr || m_nIndex >= (int)m_vFormatEtc.size()) return S_FALSE; if (pceltFetched == nullptr && celt != 1) // pceltFetched can be nullptr only for 1 item request return S_FALSE; while (m_nIndex < (int)m_vFormatEtc.size() && cReturn > 0) { *lpFormatEtc++ = m_vFormatEtc[m_nIndex++]; cReturn--; } if (pceltFetched != nullptr) *pceltFetched = celt - cReturn; return cReturn == 0 ? S_OK : S_FALSE; } STDMETHOD(Skip) (ULONG celt) { if ((m_nIndex + (int)celt) >= (int)m_vFormatEtc.size()) return S_FALSE; m_nIndex += celt; return S_OK; } STDMETHOD(Reset) (void) { m_nIndex = 0; return S_OK; } STDMETHOD(Clone) (IEnumFORMATETC FAR * FAR * ppCloneEnumFormatEtc) { if (ppCloneEnumFormatEtc == nullptr) return E_POINTER; *ppCloneEnumFormatEtc = new CEnumFormatEtc(m_vFormatEtc); ((CEnumFormatEtc *)*ppCloneEnumFormatEtc)->AddRef(); ((CEnumFormatEtc *)*ppCloneEnumFormatEtc)->m_nIndex = m_nIndex; return S_OK; } }; class CDropSource : public IDropSource { public: CDropSource() { m_nRefCount = 0; } protected: int m_nRefCount; public: // IUnknown members STDMETHOD(QueryInterface) (REFIID refiid, void FAR * FAR * ppvObject) { *ppvObject = (refiid == IID_IUnknown || refiid == IID_IDropSource) ? this : nullptr; if (*ppvObject != nullptr) ((LPUNKNOWN)*ppvObject)->AddRef(); return *ppvObject == nullptr ? E_NOINTERFACE : S_OK; } STDMETHOD_(ULONG, AddRef) (void) { return ++m_nRefCount; } STDMETHOD_(ULONG, Release) (void) { int nRefCount = --m_nRefCount; if (nRefCount == 0) delete this; return nRefCount; } // IDropSource members STDMETHOD(QueryContinueDrag) (BOOL bEscapePressed, DWORD dwKeyState) { if (bEscapePressed) return DRAGDROP_S_CANCEL; if (!(dwKeyState & (MK_LBUTTON | MK_RBUTTON))) return DRAGDROP_S_DROP; return S_OK; } STDMETHOD(GiveFeedback) (DWORD /*dwEffect*/) { return DRAGDROP_S_USEDEFAULTCURSORS; } }; class CDataObject : public IDataObject { public: CDataObject(CDropSource * pDropSource) { m_nRefCount = 0; m_pDropSource = pDropSource; m_bSwappedButtons = GetSystemMetrics(SM_SWAPBUTTON); } virtual ~CDataObject() { for (vector::iterator posStgMedium = m_vStgMedium.begin(); posStgMedium != m_vStgMedium.end(); posStgMedium++) ReleaseStgMedium(&(*posStgMedium)); } protected: CDropSource * m_pDropSource; int m_nRefCount; BOOL m_bSwappedButtons; vector m_vFormatEtc; vector m_vStgMedium; public: // IUnknown members STDMETHOD(QueryInterface) (REFIID refiid, void FAR * FAR * ppvObject) { *ppvObject = (refiid == IID_IUnknown || refiid == IID_IDataObject) ? this : nullptr; if (*ppvObject != nullptr) ((LPUNKNOWN)*ppvObject)->AddRef(); return *ppvObject == nullptr ? E_NOINTERFACE : S_OK; } STDMETHOD_(ULONG, AddRef) (void) { return ++m_nRefCount; } STDMETHOD_(ULONG, Release) (void) { int nRefCount = --m_nRefCount; if (nRefCount == 0) delete this; return nRefCount; } // IDataObject members STDMETHOD(GetData) (FORMATETC __RPC_FAR * pformatetcIn, STGMEDIUM __RPC_FAR * pmedium) { if (pformatetcIn == nullptr || pmedium == nullptr) return E_INVALIDARG; ZeroMemory(pmedium, sizeof(STGMEDIUM)); for (int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++) { if (pformatetcIn->tymed & m_vFormatEtc[nFormatEtc].tymed && pformatetcIn->dwAspect == m_vFormatEtc[nFormatEtc].dwAspect && pformatetcIn->cfFormat == m_vFormatEtc[nFormatEtc].cfFormat) { if (m_vStgMedium[nFormatEtc].tymed == TYMED_NULL) return OnRenderData(m_vFormatEtc[nFormatEtc], pmedium, (GetAsyncKeyState(m_bSwappedButtons ? VK_RBUTTON : VK_LBUTTON) >= 0)) ? S_OK : DV_E_FORMATETC; CopyMedium(pmedium, m_vStgMedium[nFormatEtc], m_vFormatEtc[nFormatEtc]); return S_OK; } } return DV_E_FORMATETC; } STDMETHOD(GetDataHere) (FORMATETC __RPC_FAR * /*pformatetc*/, STGMEDIUM __RPC_FAR * /*pmedium*/) { return E_NOTIMPL; } STDMETHOD(QueryGetData) (FORMATETC __RPC_FAR * pformatetc) { if (pformatetc == nullptr) return E_INVALIDARG; if (!(pformatetc->dwAspect & DVASPECT_CONTENT)) return DV_E_DVASPECT; HRESULT hResult = DV_E_TYMED; for (int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++) { if (!(pformatetc->tymed & m_vFormatEtc[nFormatEtc].tymed)) { hResult = DV_E_TYMED; continue; } if (pformatetc->cfFormat == m_vFormatEtc[nFormatEtc].cfFormat) return S_OK; hResult = DV_E_CLIPFORMAT; } return hResult; } STDMETHOD(GetCanonicalFormatEtc) (FORMATETC __RPC_FAR * /*pformatectIn*/, FORMATETC __RPC_FAR * pformatetcOut) { return pformatetcOut == nullptr ? E_INVALIDARG : DATA_S_SAMEFORMATETC; } STDMETHOD(SetData) (FORMATETC __RPC_FAR * pformatetc, STGMEDIUM __RPC_FAR * pmedium, BOOL bRelease) { if (pformatetc == nullptr || pmedium == nullptr) return E_INVALIDARG; m_vFormatEtc.push_back(*pformatetc); STGMEDIUM StgMedium = *pmedium; if (!bRelease) CopyMedium(&StgMedium, *pmedium, *pformatetc); m_vStgMedium.push_back(StgMedium); return S_OK; } STDMETHOD(EnumFormatEtc) (DWORD dwDirection, IEnumFORMATETC __RPC_FAR * __RPC_FAR * ppenumFormatEtc) { if (ppenumFormatEtc == nullptr) return E_POINTER; switch (dwDirection) { case DATADIR_GET: *ppenumFormatEtc = new CEnumFormatEtc(m_vFormatEtc); ((CEnumFormatEtc *)*ppenumFormatEtc)->AddRef(); return S_OK; default: *ppenumFormatEtc = nullptr; return E_NOTIMPL; } } STDMETHOD(DAdvise) (FORMATETC __RPC_FAR * /*pformatetc*/, DWORD /*advf*/, IAdviseSink __RPC_FAR * /*pAdvSink*/, DWORD __RPC_FAR * /*pdwConnection*/) { return OLE_E_ADVISENOTSUPPORTED; } STDMETHOD(DUnadvise) (DWORD /*dwConnection*/) { return E_NOTIMPL; } STDMETHOD(EnumDAdvise) (IEnumSTATDATA __RPC_FAR * __RPC_FAR * /*ppenumAdvise*/) { return OLE_E_ADVISENOTSUPPORTED; } void CopyMedium(STGMEDIUM * pMedDest, STGMEDIUM & MedSrc, FORMATETC & FmtSrc) { switch (MedSrc.tymed) { case TYMED_HGLOBAL: pMedDest->hGlobal = (HGLOBAL)OleDuplicateData(MedSrc.hGlobal, FmtSrc.cfFormat, NULL); break; case TYMED_GDI: pMedDest->hBitmap = (HBITMAP)OleDuplicateData(MedSrc.hBitmap, FmtSrc.cfFormat, NULL); break; case TYMED_MFPICT: pMedDest->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData(MedSrc.hMetaFilePict, FmtSrc.cfFormat, NULL); break; case TYMED_ENHMF: pMedDest->hEnhMetaFile = (HENHMETAFILE)OleDuplicateData(MedSrc.hEnhMetaFile, FmtSrc.cfFormat, NULL); break; case TYMED_FILE: pMedDest->lpszFileName = (LPOLESTR)OleDuplicateData(MedSrc.lpszFileName, FmtSrc.cfFormat, NULL); break; case TYMED_ISTREAM: pMedDest->pstm = MedSrc.pstm; MedSrc.pstm->AddRef(); break; case TYMED_ISTORAGE: pMedDest->pstg = MedSrc.pstg; MedSrc.pstg->AddRef(); break; } pMedDest->tymed = MedSrc.tymed; pMedDest->pUnkForRelease = nullptr; if (MedSrc.pUnkForRelease != nullptr) { pMedDest->pUnkForRelease = MedSrc.pUnkForRelease; MedSrc.pUnkForRelease->AddRef(); } } virtual BOOL OnRenderData(FORMATETC & /*FormatEtc*/, STGMEDIUM * /*pStgMedium*/, BOOL /*bDropComplete*/) { return FALSE; } }; class CDropTarget : public IDropTarget { public: CDropTarget(HWND hTargetWnd) { m_hTargetWnd = hTargetWnd; m_nRefCount = 0; m_bAllowDrop = FALSE; m_pDropTargetHelper = nullptr; ZeroMemory(&m_FormatEtc, sizeof(FORMATETC)); ZeroMemory(&m_StgMedium, sizeof(STGMEDIUM)); if (FAILED(CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER, IID_IDropTargetHelper, (LPVOID *)&m_pDropTargetHelper))) m_pDropTargetHelper = nullptr; } virtual ~CDropTarget() { if (m_pDropTargetHelper != nullptr) { m_pDropTargetHelper->Release(); m_pDropTargetHelper = nullptr; } } protected: HWND m_hTargetWnd; int m_nRefCount; struct IDropTargetHelper * m_pDropTargetHelper; vector m_vFormatEtc; BOOL m_bAllowDrop; FORMATETC m_FormatEtc; STGMEDIUM m_StgMedium; public: // IUnknown members STDMETHOD(QueryInterface) (REFIID refiid, void FAR * FAR * ppvObject) { *ppvObject = (refiid == IID_IUnknown || refiid == IID_IDropTarget) ? this : nullptr; if (*ppvObject != nullptr) ((LPUNKNOWN)*ppvObject)->AddRef(); return *ppvObject == nullptr ? E_NOINTERFACE : S_OK; } STDMETHOD_(ULONG, AddRef) (void) { return ++m_nRefCount; } STDMETHOD_(ULONG, Release) (void) { int nRefCount = --m_nRefCount; if (nRefCount == 0) delete this; return nRefCount; } STDMETHOD(DragEnter) (IDataObject __RPC_FAR * pDataObject, DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR * pdwEffect) { if (pDataObject == nullptr) return E_INVALIDARG; if (m_pDropTargetHelper != nullptr) m_pDropTargetHelper->DragEnter(m_hTargetWnd, pDataObject, (LPPOINT)&pt, *pdwEffect); ZeroMemory(&m_FormatEtc, sizeof(FORMATETC)); if (m_StgMedium.tymed != TYMED_NULL) ReleaseStgMedium(&m_StgMedium); ZeroMemory(&m_StgMedium, sizeof(STGMEDIUM)); for (int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++) { STGMEDIUM StgMedium; m_bAllowDrop = (pDataObject->GetData(&m_vFormatEtc[nFormatEtc], &StgMedium) == S_OK); if (m_bAllowDrop) { // Store drag data for later use in DragOver m_FormatEtc = m_vFormatEtc[nFormatEtc]; m_StgMedium = StgMedium; // Get client cursor position CWindow hWnd(m_hTargetWnd); CPoint point(pt.x, pt.y); hWnd.ScreenToClient(&point); *pdwEffect = OnDragEnter(m_FormatEtc, m_StgMedium, dwKeyState, point); break; } } QueryDrop(dwKeyState, pdwEffect); return S_OK; } STDMETHOD(DragOver) (DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR * pdwEffect) { if (m_pDropTargetHelper) m_pDropTargetHelper->DragOver((LPPOINT)&pt, *pdwEffect); if (m_bAllowDrop && m_FormatEtc.cfFormat != CF_NULL && m_StgMedium.tymed != TYMED_NULL) { // Get client cursor position CWindow hWnd(m_hTargetWnd); CPoint point(pt.x, pt.y); hWnd.ScreenToClient(&point); *pdwEffect = OnDragOver(m_FormatEtc, m_StgMedium, dwKeyState, point); } QueryDrop(dwKeyState, pdwEffect); return S_OK; } STDMETHOD(DragLeave) (void) { if (m_pDropTargetHelper) m_pDropTargetHelper->DragLeave(); OnDragLeave(); m_bAllowDrop = FALSE; ZeroMemory(&m_FormatEtc, sizeof(FORMATETC)); if (m_StgMedium.tymed != TYMED_NULL) ReleaseStgMedium(&m_StgMedium); ZeroMemory(&m_StgMedium, sizeof(STGMEDIUM)); return S_OK; } STDMETHOD(Drop) (IDataObject __RPC_FAR * pDataObject, DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR * pdwEffect) { if (pDataObject == nullptr) return E_INVALIDARG; if (m_pDropTargetHelper) m_pDropTargetHelper->Drop(pDataObject, (LPPOINT)&pt, *pdwEffect); if (m_bAllowDrop && m_FormatEtc.cfFormat != CF_NULL && QueryDrop(dwKeyState, pdwEffect)) { STGMEDIUM StgMedium; if (pDataObject->GetData(&m_FormatEtc, &StgMedium) == S_OK) { // Get client cursor position CWindow hWnd(m_hTargetWnd); CPoint point(pt.x, pt.y); hWnd.ScreenToClient(&point); if (!OnDrop(m_FormatEtc, StgMedium, *pdwEffect, point)) *pdwEffect = DROPEFFECT_NONE; ReleaseStgMedium(&StgMedium); } } m_bAllowDrop = FALSE; ZeroMemory(&m_FormatEtc, sizeof(FORMATETC)); if (m_StgMedium.tymed != TYMED_NULL) ReleaseStgMedium(&m_StgMedium); ZeroMemory(&m_StgMedium, sizeof(STGMEDIUM)); return S_OK; } void AddSupportedFormat(FORMATETC & FormatEtc) { m_vFormatEtc.push_back(FormatEtc); } void AddSupportedFormat(CLIPFORMAT cfFormat) { FORMATETC FormatEtc; ZeroMemory(&FormatEtc, sizeof(FORMATETC)); FormatEtc.cfFormat = cfFormat; FormatEtc.dwAspect = DVASPECT_CONTENT; FormatEtc.lindex = -1; FormatEtc.tymed = TYMED_HGLOBAL; AddSupportedFormat(FormatEtc); } BOOL QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect) { DWORD dwEffects = *pdwEffect; if (!m_bAllowDrop) { *pdwEffect = DROPEFFECT_NONE; return FALSE; } *pdwEffect = (dwKeyState & MK_CONTROL) ? ((dwKeyState & MK_SHIFT) ? DROPEFFECT_LINK : DROPEFFECT_COPY) : ((dwKeyState & MK_SHIFT) ? DROPEFFECT_MOVE : 0); if (*pdwEffect == 0) { if (dwEffects & DROPEFFECT_COPY) *pdwEffect = DROPEFFECT_COPY; else if (dwEffects & DROPEFFECT_MOVE) *pdwEffect = DROPEFFECT_MOVE; else if (dwEffects & DROPEFFECT_LINK) *pdwEffect = DROPEFFECT_LINK; else *pdwEffect = DROPEFFECT_NONE; } else if (!(*pdwEffect & dwEffects)) *pdwEffect = DROPEFFECT_NONE; return (*pdwEffect != DROPEFFECT_NONE); } virtual DWORD OnDragEnter(FORMATETC & /*FormatEtc*/, STGMEDIUM & /*StgMedium*/, DWORD /*dwKeyState*/, CPoint /*point*/) { return FALSE; } virtual DWORD OnDragOver(FORMATETC & /*FormatEtc*/, STGMEDIUM & /*StgMedium*/, DWORD /*dwKeyState*/, CPoint /*point*/) { return FALSE; } virtual BOOL OnDrop(FORMATETC & /*FormatEtc*/, STGMEDIUM & /*StgMedium*/, DWORD /*dwEffect*/, CPoint /*point*/) { return FALSE; } virtual void OnDragLeave() { } }; template class CDropTargetT : public CDropTarget { public: CDropTargetT(HWND hTargetWnd) : CDropTarget(hTargetWnd) { m_pDelegate = nullptr; } protected: T * m_pDelegate; public: BOOL Register(T * pDelegate) { m_pDelegate = pDelegate; return TRUE; } virtual DWORD OnDragEnter(FORMATETC & FormatEtc, STGMEDIUM & StgMedium, DWORD dwKeyState, CPoint point) { return m_pDelegate == nullptr ? DROPEFFECT_NONE : m_pDelegate->OnDragEnter(FormatEtc, StgMedium, dwKeyState, point); } virtual DWORD OnDragOver(FORMATETC & FormatEtc, STGMEDIUM & StgMedium, DWORD dwKeyState, CPoint point) { return m_pDelegate == nullptr ? DROPEFFECT_NONE : m_pDelegate->OnDragOver(FormatEtc, StgMedium, dwKeyState, point); } virtual BOOL OnDrop(FORMATETC & FormatEtc, STGMEDIUM & StgMedium, DWORD dwEffect, CPoint point) { return m_pDelegate == nullptr ? FALSE : m_pDelegate->OnDrop(FormatEtc, StgMedium, dwEffect, point); } virtual void OnDragLeave() { if (m_pDelegate != nullptr) m_pDelegate->OnDragLeave(); } }; template class CDataObjectT : public CDataObject { public: CDataObjectT(CDropSource * pDropSource) : CDataObject(pDropSource) { m_pDelegate = FALSE; } protected: T * m_pDelegate; public: BOOL Register(T * pDelegate) { m_pDelegate = pDelegate; return TRUE; } virtual BOOL OnRenderData(FORMATETC & FormatEtc, STGMEDIUM * pStgMedium, BOOL bDropComplete) { return m_pDelegate == nullptr ? FALSE : m_pDelegate->OnRenderData(FormatEtc, pStgMedium, bDropComplete); } }; template class CDragDrop { public: CDragDrop() { m_pDropSource = nullptr; m_pDataObject = nullptr; m_pDropTarget = nullptr; m_hTargetWnd = nullptr; } virtual ~CDragDrop() { if (m_pDropSource != nullptr) m_pDropSource->Release(); if (m_pDataObject != nullptr) m_pDataObject->Release(); } protected: CDropSource * m_pDropSource; CDataObjectT * m_pDataObject; CDropTargetT * m_pDropTarget; HWND m_hTargetWnd; public: BOOL Register(T * pDelegate, BOOL bDropSource = TRUE) { m_hTargetWnd = pDelegate->m_hWnd; // Instantiate new drop target object m_pDropTarget = new CDropTargetT(m_hTargetWnd); m_pDropTarget->Register(pDelegate); // Register drop target if (FAILED(RegisterDragDrop(m_hTargetWnd, m_pDropTarget))) { delete m_pDropTarget; m_pDropTarget = nullptr; return FALSE; } // Is this a drop target only? if (!bDropSource) return TRUE; // Instantiate new drop source object m_pDropSource = new CDropSource; m_pDropSource->AddRef(); m_pDataObject = new CDataObjectT(m_pDropSource); m_pDataObject->AddRef(); // Register drop source delegate for data render return m_pDataObject->Register(pDelegate); } BOOL Revoke() { m_pDropTarget = nullptr; return (RevokeDragDrop(m_hTargetWnd) == S_OK); } BOOL AddTargetFormat(CLIPFORMAT cfFormat) { if (m_pDropTarget == nullptr) return FALSE; m_pDropTarget->AddSupportedFormat(cfFormat); return TRUE; } BOOL AddSourceFormat(CLIPFORMAT cfFormat) { if (m_pDataObject == nullptr) return FALSE; FORMATETC FormatEtc; ZeroMemory(&FormatEtc, sizeof(FORMATETC)); FormatEtc.cfFormat = cfFormat; FormatEtc.dwAspect = DVASPECT_CONTENT; FormatEtc.lindex = -1; FormatEtc.tymed = TYMED_HGLOBAL; STGMEDIUM StgMedium; ZeroMemory(&StgMedium, sizeof(STGMEDIUM)); return SUCCEEDED(m_pDataObject->SetData(&FormatEtc, &StgMedium, TRUE)); } BOOL SetClipboard(FORMATETC & FormatEtc, STGMEDIUM & StgMedium) { if (m_pDataObject == nullptr) return DROPEFFECT_NONE; if (FAILED(m_pDataObject->SetData(&FormatEtc, &StgMedium, TRUE))) return DROPEFFECT_NONE; return (OleSetClipboard(m_pDataObject) == S_OK); } BOOL FlushClipboard() { return (OleFlushClipboard() == S_OK); } DWORD DoDragDrop(SHDRAGIMAGE * pDragImage = nullptr, DWORD dwValidEffects = DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK) { if (m_pDataObject == nullptr) return DROPEFFECT_NONE; IDragSourceHelper * pDragSourceHelper = nullptr; // Instantiate drag source helper object if (pDragImage != nullptr) { if (FAILED(CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER, IID_IDragSourceHelper, (LPVOID *)&pDragSourceHelper))) pDragSourceHelper = nullptr; if (pDragSourceHelper != nullptr) pDragSourceHelper->InitializeFromBitmap(pDragImage, m_pDataObject); } DWORD dwEffects = DROPEFFECT_NONE; dwEffects = ::DoDragDrop(m_pDataObject, m_pDropSource, dwValidEffects, &dwEffects) == DRAGDROP_S_DROP ? DROPEFFECT_NONE : dwEffects; // Destroy drag source helper object if (pDragSourceHelper != nullptr) pDragSourceHelper->Release(); return dwEffects; } };