仿酷狗音乐播放器开发日志二十七 用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. 音乐文件 ...
随机推荐
- linux入门教程(二) 图形界面还是命令窗口
对于linux的应用,我想大多数都是用在服务器领域,对于服务器来讲真的没有必要跑一个图形界面.所以我们平时安装linux操作系统时往往是不安装图形界面的.说到这里也许你会有疑问,图形界面还能选择装或者 ...
- TopCoder 603 div1 & div2
div2 250pts MiddleCode 题意:s串长度为奇数时,将中间字符取掉并添加到t末尾:长度为偶数时,将中间两个较小的字符取掉并添加到末尾. 分析:直接做,学习了一下substr(s, p ...
- JAVA+ Proxool+ SQLserver 2008 “signer information does not match signer information of other classes in the same package”
1. Proxool+SQLserver2008(sqljdbc4.jar)集成问题最近在项目中遇到个问题:我用的是Proxool连接池,连接SQLserver2008数据库,控制台报错:签名信息和同 ...
- SQL索引一步到位(此文章为“数据库性能优化二:数据库表优化”附属文章之一)
SQL索引一步到位(此文章为“数据库性能优化二:数据库表优化”附属文章之一) SQL索引在数据库优化中占有一个非常大的比例, 一个好的索引的设计,可以让你的效率提高几十甚至几百倍,在这里将带你一步步揭 ...
- managedQuery和query的区别,
我们都知道在Android系统中,SQLite数据库的相关操作方式被封装为内容提供Content Provider,可以帮助那些不会SQL语言的开发者快速实现Android平台上的数据库操作,但是平时 ...
- Control Flow in Async Programs
Control Flow in Async Programs You can write and maintain asynchronous programs more easily by using ...
- linux下对符合条件的文件大小做汇总统计的简单命令
(1)统计当前目录下的 *txt 文件du -c -h *txt (2)统计当前目录下的 *txt 文件, 并求出总大小du *txt |awk 'BEGIN{count=0;size=0;} ...
- java开发之多线程需要学习和理解的东西
40个Java多线程问题总结 http://www.codeceo.com/article/40-java-thread-problems.html
- Qt之自定义界面(QMessageBox)
简述 通过前几节的自定义窗体的学习,我们可以很容易的写出一套属于自己风格的界面框架,通用于各种窗体,比如:QWidget.QDialog.QMainWindow. 大多数窗体的实现都是采用控件堆积来完 ...
- UVa 1647 (递推) Computer Transformation
题意: 有一个01串,每一步都会将所有的0变为10,将所有的1变为01,串最开始为1. 求第n步之后,00的个数 分析: 刚开始想的时候还是比较乱的,我还纠结了一下000中算是有1个00还是2个00 ...