Figure 1: ClipSpy shows what Word 97 puts on the clipboard after a simple copy operation.
Figure 2: ClipSpy showing the data created by Word when dragging and dropping the same text.
Introduction
Recently, I had some free time and I was thinking about an area of Windows programming that I didn't know much about, but wanted to learn more about, namely the clipboard. I had written a utility for myself that dealt with HDROP data on the clipboard to simulate Explorer's Copy and Paste commands, but using just one format wasn't enough. I wanted to see deeper into the contents of the clipboard, and in the process maybe uncover some neat factoids about apps that use the clipboard. (For example, what's a "Woozle"? - See Figure 2 above.)
Well, the free time turned into a free weekend (lucky for me I had all my Christmas shopping done!), and the result of my self-teaching is ClipSpy, a souped-up clipboard viewer. It can also view the data in a drag-and-drop operation (since MFC uses the same class to handle drag-and-drop and the clipboard), but I chose the name ClipSpy since ClipboardAndDragAndDropSpy
is a bit harder to type.
If you're just itching to learn more about the clipboard yourself, check out the "Implementation details" section of the article, where I point out interesting points in the code.
What ClipSpy Does
ClipSpy registers itself as a clipboard viewer. Nothing magical there. But when the contents of the clipboard change, ClipSpy enumerates all the data formats on the clipboard (there may be many) and displays them all in the left-hand pane. Clicking on a format in the list shows the data that was placed on the clipboard in the right-hand pane. The right pane shows the raw data as well as the ASCII representation, similar to the memory debug window in MSVC.
The left-hand pane also registers as a drop target, so you can drag any data from a program that supports drag-and-drop, drop it over the list, and ClipSpy will show all the data that the drag source provided. Again, the formats are listed in the left-hand pane, and clicking a format shows the corresponding data in the right-hand pane.
The File menu also has a few commands. The Read Clipboard command re-reads the contents of the clipboard. Use this command if you have done a drag-and-drop and you want ClipSpy to redisplay the clipboard contents. The Empty Clipboard command clears out the clipboard. The Save Selected Data command is available when you are viewing a block of data, and saves that data to a file.
Implementation Details
ClipSpy does most of its work via the MFC class COleDataObject
that implements the IDataObject
interface and provides access to the data on the clipboard or drag source. The function CLeftView::ReadDataAndFillList()
handles enumerating the formats on the clipboard and filling the list control. The enumeration goes like this:
BOOL CLeftView::ReadDataAndFillList ( COleDataObject* pDO )
{
FORMATETC etc;
UINT uNumFormats = 0;
pDO->BeginEnumFormats();
while ( pDO->GetNextFormat ( &etc ))
{
if ( pDO->IsDataAvailable ( etc.cfFormat ))
{
uNumFormats++;
}
}
For each format, ClipSpy checks to see if it is a built-in type. If not, it gets the descriptive string
for the format by calling GetClipboardFormatName()
:
TCHAR szFormat [256];
GetClipboardFormatName ( etc.cfFormat, szFormat, 256 );
ClipSpy has a list of string
s used for the built-in types (since GetClipboardFormatName()
only works for custom types registered by programs). ClipSpy then tries to read the data for the format:
HGLOBAL hgData;
UINT uDataSize;
CClipSpyDoc* pDoc = GetDocument();
hgData = pDO->GetGlobalData ( etc.cfFormat );
if ( NULL != hgData )
{
uDataSize = GlobalSize ( hgData );
pDoc->AddDataBlock ( hgData, uDataSize );
}
else
{
pDoc->AddEmptyBlock();
}
The functions AddDataBlock()
and AddEmptyBlock()
are members of the document class, which stores copies of all the data.
This same code is used when data is dropped on the list control - the function CLeftView::OnDrop()
provides a COleDataObject
object that is passed directly to ReadDataAndFillList()
.
The other interesting pieces of code are CLeftView::OnItemchanged()
and CClipSpyView::OnUpdate()
. The first function detects when the selection in the list changes, and calls UpdateAllViews()
, which in turn calls OnUpdate()
. OnUpdate()
handles filling in the rich edit control with a dump of the data.
Quirks in the Program
The right-hand pane is a rich edit control, which supports copying to the clipboard. However, you should not copy from it, since doing so can lead to crashes inside OLE. Again, this is probably due to something I'm doing wrong, but in the meantime, just don't do it. :)
Since clipboard data is originally allocated with GlobalAlloc()
, each block of data may be longer than the data itself. (GlobalAlloc()
can allocate more memory than you request for performance and byte-alignment reasons.) For example, on Win 98, if I copy an 8-character word to the clipboard using MSVC, the clipboard contains 32 bytes of CF_TEXT
(plain ANSI text) data. The first 9 are the word (8 bytes plus a terminating null
), but the rest are random garbage. ClipSpy displays the entire block of data, and doesn't try to interpret the data in any way.
Something to keep in mind if you debug the program - if you are stepping through the code, do not try to use the clipboard. Since ClipSpy isn't running, the entire clipboard operation hangs and brings down ClipSpy, MSVC, and whatever program you did the clipboard operation in. Ouch! On Windows 9x, you might as well hit the reset button at that point.
Revision History
- December 20, 1999: Version 1.0, first release
- October 28, 2001: Version 1.2, added ability to save any block of data; added
IDropTargetHelper
support to CLeftView
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.