ClipSpy
1999.12.27 [beginner] 作者:Michael Dunn
翻译:陈贵敏(efoxxx)
声明:此译文仅供网友们学习之用!您可以随意转载此译文,希望您转载时保留作译者和此声明。如有翻译不当之处,敬请指正:mailto:efoxxx@263.net。近期,我会在《剪贴板大观园》系列中补充大量新的文章,希望与您交流,共同提高!
描述:一个观察剪贴板和即拖即存数据的工具。 要求平台:VC6; Win 98/2000, Unicode
图1:ClipSpy展示了Word 97中简单的copy操作后被贴到剪贴板上的数据
图2:ClipSpy展示了Word中即拖即存所生成的数据
简介
最近有些空闲时间,我一直在思考Windows编程中一个我一直想知道却不知道的领域,也就是剪贴板。
我已经自己写了一个处理剪贴板上文件拖放(HDROP) 数据的工具,用它来仿真Explorer(资源管理器)的拷贝和粘贴命令,可是呢,仅仅能拷贝粘贴一种数据格式是远远不够的。我想更深入地考察一下剪贴板上的内容,通过这个过程说不准儿我会找到一些关于应用程序中使用剪贴板的一些技巧性的东西。(举个例子,什么是“Woozle”?――见上边图2左边的列表框。)
好啦,这一段空闲时间已经到了最后的周末了(我已把圣诞节要买的东西都买好了,真是太妙了),而且这个阶段的自学的最终结局是“ClipSpy”,一个马力增强了的剪贴板观察器。 另外,它还可以观察到即拖即存操作中的数据(因为MFC使用同一个类来处理即拖即存和剪贴板),不过,我还是叫它ClipSpy ,因为ClipboardAndDragAndDropSpy敲起来确实有点儿费劲儿。
如果您急切地想知道更多有关clipboard的知识,请直接跳到“实现细节”部分,这里我会专门指出代码中的值得一看的地方。
ClipSpy是用MSVC 6, SP2所写。我分别在 Windows 98 和 Windows 2000 build 2072(both ANSI and Unicode builds )作了测试。
ClipSpy 做了些什么
ClipSpy将自己注册为一个剪贴板观察器,这一部分其实没有什么神秘的。 可就在剪贴板上的内容发生变化的同时,ClipSpy 枚举出所有在剪贴板上的数据格式(剪贴板上的数据格式可能有很多种)并且将它们显示在左侧的面板中。 在格式列表中选择一种格式并单击鼠标左键,就会在右侧的面板中显示放在剪贴板上的数据。右侧面板的功能有点儿象MSVC的内存debug窗口,它不仅有ASCII代码,还有原始数据。
左侧的面板亦可做为拖放操作的目标来用,也就是说你可以从一个支持拖放的程序中把任何格式的数据拖到此列表中来,ClipSpy会显示出全部拖过来的数据。跟前边说一样,各种数据格式列于左侧的面板中,单击任一种格式的数据ClipSpy都会将相应的数据显示在右边的面板中。
File菜单下也有一对相应的菜单命令。“Read Clipboard”命令为重新读取剪贴板上的内容,你可以在执行了拖放操作之后使用此命令来再显示以下剪贴板上的内容。 “Empty Clipboard”命令会让你的剪贴板空空如也。
实现细节
ClipSpy的大多数工作都是通过MFC类COleDataObject完成的,此COleDataObject实现了接口IdataObject,并且提供了访问剪贴板上的数据以及拖放的数据源的方法。成员函数CLeftView::ReadDataAndFillList()是用来枚举剪贴板上的数据格式并将其写到列表(list)控件中。枚举代码如下所示:
BOOL CLeftView::ReadDataAndFillList ( COleDataObject* pDO )
{
FORMATETC etc;
UINT uNumFormats = 0; //.剪贴板上数据格式种类数
pDO->BeginEnumFormats();
while ( pDO->GetNextFormat ( &etc ))
{
if ( pDO->IsDataAvailable ( etc.cfFormat ))
{
uNumFormats++;
}
}
}
对于每一种格式,ClipSpy首先检查它是否是内建类型。如果不是的话,ClipSpy就会取得此格式的标记字符串,并调用函数GetClipboardFormatName():
TCHAR szFormat [256];
GetClipboardFormatName ( etc.cfFormat, szFormat, 256 );
ClipSpy有一用于内建类型的字符串列表(因为GetClipboardFormatName()函数只识别程序已注册过的基本类型)。然后,ClipSpy开始试着去读取此格式对应的数据:
HGLOBAL hgData;
UINT uDataSize;
CClipSpyDoc* pDoc = GetDocument();
// Get an HGLOBAL of the data.
hgData = pDO->GetGlobalData ( etc.cfFormat );
if ( NULL != hgData )
{
uDataSize = GlobalSize ( hgData );
pDoc->AddDataBlock ( hgData, uDataSize );
}
else
{
pDoc->AddEmptyBlock();
}
函数AddDataBlock()和AddEmptyBlock()都是文档类的成员函数。文档类则是存放所有数据的拷贝的场所。
当把数据拖到列表空间中时,也会执行一样的代码――成员函数CLeftView::OnDrop()中有一COleDataObject 对象,在调用ReadDataAndFillList()时直接将此对象作为参数传递过来。
其它你可能感兴趣的地方有CLeftView::OnItemchanged() 和 CClipSpyView::OnUpdate()。CLeftView::OnItemchanged()用于监测列表,当你更换了选中的Format项时,会触发此事件,并调用UpdateAllViews(),而UpdateAllViews()会依次调用OnUpdate()。OnUpdate()用于将数据写到RechEdit控件中去。
本程序存在的一些问题
在拖放操作之后,函数COleDataObject::GetGlobalData()有时会发生读取某些数据失败。由于在拖放这一技术上我也只是个新手,我还没有找到其原因(或者说是其解决方案)。呵呵,现在如果这一可怕的情况发生的话,ClipSpy只会列出其数据格式,而在“Data size”一栏中提示“<Data unavailable>”。
注意,程序的右边面板上放的是一个RichEdit控件,你可以从它那里拷贝东西到剪贴板上。可我必须告诉你,你不要对它进行拷贝,这样做会导致OLE内部冲突。再次声明:这个问题可能是由于我的代码自身的错误,您就不要用此功能好了。:)
由于剪贴板数据所占内存最初是由GlobalAlloc()函数分配的,所以每一块儿数据都比数据本身要长。(因为“字节对其(byte-alignment)”的原因,GlobalAlloc()会分配一块儿比你执行请求要大的内存)。比如说在Win98中,我用MSVC拷贝8个字符长的单词到剪贴板上,剪贴板上包含32字节的CF_TEXT(普通的ANSI文本)数据,其中前9个那个单词(8字节加上一个字符串结束符null),剩下的都是些乱糟糟的垃圾。ClipSpy把一整块儿的数据都显示出来,不作任何的解释处理。
对了,你一定要记住:如果你要debug这个程序――也就是说当你一步一步执行程序时,千万不要使用剪贴板。因为ClipSpy还没有完全运行起来,我可要祝贺你了,:)全部的剪贴板操作被挂起,ClipSpy、MSVC以及所有你曾在这一时间内使用剪贴板操作的程序都统统崩溃!妈呀,好像只有按Reset键重启爱机了。
请访问站点http://home.inreach.com/mdunn/code/,这里有相关的最新更新以及我的其它一些文章。