一、传统剪贴板
传统剪贴板的形式非常的简单,其基本思路是当复制时程序复制一个数据副本给全局内存对象,打开剪贴板并且清空剪贴板当中的数据,将全局内存对象复制给剪贴 板最后关闭剪贴板;从剪贴板中获取数据的顺序是打开剪贴板获取全局内存对象,锁定全局内存对象后从中复制数据,解锁内存对象并且关闭剪贴板。需要注意的是 剪贴板在某个时刻只能被一个程序所打开,如果试图打开一个已经被其他程序打开的剪贴板就会导致API 函数调用返回NULL 值,由于这个机制要求所有程序在 打开剪贴板后在尽可能快的时间内关闭剪贴板。
下面是复制和粘贴的两个例程,这里把m_string 当中的文本复制到剪贴板:
// 复制数据
char m_string[] = "Hello,World";
if (::OpenClipboard(this->m_hWnd))
{
HANDLE hMem = ::GlobalAlloc(GHND,::lstrlen(m_string) + 1);
char *phandle = (char *)::GlobalLock(hMem);
lstrcpy(phandle,m_string);
::GlobalUnlock(hMem);
::EmptyClipboard();
::SetClipboardData(CF_TEXT,hMem);
::CloseClipboard();
}
// 粘贴数据
m_string[BUFFER];
if (::OpenClipboard(this->m_hWnd))
{
HANDLE hMem = ::GetClipboardData(CF_TEXT);
char *phandle = GlobalLock(hMem);
if (::lstrlen(phandle) < BUFFER)
::lstrcpy(m_string,phandle);
GloableUnlock(hMem);
::CloseClipboard();
}
除了使用系统定义的一系列数据类型ID 外我们还可以使用::RegisterClipboardFormat(LPCSTR) 函数来注册自定义的剪贴板数据ID ,这个API 函数保证只要参数提供的字符串内容相同,就会返回相同的UINT 类型的ID 值。
某些程序需要一个延迟型剪贴板,这种剪贴板的基本思想是:给剪贴板发送一个只包含数据类型ID 但不包括全局内存对象的SetClipboardData 调 用,直至有其他程序需要该剪贴板内容数据时再调用SetClipboardData 函数将数据传送给剪贴板。这种程序需要响应 WM_RENDERFORMAT 和WM_RENDERALLFORMAT 两个消息,在处理钱一个消息时我们不必打开剪贴板而只需要直接传送数据即可而后一 个消息需要打开剪贴板。
有时在我们打开剪贴板获取其中的内容之前我们需要知道剪贴板中是否有我们需要的数据,因此我们就需要调用::IsClipboardFomatAvaliable(UINT nID) API 函数,这个函数的参数是我们指定的数据类型ID 。另外我们可以调用
UINT EnumClipboardFormats(UINT nID) 函数来枚举所有的在剪贴板当中的数据类型。调用函数GetPriorityClipboardFormat 函数通过提供给参数的一个UINT 数组API 函数会从中选择一个优先的数据ID 予以返回。
关于传统剪贴板更详细的内容可以参考《WINDOWS 程序设计》。
二、OLE 类型的剪贴板
OLE 类型的剪贴板提供了比传统剪贴板更为强大的功能,但是其使用也较为复杂。在MFC 当中OLE 剪贴板被封装在COleDataSource 和 COleDataObject 两个类当中,这两个类抽象了剪贴板的数据提供者和剪贴板的数据使用者。在类的内部使用了COM 技术来构建。
1. 简单的OLE 剪贴板使用
简单的OLE 剪贴板也使用全局内存对象,在提供数据方面其使用步骤为:在堆上声明一个COleDataSource 对象,调用 CatchGlobalData 函数将全局内存对象句柄交给COleDataSource 对象,最后调用SetClipboard() 成员函数将数据交给 剪贴板;在获取数据方面其使用步骤为:在栈上声明一个COleDataObject 对象,调用AttachClipboard 成员函数将对象与剪贴板连接 起来,调用GetGlobalData 成员函数获取剪贴板中的数据,使用完全局内存对象后调用::GlobalFree 释放全局内存对象。
2. 使用其他媒体的OLE 剪贴板调用
OLE 剪贴板较传统剪贴板强大的一个方面体现在了OLE 剪贴板允许使用多种媒体来传递数据。在使用多种媒体传递数据时,最重要的是FORMATETC 和STGMEDIUM 两个数据结构。这两个数据结构的原型如下:
typedef struct tagFORMATETC
{
CLIPFORMAT cfFormat; // 剪贴板数据对象类型,如CF_TEXT 等
DVTARGETDEVICE *ptd; // 目标设备,一般设置为NULL
DWORD dwAspect; //DVASPECT_CONTENT
LONG lindex; //-1
DWORD tymed; // 媒体类型,如TYMED_HGLOBAL 、TYMED_FILE 等
}FORMATETC, *LPFORMATETC;
typedef struct tagSTGMEDIUM
{
DWORD tymed; // 等同于FORMATETC 当中的tymed 字段
[switch_type(DWORD), switch_is((DWORD) tymed)]
union { // 联合体当中储存了当前数据信息
[case(TYMED_GDI)] HBITMAP hBitmap;
[case(TYMED_MFPICT)] HMETAFILEPICT hMetaFilePict;
[case(TYMED_ENHMF)] HENHMETAFILE hEnhMetaFile;
[case(TYMED_HGLOBAL)] HGLOBAL hGlobal;
[case(TYMED_FILE)] LPWSTR lpszFileName;
[case(TYMED_ISTREAM)] IStream *pstm;
[case(TYMED_ISTORAGE)] IStorage *pstg;
[default] ;
};
[unique] IUnknown *pUnkForRelease;
}STGMEDIUM;
typedef STGMEDIUM *LPSTGMEDIUM;
在使用非全局内存对象传递剪贴板数据当中,最常用的是使用文件来传递数据,这样做的好处在于可以避免内存的过多浪费,其步骤如下:
// 提供数据
char string[] = "Hello World";
char TempPath[MAX_PATH],FileName[MAX_PATH];
::GetTempPath(MAX_PATH,TempPath); // 获取临时文件夹路径
::GetTempFileName(TempPath,"tmp",0,FileName); // 设置临时文件名
// 打开FileName 文件写入string
LPWSTR wszFileName = (LPWSTR)::CoTaskMemAlloc(MAX_PATH * sizeof(TCHAR));
#ifdef UNICODE // 由于STGMEDIUM 当中的lpszFileName 要求UNICODE 字符串,因此进行转换
::lstrcpy(wszFileName,TempFileName)
#else
::MulityByteToWideChar(CP_ACP,MB_PRECOMPOSED,szFileName,-1,wszFileName,MAX_PATH);
#endif
FORMATETC fe = {CF_TEXT,NULL,DVASCEPT_CONTENT,-1,TYMED_FILE};
STGMEDIUM stgm;
stgm.tymed = TYMED_FILE;
stgm.lpszFileName = wszFileName;
COleDataSource *ods = new COleDataSource;
ods->CatchData(CF_TEXT,&fe,&stgm);
ods->SetClipboardData();
// 获取数据的方法
STGMEDIUM stgm ;
FORMATETC fe = {CF_TEXT,NULL,DVASCEPT_CONTENT,-1,TYMED_FILE};
COleDataObject odo;
odo.AttachClipboard();
if (odo.GetData(CF_TEXT,&fe,&stgm) && stgm.tymed == TYMED_FILE)
{
TCHAR FileName[MAX_PATH];
#ifdef UNICODE
::lstrcpy(FileName,stgm.lpszFileName);
#else
::WideCharToMulityByte(CP_ACP,0,stgm.lpszFileName,-1,FileName,sizeof (FileName) / sizeof (TCHAR),NULL,NULL);
// 打开文件读取数据
ReleaseStgMedium(&stgm);
}
在上面例程当中,只能读取由TYMED_FILE 作为媒体的剪贴板数据,更通用的办法是调用COleDataObject::GetFile() 成员函数来获取一个CFile 指针,利用这个指针来读取数据。其例程如下:
FORMATETC fe = {CF_TEXT,NULL,DVASCEPT_CONTENT,-1,TYMED_FILE |TYMED_HGLOBAL};
STGMEDIUM stgm;
COleDataObject odo;
odo.AttachClipboard()
switch (odo.GetData(CF_TEXT,&fe,&odo))
{
case TYMED_FILE:
CFile *pfile = odo.GetFile();
// 读取数据
delete pfile;
case TYMED_HGLOBAL:
// 用简单OLE 剪贴板方式获取数据
}
在OLE 剪贴板中并没有象传统剪贴板一样对每中数据类型的ID 在剪贴板中只允许放入一个数据项,在OLE 剪贴板中只要在FORMATETC 当中的tymed 字段定义的媒体类型不同即使是相同数据类型ID 的数据项允许存在多个数据项。
OLE 剪贴板的数据类型存在检查通过调用COleDataObject::IsDataAvaliable 成员函数来实现,这个成员函数接受两个参数一个 指示数据类型ID 的CFFORMAT 值和一个指向FORMATETC 结构的指针,只要在OLE 剪贴板中存在参数指定的数据类型那么就会返回一个非零值。
二、OLE 拖放
在MFC 当中实现OLE 拖放需要使用两个类:COleDropSource 和COleDropTarget ,第一个类实现了拖放数据的提供,第二个类实现 了拖放数据的获取。在现实的使用过程当中我们并不直接使用COleDropSource 类来提供数据,作为替代我们使用COleDataSource 来提 供数据。COleDataSource 类的DoDragDrop 成员函数将会创建COleDropSource 类对象并且调用其中的成员函数来提供数据。 与OLE 剪贴板的提供数据不同,在OLE 拖放当中被其他函数或者类调用的对象不再是COleDataSource 而是COleDropSource ,因此 COleDataSource 不再需要在堆上创建而只需要在栈上创建即可。提供数据的一个简单例程如下:
char szText[] = "Hello world";
HANDLE handle = ::GlobalAlloc(GHND,lstrlen(szText) + 1);
char *phandle = ::GlobalLock(handle);
lstrcpy(phandle,szText);
::GlobalUnlock(handle);
COleDataSource ods;
ods.CatchDataGlobal(handle);
DROPEFFECT de = ods.DoDragDorp(DROPEFFECT_MOVE | DROPEFFECT_COPY); // 允许拖放移动和复制两种选项
if (de == DROPEFFECT_COPY)
// 向文档类当中加入新项
else
// 改变文档类当中被拖动项的数据
在允许OLE 拖动的目标程序的视图类当中我们需要加入一个COleDropTarget 保护型数据对象,在OnCreate 成员函数当中我们需要加入对 COleDropTarget::Register 成员函数的调用。然后我们需要处理OnDropEnter,OnDropOver,OnDrop 三个成 员函数的覆盖。这三个成员函数分别对应拖动对象进入窗口,拖动对象在窗口当中移动,拖动对象在窗口中被放下。对这三个成员函数的覆盖的例子如下:
DROPEFFECT CCheckedView::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
CScrollView::OnDragEnter(pDataObject, dwKeyState, point);
if (!pDataObject->IsDataAvailable(CF_TEXT))
return FALSE;
return ((dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE);
}
DROPEFFECT CCheckedView::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
CScrollView::OnDragOver(pDataObject, dwKeyState, point);
if (!pDataObject->IsDataAvailable(CF_TEXT))
return FALSE;
return ((dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE);
}
BOOL CCheckedView::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
CScrollView::OnDrop(pDataObject, dropEffect, point);
if (pDataObject->IsDataAvailable(CF_TEXT))
{
// 读取数据加入到文档中并且刷新视图
return TRUE;
}
return FALSE;
}