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

中秋到了,出去玩了几天。今天把仿酷狗程序做了收尾,已经开发完成了,下一篇博客把完结的情况说一下。在这篇博客里说一下使用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

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

  1. 仿酷狗音乐播放器开发日志二十四 选项设置窗体的实现(附328行xml布局源码)

    转载请说明原出处,谢谢~~ 花了两天时间把仿酷狗的选项设置窗体做出来了,当然了只是做了外观.现在开学了,写代码的时间减少,所以整个仿酷狗的工程开发速度减慢了.今天把仿酷狗的选项设置窗体的布局代码分享出 ...

  2. 仿酷狗音乐播放器开发日志二十六 duilib在标题栏弹出菜单的方法

    转载请说明原出处,谢谢~~ 上篇日志说明了怎么让自定义控件响应右键消息.之后我给主窗体的标题栏增加右键响应,观察原酷狗后可以发现,在整个标题栏都是可以响应右键并弹出菜单的.应该的效果如下: 本以为像上 ...

  3. 仿酷狗音乐播放器开发日志二十五 duilib右键事件的不足的bug修复

    转载请说明原出处,谢谢~~ 虽然仿酷狗的各个菜单早就写好了,但是一直没有附加到程序里.今天把菜单和播放列表控件关联时发现了问题. 和播放列表相关的菜单有三个,分别是每个音乐项目控件相关的菜单.分组的菜 ...

  4. 仿酷狗音乐播放器开发日志二十三 修复Option控件显示状态不全的bug(附源码)

    转载请说明原出处,谢谢~~ 整个仿酷狗工程的开发将近尾声,现在还差选项设置窗体的部分,显然在设置窗体里用的最多的就是OptionUI控件,我在写好大致的布局后去测试效果,发现Option控件的显示效果 ...

  5. 仿酷狗音乐播放器开发日志十九——CTreeNodeUI的bug修复二(附源码)

    转载请说明原出处,谢谢 今天本来打算把仿酷狗播放列表的子控件拖动插入功能做一下,但是仔细使用播放列表控件时发现了几个逻辑错误,由于我的播放 列表控件是基于CTreeViewUI和CTreeNodeUI ...

  6. 仿酷狗音乐播放器开发日志十一——CTreeNodeUI的bug修复

    由于做播放列表控件,我的CMusicLength控件继承了CTreeVieWUI控件,在向分组控件中添加播放项目时,发现代码无法正常工作,调用CTreeNodeUI控件的Add方法后无反应,导致我的播 ...

  7. 仿酷狗音乐播放器开发日志三——修复CEditUI的bug2

    无意中发现了CEditUI控件的另一个bug,当我给播放器的搜索栏获取焦点时,这时再改变窗体大小,原本搜索栏应该对应着也改变大小,却发现CEditUI内嵌的edit控件没有跟着改变(如下图),跟着调试 ...

  8. Redrain仿酷狗音乐播放器开发完毕,发布测试程序

    转载请说明原出处,谢谢~~ 从暑假到现在中秋刚过,我用duilib开发仿酷狗播放器大概经历了50天.做仿酷狗的意图只是看原酷狗的界面比较漂亮,想做个完整一些的工程来练习一下duilib.今天把写好的程 ...

  9. 项目源码--Android类似酷狗音乐播放器

    下载源码 知识技能概要: 1.音乐文件的扫描与管理 2.音频流的解码 3. UI控件的综合使用 4.播放列表方式管理 5.随机播放方式 6.源码带详细的中文注释 ...... 详细介绍 1. 音乐文件 ...

随机推荐

  1. node基础10:处理异常

    1.处理异常 当发生异常时,如果不作处理,那么服务器会奔溃.由于node的异步调用的特性,所以不但要考虑主程序的异常,还有处理异步调用的异常. 代码如下: /** * server.js */ var ...

  2. 11月8日下午Jquery取属性值(复选框、下拉列表、单选按钮)、做全选按钮、JSON存储、去空格

    1.jquery取复选框的值 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "htt ...

  3. oracle在impdp时报ORA-31655和ORA-39154

    检查表空间大小设置的是否合理. 另外可以试试 grant IMP_FULL_DATABASE to user;增加导入权限. (转)

  4. 笔记本电脑关闭小键盘(即打字按P出现星号键)

    开关方法:Fn + NumLk (联想电脑的NumLk 一般为F8,其他电脑自己在键盘找找罗)

  5. [Elixir009]像GenServer一样用behaviour来规范接口

    1.Behaviour介绍 Erlang/Elixir的Behaviour类似于其它语言中的接口(interfaces),本质就是在指定behaviours的模块中强制要求导出一些指定的函数,否则编译 ...

  6. 【poj3233】 Matrix Power Series

    http://poj.org/problem?id=3233 (题目链接) 题意 给出一个n×n的矩阵A,求模m下A+A2+A3+…+Ak 的值 Solution 今日考试就A了这一道题.. 当k为偶 ...

  7. JS不兼容减号,css属性转驼峰写法

    <script> function changeFormat(str) { return str.replace(/-(\w)/g, function(k, r) { return r.t ...

  8. 对于fmri的hrf血液动力学响应函数的一个很直观的解释-by 西南大学xulei教授

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% clear all;clc; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ...

  9. 一道program test题目

    前天去面试的一道上机测试题(凭记忆写的题目)Question:给定输入整数\(\left( k \right)\),找到最小的自然数\(n\left( {n \ge 1} \right)\),使得下列 ...

  10. Castle IOC容器内幕故事(下)

    主要内容 1.ComponentModelBuilder 和 Contributors 2.Contributors分析 3.Handles分析 4.ComponentActivator分析 一.Co ...