仿酷狗音乐播放器开发日志二十七 用ole为窗体增加文件拖动功能(附源码)

转载请说明原出处,谢谢~~

       中秋到了,出去玩了几天。今天把仿酷狗程序做了收尾,已经开发完成了,下一篇博客把完结的情况说一下。在这篇博客里说一下使用OLE为窗体增加文件拖拽的功能。使用播放器,我更喜欢直接拖动音乐文件添加到软件里,所以做这个功能很重要。做OLE拖拽之前学习了两篇文章:

http://www.codeproject.com/Articles/840/How-to-Implement-Drag-and-Drop-Between-Your-Progra%E3%80%91

http://blog.csdn.net/liu4584945/article/details/6205341

      先来看一下原酷狗里的文件拖动功能:


         可以看到,我拖动音乐文件到软件里,进去音乐列表的范围内就显示可复制的图标,不在范围则显示不可拖拽的图标。

         让软件支持文件拖拽有两种方法:OLE拖放和文件管理器拖放。第一种方法通过处理窗体的WM_DROPFILES消息,窗体就可以收到拖放进来的文件名。OLE拖放允许你拖放可同时被保存在剪贴板上的任何数据,并且更加细致的控制拖放过程。第一个是比较简单的也是我之前一直使用的方法,下面相关函数的介绍:

        UINT DragQueryFile(HDROP hDrop, UINT iFile, LPTSTR lpszFile, UINT cch)

       本函数用来取得拖放的文件名。其中,hDrop是一个指向含有被拖放的文件名的结构体的句柄;iFiles是要查询的文件序号,因为一次可能同时拖动很多个文件;lpszFiles是出口缓冲区指针,保存iFiles指定序号的文件的路径名,cch指定该缓冲区的大小。有两点值得注意,第一,如果我们在调用该函数的时候,指定iFile为0xFFFFFFFF,则DragQueryFile将忽略lpszFile和cch参数,返回本次拖放操作的文件数目;第二,如果指定lpszFile为NULL,则函数将返回实际所需的缓冲区长度。

         BOOL DragQueryPoint(HDROP hDrop, LPPOINT lppt);


         本函数用来获取,当拖放操作正在进行时,鼠标指针的位置。第二个参数lppt是一个指向POINT结构体的指针,用来保存文件放下时,鼠标指针的位置。窗口可以调用该函数以查询文件是否落在自己的窗口矩形中。

         void DragFinish(HDROP hDrop);

         当拖放操作处理完毕后需调用该函数释放系统分配来传输文件名的内存。

         使用这个方法时,在窗体初始化完成后调用函数调用DragAcceptFiles(m_hWnd,TRUE),让窗体可以接收WM_DROPFILES消息。然后在Duilib的窗体类中重写HandleCustomMessage函数,去处理WM_DROPFILES消息,代码如下:

	else if(uMsg == WM_DROPFILES)
	{
		HDROP hDrop = (HDROP)wParam;
		TCHAR szFilePathName[_MAX_PATH] = {0};

		UINT  nNumOfFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); //得到文件个数

		for (UINT nIndex=0 ; nIndex< nNumOfFiles; ++nIndex)
		{
			DragQueryFile(hDrop, nIndex, szFilePathName, _MAX_PATH);  //得到文件名
			//获取了文件名,开始处理
		}

		DragFinish(hDrop);
	}


        这样就处理完了,处理WM_DROPFILES消息的方法简单,但是效果比较差,无法动态获取文件在窗体上的坐标,样式也难看一些,拖动时的图标仅仅是一个加号而不是原文件的图标样式。适用于做一些要求简单的文件拖动效果。


       接下来说一下OLE文件拖动:

       OLE文件拖动属于Windows的外壳扩展编程。我在网上查了一些资料,都是关于MFC下OLE拖放的。最后找到了博客开头起到的文件是介绍win32拖放的。我参考了两篇文章的代码,最终封装为一个DropTargetEx类。但是这样做了之后的确是可以达到拖放效果,但是发现拖放时的图标还仅仅是一个加号,而不像我博客开头贴的原酷狗的图片,是对应的文件的图标。查阅资料后了解需要使用IDropTargetHelper接口,让系统辅助来处理消息,就可以达到漂亮的拖拽效果,具体代码我都写在类里面了。大家可以根据自己的需求来修改。

       这里先看一下最终的效果:


       这个类可以用于win32工程和duilib工程里,使用方法为,在duilib的窗体类中声明一个拖放类的对象:

	CDropTargetEx	m_DropTarget;	//使窗体支持拖放操作

        然后在Notify函数的消息里写入下面的代码来注册拖放窗体:

<span style="font-size:14px;">	m_DropTarget.DragDropRegister(m_hWnd);
	m_DropTarget.SetHDropCallBack(OnDropFiles);</span>

          这里需要写一个回调函数,来通知主窗体文件被拖动,回调函数的圆形如下,其中CFrameWnd为你的窗体类:

      typedef void (*DROPCALLBACK)(CFrameWnd*, HDROP);
          回调函数的具体写法和WM_DROPFILES消息处理的方法类似,需要把回调函数声明为窗体类的友元。这样就增加了拖动功能。CDropFileEx类的代码如下:

    

#ifndef DROP_TARGET_EX_H
#define DROP_TARGET_EX_H

#include "OleIdl.h"
#include "ShObjIdl.h"

typedef struct _DRAGDATA
{
	int cfFormat;
	STGMEDIUM stgMedium;

}DRAGDATA,*LPDRAGDATA;

typedef void (*DROPCALLBACK)(CFrameWnd*, HDROP);

class CDropTargetEx : public IDropTarget
{
public:
	CDropTargetEx(CFrameWnd *pMainWnd);
	virtual ~CDropTargetEx();

	BOOL DragDropRegister(HWND hWnd,DWORD AcceptKeyState = MK_LBUTTON);

	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject);

	ULONG STDMETHODCALLTYPE AddRef(void);

	ULONG STDMETHODCALLTYPE Release(void);

	HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState,POINTL pt,	DWORD *pdwEffect);

	HRESULT STDMETHODCALLTYPE DragEnter(IDataObject * pDataObject,DWORD grfKeyState, POINTL pt,	DWORD * pdwEffect);

	HRESULT STDMETHODCALLTYPE DragLeave(void);

	HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj,DWORD grfKeyState,	POINTL pt,	DWORD __RPC_FAR *pdwEffect);

	BOOL GetDragData(IDataObject *pDataObject,FORMATETC cFmt);

	void SetHDropCallBack(DROPCALLBACK pFun);

	void ProcessDrop(LPDRAGDATA pDropData/*HDROP hDrop*/);

	//枚举数据格式的函数,我这里并没有用到,但是将来可能会用,函数目前只枚举了HDROP类型
	BOOL EnumDragData(IDataObject *pDataObject);

private:
	CFrameWnd *m_pMainWnd;
	CDuiRect	m_rcList;

	ULONG	tb_RefCount;
	HWND	m_hTargetWnd;
	DWORD	m_AcceptKeyState;

	bool	m_bUseDnDHelper;
	IDropTargetHelper* m_piDropHelper;

	DROPCALLBACK	m_pDropCallBack;
	vector<LPDRAGDATA> m_Array;
};
#endif	//DROP_TARGET_EX_H

#include "duilib.h"

CDropTargetEx::CDropTargetEx(CFrameWnd *pMainWnd):
	m_pMainWnd(pMainWnd),
	tb_RefCount(0),
	m_hTargetWnd(0),
	m_AcceptKeyState(0),
	m_piDropHelper(NULL),
	m_bUseDnDHelper(false),
	m_pDropCallBack(NULL)
{
	// Create an instance of the shell DnD helper object.
	if ( SUCCEEDED( CoCreateInstance ( CLSID_DragDropHelper, NULL, 
		CLSCTX_INPROC_SERVER,
		IID_IDropTargetHelper, 
		(void**) &m_piDropHelper ) ))
	{
		m_bUseDnDHelper = true;
	}
}

CDropTargetEx::~CDropTargetEx()
{
	if ( NULL != m_piDropHelper )
		m_piDropHelper->Release();
}

BOOL CDropTargetEx::DragDropRegister(HWND hWnd,	DWORD AcceptKeyState)
{
	if(!IsWindow(hWnd))return false;
	HRESULT s = ::RegisterDragDrop (hWnd,this);
	if(SUCCEEDED(s))
	{ 
		m_hTargetWnd = hWnd;
		m_AcceptKeyState = AcceptKeyState; 
		if (m_pMainWnd->GetLeftListPos(m_rcList))
			return true;
		return false;
	}
	else 
	{ 
		return false; 
	}

}

HRESULT STDMETHODCALLTYPE CDropTargetEx::QueryInterface(REFIID iid, void ** ppvObject)
{
	*ppvObject = NULL;

	if (iid == IID_IDropTarget)
		*ppvObject = static_cast<IDropTarget*>(this);

	if( *ppvObject != NULL )
		AddRef();
	return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
}


ULONG STDMETHODCALLTYPE CDropTargetEx::AddRef(void)
{
	InterlockedIncrement(&tb_RefCount); 
	return tb_RefCount;
}

ULONG STDMETHODCALLTYPE CDropTargetEx::Release(void)
{
	ULONG ulRefCount = InterlockedDecrement(&tb_RefCount);
	return ulRefCount; 
}

HRESULT STDMETHODCALLTYPE CDropTargetEx::DragOver(DWORD grfKeyState,POINTL pt,	DWORD *pdwEffect)
{
	ScreenToClient(m_hTargetWnd, (LPPOINT)&pt);
	if( grfKeyState != m_AcceptKeyState || pt.x < m_rcList.left || pt.x > m_rcList.right || pt.y < m_rcList.top || pt.y > m_rcList.bottom)
	{
		*pdwEffect = DROPEFFECT_NONE;	
	}
	else
	{
		*pdwEffect = DROPEFFECT_COPY ;
	}
	if ( m_bUseDnDHelper )
	{
		m_piDropHelper->DragOver((LPPOINT)&pt, *pdwEffect);
	}
	
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CDropTargetEx::DragEnter(IDataObject * pDataObject,DWORD grfKeyState, POINTL pt,	DWORD * pdwEffect)
{
	if( grfKeyState != m_AcceptKeyState )
	{
		*pdwEffect = DROPEFFECT_NONE;
		return S_OK;
	}
	//我这里只关心CE_HDROP类型,如果需要,可以调用EnumDragData函数来枚举所有类型
	FORMATETC cFmt = {(CLIPFORMAT) CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
	GetDragData(pDataObject, cFmt);

	*pdwEffect = DROPEFFECT_COPY;

	if ( m_bUseDnDHelper )
	{
		m_piDropHelper->DragEnter ( m_hTargetWnd, pDataObject, (LPPOINT)&pt, *pdwEffect );
	}
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CDropTargetEx::DragLeave(void)
{
	int temp = m_Array.size();
	for(UINT i = 0;i < m_Array.size(); i++)
	{
		LPDRAGDATA pData = m_Array[i];
		::ReleaseStgMedium(&pData->stgMedium);
		delete pData;
		m_Array.clear();
	}

	if ( m_bUseDnDHelper )
	{
		m_piDropHelper->DragLeave();
	}

	return S_OK;
}

HRESULT STDMETHODCALLTYPE CDropTargetEx::Drop(IDataObject *pDataObj,DWORD grfKeyState,	POINTL pt,	DWORD __RPC_FAR *pdwEffect)
{

	int temp = m_Array.size();
	for(UINT i = 0; i < m_Array.size(); i++)
	{
		LPDRAGDATA pData = m_Array[i];

		//我这里只获取了HDROP类型的数据,所以直接开始处理
		ProcessDrop(pData);
		::ReleaseStgMedium(&pData->stgMedium);
		delete pData;
		m_Array.clear();
	}
	
	if ( m_bUseDnDHelper )
	{
		m_piDropHelper->Drop ( pDataObj,  (LPPOINT)&pt, *pdwEffect );
	}

	return S_OK;
}

BOOL CDropTargetEx::EnumDragData(IDataObject *pDataObject)
{	
	IEnumFORMATETC *pEnumFmt = NULL;

	//如果获取成功,则可以通过IEnumFORMATETC接口的Next方法,来枚举所有的数据格式:
	HRESULT ret = pDataObject->EnumFormatEtc (DATADIR_GET,&pEnumFmt);
		pEnumFmt->Reset ();

	HRESULT Ret = S_OK;
	FORMATETC cFmt = {0};
	ULONG Fetched = 0;

	while(Ret != S_OK)
	{
		Ret = pEnumFmt->Next(1,&cFmt,&Fetched);

		if(SUCCEEDED(ret))
		{
			if(  cFmt.cfFormat == CF_HDROP)
			{
				if(GetDragData(pDataObject,cFmt))
					return TRUE;
			}
		}
		else
		{
			return FALSE;
		}
	}
	return TRUE;
}

BOOL CDropTargetEx::GetDragData(IDataObject *pDataObject,FORMATETC cFmt)
{
	HRESULT ret=S_OK;
	STGMEDIUM stgMedium;

	ret = pDataObject->GetData(&cFmt, &stgMedium);

	if (FAILED(ret))
		return FALSE;

	if (stgMedium.pUnkForRelease != NULL)
		return FALSE;


	switch (stgMedium.tymed)
	{
	//可以扩充这块代码,配合EnumDragData函数来保存更多类型的数据
	case TYMED_HGLOBAL:
		{

			LPDRAGDATA pData = new DRAGDATA;

			pData->cfFormat = cFmt.cfFormat ;
			memcpy(&pData->stgMedium,&stgMedium,sizeof(STGMEDIUM));

			m_Array.push_back(pData);

			return true;
			break;

		}
	default:
		// type not supported, so return error
		{
			::ReleaseStgMedium(&stgMedium);
		}
		break;
	}

	return false;

}

void CDropTargetEx::SetHDropCallBack(DROPCALLBACK pFun)
{
	if (pFun != NULL)
	{
		m_pDropCallBack = pFun;
	}
}

void CDropTargetEx::ProcessDrop(LPDRAGDATA pDropData/*HDROP hDrop*/)
{
	switch(pDropData->cfFormat)
	{
	case CF_TEXT:
		{
			//m_pTextCallBack((HDROP)pDropData->stgMedium.hGlobal);
			break;
		}
	case CF_HDROP:
		{
			m_pDropCallBack(m_pMainWnd, (HDROP)pDropData->stgMedium.hGlobal);
			break;
		}
	default:
		break;
	}

}

总结:

        CDropTargetEx类的下载地址为:点击打开链接

        目前只是根据我的需求编写 CDropTargetEx类,实际上还可以扩充来完成更多功能。


    Redrain   2014.9.9

原文地址:https://www.cnblogs.com/redrainblog/p/3963341.html