仿酷狗音樂播放器開發日誌二十七 用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

http://blog.csdn.net/leehong2005/article/details/8609478

      先來看一下原酷狗裏的文件拖動功能:


         可以看到,我拖動音樂文件到軟件裏,進去音樂列表的範圍內就顯示可複製的圖標,不在範圍則顯示不可拖拽的圖標。

         讓軟件支持文件拖拽有兩種方法: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


發佈了84 篇原創文章 · 獲贊 208 · 訪問量 61萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章