仿酷狗音乐播放器开发日志二十七 用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
仿酷狗音乐播放器开发日志二十七 用ole为窗体增加文件拖动功能(附源码)的更多相关文章
- 仿酷狗音乐播放器开发日志二十四 选项设置窗体的实现(附328行xml布局源码)
转载请说明原出处,谢谢~~ 花了两天时间把仿酷狗的选项设置窗体做出来了,当然了只是做了外观.现在开学了,写代码的时间减少,所以整个仿酷狗的工程开发速度减慢了.今天把仿酷狗的选项设置窗体的布局代码分享出 ...
- 仿酷狗音乐播放器开发日志二十六 duilib在标题栏弹出菜单的方法
转载请说明原出处,谢谢~~ 上篇日志说明了怎么让自定义控件响应右键消息.之后我给主窗体的标题栏增加右键响应,观察原酷狗后可以发现,在整个标题栏都是可以响应右键并弹出菜单的.应该的效果如下: 本以为像上 ...
- 仿酷狗音乐播放器开发日志二十五 duilib右键事件的不足的bug修复
转载请说明原出处,谢谢~~ 虽然仿酷狗的各个菜单早就写好了,但是一直没有附加到程序里.今天把菜单和播放列表控件关联时发现了问题. 和播放列表相关的菜单有三个,分别是每个音乐项目控件相关的菜单.分组的菜 ...
- 仿酷狗音乐播放器开发日志二十三 修复Option控件显示状态不全的bug(附源码)
转载请说明原出处,谢谢~~ 整个仿酷狗工程的开发将近尾声,现在还差选项设置窗体的部分,显然在设置窗体里用的最多的就是OptionUI控件,我在写好大致的布局后去测试效果,发现Option控件的显示效果 ...
- 仿酷狗音乐播放器开发日志十九——CTreeNodeUI的bug修复二(附源码)
转载请说明原出处,谢谢 今天本来打算把仿酷狗播放列表的子控件拖动插入功能做一下,但是仔细使用播放列表控件时发现了几个逻辑错误,由于我的播放 列表控件是基于CTreeViewUI和CTreeNodeUI ...
- 仿酷狗音乐播放器开发日志十一——CTreeNodeUI的bug修复
由于做播放列表控件,我的CMusicLength控件继承了CTreeVieWUI控件,在向分组控件中添加播放项目时,发现代码无法正常工作,调用CTreeNodeUI控件的Add方法后无反应,导致我的播 ...
- 仿酷狗音乐播放器开发日志三——修复CEditUI的bug2
无意中发现了CEditUI控件的另一个bug,当我给播放器的搜索栏获取焦点时,这时再改变窗体大小,原本搜索栏应该对应着也改变大小,却发现CEditUI内嵌的edit控件没有跟着改变(如下图),跟着调试 ...
- Redrain仿酷狗音乐播放器开发完毕,发布测试程序
转载请说明原出处,谢谢~~ 从暑假到现在中秋刚过,我用duilib开发仿酷狗播放器大概经历了50天.做仿酷狗的意图只是看原酷狗的界面比较漂亮,想做个完整一些的工程来练习一下duilib.今天把写好的程 ...
- 项目源码--Android类似酷狗音乐播放器
下载源码 知识技能概要: 1.音乐文件的扫描与管理 2.音频流的解码 3. UI控件的综合使用 4.播放列表方式管理 5.随机播放方式 6.源码带详细的中文注释 ...... 详细介绍 1. 音乐文件 ...
随机推荐
- Rockethon 2015
A Game题意:A,B各自拥有两堆石子,数目分别为n1, n2,每次至少取1个,最多分别取k1,k2个, A先取,最后谁会赢. 分析:显然每次取一个是最优的,n1 > n2时,先手赢. 代码: ...
- lintcode :Valid Palindrome 有效回文串
题目: 有效回文串 给定一个字符串,判断其是否为一个回文串.只包含字母和数字,忽略大小写. 样例 "A man, a plan, a canal: Panama" 是一个回文. & ...
- SpringMVC学习总结(六)——SpringMVC文件上传例子(2)
基本的SpringMVC的搭建在我的上一篇文章里已经写过了,这篇文章主要说明一下使用SpringMVC进行表单上的文件上传以及多个文件同时上传的不同方法 一.配置文件: SpringMVC 用的是 的 ...
- MyBatis,动态传入表名,字段名的解决办法
转载:http://luoyu-ds.iteye.com/blog/1517607 今天做项目,遇到的问题就是需求修改数据表的记录,而且字段名都不是固定的,也就是说是需要通过参数传入的, 本来这也不是 ...
- Android开发之EditText属性详解
1.EditText输入的文字为密码形式的设置 (1)通过.xml里设置: 把该EditText设为:android:password="true" // 以”.”形式显示文本 ( ...
- tokudb引擎安装-2
前言:因为现在tokuDB直接整合到Percona server里面了,下载页面直接跳转到下载Percona Server 页面了.安装方法跟以前不一样了,下面就来看一下新版本怎么安装了 ##准备阶段 ...
- 一些非常有用的html,css,javascript代码片段(持久更新)
1.判断设备是否联网 if (navigator.onLine) { //some code }else{ //others code } 2.获取url的指定参数 function getStrin ...
- mysql备份恢复数据库据/表
备份单个数据库,只备份表,如要恢复,必须先创建一个数据库[root@s]# mysqldump -u root -p dbname1 > dbname1.sql[root@s]# mysql - ...
- [Codeforces137C]History(排序,水题)
题目链接:http://codeforces.com/contest/137/problem/C 题意:给n对数,分别是一个事件的起始和终止时间.问被有几个事件被其他事件包含. 思路:先排序,按照起始 ...
- Android 面试题(经典)
1.Actvity的生命周期,生命周期中的onCreate与onResume有什么区别 Activity的生命周期有:onCreate,onStart,onRestart,onResume,onPau ...