剪贴板和OLE拖放[转]

剪贴板和OLE拖放
2009-03-09 12:31
一、传统剪贴板
     传统剪贴板的形式非常的简单,其基本思路是当复制时程序复制一个数据副本给全局内存对象,打开剪贴板并且清空剪贴板当中的数据,将全局内存对象复制给剪贴板最后关闭剪贴板;从剪贴板中获取数据的顺序是打开剪贴板获取全局内存对象,锁定全局内存对象后从中复制数据,解锁内存对象并且关闭剪贴板。需要注意的是剪贴板在某个时刻只能被一个程序所打开,如果试图打开一个已经被其他程序打开的剪贴板就会导致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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值