南京市随园8-2号702室
王珂
---- 一、概述
---- 在Windows操作系统中存在两种剪贴板机制:Windows标准剪贴板和OLE剪贴板机制。
---- 标准的Windows剪贴板是一个被所有Windows应用程序共享的系统服务,因此它并没有自己的句柄或类。但你可以通过CWnd类的成员函数来管理剪贴板。
---- 自从OLE(Object Linking and Embedding,对象链接和嵌入)诞生之后,Windows操作系统中便出现了第二种剪贴板机制——OLE剪贴板机制。标准的Windows剪贴板API(Application Programming Interface,应用程序编程接口)依然可用,但是他已经被OLE数据传输机制来实现了。OLE支持UDT(Uniform Data Transfer,统一数据传输),并可以通过拖放操作实现剪贴板的剪切、复制和粘贴等操作。OLE剪贴板除了拥有标准Windows剪贴板的性能外,还支持传输用户自定义的剪贴板格式,并能够在传输数据时绑定OLE格式(如字体、字号等)。OLE剪贴板机制将成为更为主要的数据传输机制。
---- 本文将简要叙述标准Windows剪贴板的实现,并将重点放在讨论如何通过Visual C++实现OLE剪贴板上。
---- 二、选择适当的剪贴板机制
---- 在选择使用何种剪贴板机制时通常应遵循下面的原则:
---- 如果应用程序在将来又可能具有新的性能(比如现在只需要传输纯文本,但将来有可能需要另外传输字体等特性),那么使用OLE剪贴板。
---- 如果你正在使用一个OLE应用程序,或者你希望使用任何OLE特性(如拖放等)那么你应当使用OLE剪贴板机制。
---- 如果你提供了OLE格式(如字体、字号等),那么使用OLE剪贴板机制。
---- 三、使用Windows标准剪贴板
---- 大多数Windows下的应用程序支持剪切或复制数据到Windows剪贴板中以及从剪贴板粘贴数据至目的地。在这个过程中,剪贴板数据格式在多种应用程序之间发生了变化。系统构架仅仅通过实现一些有限的类来支持一些有限的剪贴板格式,下表列出了Windows标准剪贴板支持的格式(第一列“值”将在下面的程序代码中使用)。
值 含义 CF_BITMAP 一个对应于位图的句柄(HBITMAP)。 CF_DIB 一个包含BITMAPINFO结构并且 跟着位图数据的内存对象。 CF_DIF 数据交互格式 CF_DSPBITMAP 有一个私有格式的位图显示格式。 CF_DSPENHMETAFILE 有一个私有格式的增强的元文件的显示格式。 CF_DSPMETAFILEPICT 有一个私有格式的元图显示格式。 CF_DSPTEXT 有一个私有格式的文本显示格式。 CF_ENHMETAFILE 一个增强的元文件(HENHMETAFILE结构)的句柄。 CF_GDIOBJFIRST到 CF_GDIOBJLAST 应用软件定义 的一系列GDI对象的整型值。 CF_HDROP 一个HDROP类型的句柄,用来标识一列文件。 CF_METAFILEPICT 一个使用METAFILEPICT结构定义的元图文件的句柄。 CF_OEMTEXT 预定义的字符的文本格式,每一行都绑定 一个CR-LF字符,并且用一个空字符表示数据结尾。 CF_OWNERDISPLAY 剪贴板拥有者的显示格式, 剪贴板的拥有者必须显示并且更新剪贴板的观察器窗口, 并且接收WM_ASKCBFORMATNAME、WM_HSCROLLCLIPBOARD、 WM_PAINTCLIPBOARD、WM_SIZECLIPBOARD以及 WM_VSCROLLCLIPBOARD等消息,hMem参数必需为NULL。 CF_PALETTE 调色板的句柄 CF_PRIVATEFIRST到CF_PRIVATELAST 私有的剪贴板格式的整型值。 CF_RIFF 能够提交比CF_WAVE标准波表文件格式更为复杂的音频数据 CF_SYLK 微软公司的SYLK(Symbolic Link,符号链接)格式 CF_TEXT 文本格式 CF_WAVE 使用一种标准波表文件格式如11kHz或22kHz等 PCM(Pulse Code Modulation,脉冲编码调制器)提交音频数据。 CF_TIFF TIFF图形格式 CF_UNICODETEXT Unicode文本格式( 注意:仅适用于Windows NT或Windows 2000操作系统)---- 表1 常用的标准剪贴板格式
---- 要编写一个实现剪切和复制命令的函数,就要在你的应用程序中实现选定操作;要编写一个实现粘贴命令的函数,就需要请求剪贴板来检测它是否包含你的应用程序能够支持的数据。下面的代码实现了复制命令,其它实现可仿照进行,在此不再赘言。
---- 程序示例:
void CMyView::OnEditCopy() { if ( !OpenClipboard() ) { AfxMessageBox( "无法打开剪贴板" ); return; } // 删除目前剪贴板的内容 if( !EmptyClipboard() ) { AfxMessageBox( "无法清除剪贴板" ); return; } // 获取选定的数据 // 检查是否为剪贴板支持的格式 if ( ::SetClipboardData( CF_??, hData ) == NULL ) // CF_??指定了剪贴板中数据的格式, //表1列出了标准的剪贴板格式 { AfxMessageBox( "无法将数据复制到剪贴板当中" ); CloseClipboard(); return; } // ... CloseClipboard(); }---- 四、使用OLE剪贴板机制
---- 首先举个例子给你一些关于OLE剪贴板的感性认识,同时说明你需要为OLE剪贴板做哪些事情:Microsoft Excel为工作表注册了一个自定义的格式,这个格式能够比其它标准格式(如位图或纯文本等)提供更多的信息。当此数据被粘贴到一个支持工作表的程序(比如Lotus 1-2-3)时,所有的原工作表中的公式和数值将被保留,并且还可能会根据需要被更新。Excel同样将数据以OLE格式存放在剪贴板中,这样它就可以作为一个OLE对象被嵌入。任何OLE文档包容器(Container)(比如Microsoft Word)能够将该数据作为嵌入对象粘贴进文档(比如通过“选择性粘贴”,可以在Word中粘贴进Excel工作表对象)。这个嵌入对象能够通过激活Microsoft Excel来进行修改(在Word中可以通过双击对象实现)。该工作表甚至可以被粘贴到一个绘图程序(比如的画笔)。当然,这时你无论如何都没有办法将其中的数据像在工作表中一样修改,因为它已经是图片了。
---- 从上例总结一下,我们应当作的事情大致有:注册自定义的格式、传输格式到剪贴板上以及实现复制、剪切和粘贴。
---- 注册自定义格式
---- OLE剪贴板中的数据存在于多种格式。当一个用户选择从剪贴板粘贴数据时,应用程序应当能够选择使用何种格式粘贴数据。应用程序应当提供大部分格式的信息,除非用户指定使用某一种特定格式粘贴(比如只粘贴文字或只粘贴图片等)。
---- Windows定义了很多能够通过剪贴板传输的标准格式(见表1),OLE也定义了很多特殊的格式。应用程序可以通过获取更加详细的信息来注册他们自己的剪贴板格式。这可以通过使用Win32 API函数RegisterClipboardFormat来实现:
---- RegisterClipboardFormat ( lpszFormat );
---- 说明 lpzxFormat是指向一个字符串的指针,用以命名自定义的格式。该函数返回无符号整数,该数即为格式的ID号
---- 在注册了自定义的格式之后,便可以使用RegisterClipboardFormat函数的返回值来标识并使用该格式。
---- 将格式传输到剪贴板上
---- 要增加更多的格式到剪贴板上,你必须从COleClientItem或COleServerItem继承一个类,并且在该类中重载OnGetClipboardData函数。在这个函数中,你应当做按照下列步骤完成。
---- 将更多的格式放置在剪贴板上
---- 1. 建立一个COleDataSource对象。
---- 2. 传递该数据源到一个函数,用该函数通过访问COleDataSource::CacheGlobalData函数来将你的数据格式添加到支持的格式列表。
---- 3. 通过访问COleDataSource::CacheGlobalData,为每一个你向支持的格式添加标准格式。
---- 程序示例:
COleDataSource* CMyItem::OnGetClipboardData( BOOL bIncludeLink,LPPOINT pptOffset, LPSIZE pSize) { ASSERT_VALID(this); if (m_pServerNode == NULL) return NULL; COleDataSource* pDataSource = new COleDataSource; TRY { GetNativeClipboardData(pDataSource); GetClipboardData(pDataSource, bIncludeLink, pptOffset, pSize); } CATCH_ALL(e) { delete pDataSource; THROW_LAST(); } END_CATCH_ALL ASSERT_VALID(pDataSource); return pDataSource; }---- 复制、剪切和粘贴数据
---- 将数据复制或剪切到剪贴板上
---- 1. 确定将要被复制的数据是一个本地数据还是一个嵌入对象或链接。
---- 如果数据是一个嵌入对象或链接,创建一个指向被选定数据的COleClientItem指针。
---- 如果数据是本地化的并且应用程序是一个服务器,那么从COleServerItem继承一个新的类,并创建该对象。否则,为数据建立一个COleDataSource对象。
---- 2. 访问选定对象的CopyToClipboard成员函数。
---- 3. 如果用户选择剪切命令而不是复制,那么从你的应用程序中删除那些数据。
---- 程序示例:
void CMainView::OnEditCut() { ASSERT(m_pSelection != NULL); TRY { m_pSelection- >CopyToClipboard(TRUE); OnEditClear(); } CATCH_ALL(e) { AfxMessageBox(IDP_CLIPBOARD_CUT_FAILED); } END_CATCH_ALL } void CMainView::OnEditCopy() { ASSERT(m_pSelection != NULL); TRY { m_pSelection- >CopyToClipboard(TRUE); } CATCH_ALL(e) { AfxMessageBox(IDP_CLIPBOARD_COPY_FAILED); } END_CATCH_ALL }---- 从剪贴板粘贴数据
---- 粘贴数据比复制更加复杂,因为你需要选择粘贴的格式。
---- 1. 在你的视中,实现OnEditPaste来处理用户从编辑菜单选择粘贴命令的操作。
---- 2. 在OnEditPaste函数中,建立一个COleDataObject对象并且访问它的AttachClipboard成员函数来将这个对象绑定到剪贴板。
---- 3. 访问COleDataObject::IsDataAvailable函数来检查是否可以使用特殊的格式。当然,你也可以通过循环使用COleDataObject::BeginEnumFormats来寻找其它格式直到你找到了最适合的格式。
---- 4. 粘贴数据。
---- 程序示例:
CRectItem* CMainView::DoPasteItem(BOOL bLink, COleDataObject* pDataObject,CPoint* pPoint, CLIPFORMAT cfFormat) { BeginWaitCursor(); CRectItem* pItem = GetDocument()- >CreateItem(); ASSERT_VALID(pItem); BOOL bAllowAdjust = (pPoint == NULL) ? TRUE : FALSE; COleDataObject clipboardData; if (pDataObject == NULL) { clipboardData.AttachClipboard(); pDataObject = &clipboardData; } TRY { if (cfFormat == CMainDoc::m_cfPrivate) { DoPasteNative(pDataObject, pPoint, pItem); } else if (!bLink && cfFormat == 0 && pDataObject- >IsDataAvailable(CMainDoc::m_cfPrivate)) { DoPasteNative(pDataObject, pPoint, pItem); } else if (bAllowAdjust) { CPoint ptDef(10, -10); DoPasteStandard(bLink, pDataObject, &ptDef, pItem, cfFormat); } else { DoPasteStandard(bLink, pDataObject, pPoint, pItem, cfFormat); } if (bAllowAdjust) { GetDocument()- >AdjustItemPosition(pItem); } } CATCH_ALL(e) { TRACE0("failed to embed/link an OLE object\n"); pItem- >Delete(); pItem = NULL; } END_CATCH_ALL SetSelection(pItem, TRUE); GetDocument()- >SetModifiedFlag(); GetDocument()- >UpdateAllViews(NULL, 0, pItem); EndWaitCursor(); return pItem; } void CMainView::OnEditPaste() { COleDataObject clipboardData; clipboardData.AttachClipboard(); DoPasteItem(&clipboardData); UpdateAllViews(); }
---- 说明 将粘贴操作(如OnEditPaste函数)与实现粘贴的函数(如DoPasteItem)分开的最大优点在于,当数据被拖放到你的应用程序中时,可以使用同样的粘贴代码。比如你可以在OnDrop函数中访问DoPasteItem函数来重用代码。另外,程序代码中的DoPasteNative和DoPasteStandard函数仅仅说明一个概念,因此不再实现。