With just a few lines of code, you can easily add Clipboard functionality to your application.
Introduction
In this first instalment of a four-part series of articles on programmatically transferring
data to and from the Windows Clipboard, I'll explain the basic steps of using the
Clipboard using the Clipboard API. After I've gone over the basics steps, which are
used no matter what data format is on the Clipboard, I'll present a demo application
that illustrates how to programmatically transfer simple (ANSI) text to and from the
Clipboard.
Using the Windows Clipboard API
There are actually two distinct mechanisms for interfacing to the Clipboard. The first
involves using the Windows Clipboard API and the second uses OLE. Since the Clipboard API
is by far the most common method used, most of the demos in this little series will use this
technique. If you're familiar with the Windows API - especially the means by which memory
is allocated via the GlobalAlloc and GlobalLock functions - the steps needed to use the
Clipboard will be all the easier to learn. Another thing to realize at this point is that
regardless of the type of data being transferred to the clipboard, the same basic programmatic
steps are taken any time you are transferring data to or from the Clipboard. Figure 1 shows
these steps in a standard UML Sequence Diagram with the subsequent sections going into more
detail about each step.
Figure 1: These are the standard steps used to transfer data to the Clipboard using the Windows Clipboard API.
Allocating Memory for Your Data
The verbiage "placing data on the Clipboard" is really a misnomer as the Clipboard is not
some sort of global data buffer. Actually, the Clipboard is little more than a handle to a
data buffer that is created and maintained by the application that is making this data available
for other applications. Since the data must be accessible from all processes, the Windows API
functions, GlobalAlloc and GlobalLock, are used.
As a quick refresher, here is the syntax for these two functions and how they work:
The GlobalAlloc function is used to allocate a global block of memory that will hold the entirety
of the data being made available via the Clipboard.
HGLOBAL GlobalAlloc(UINT uFlags, SIZE_T dwBytes)
The uFlags parameter is used to specify to Windows how the memory is to be allocated.
If you specify a value of 0 (or NULL), then a value of GMEM_FIXED is assumed. The valid
values for this parameter are in the Table 1 and can be combined with the logical or operator.
Table 1 - GlobalAlloc flags
|
uFlag Value
|
Description
|
GHND
|
Same as GMEM_MOVEABLE | GMEM_ZEROINT
|
GMEM_FIXED
|
The default value, this allocated a block of fixed (i.e., non-moveable) memory. The return value from GlobalAlloc
when specifying GMEM_FIXED is a pointer.
|
GMEM_MOVEABLE
|
In Windows, memory blocks are never moved in physical memory. However, the can be moved within the default heap. Therefore,
the return value when specifying GMEM_MOVEABLE is a handle to the memory.
|
GPTR
|
This value combines GMEM_FIXED and GMEM_ZEROINT, the later simply initializing the allocated memory to all zeroes.
|
Note - Since specifying a value of GMEM_FIXED for the uFlag parameter to the GlobalAlloc call will
return a pointer while specifying GMEM_MOVEABLE will return a handle, these two values are mutually exclusive.
The parameter dwBytes is a double word that enables you to specify how large a buffer you wish to allocate.
The second function that you need to call to allocate the memory for your data is the Windows API function, GlobalLock.
LPVOID GlobalLock(HGLOBAL hMem)
This function is very straight-forward and takes as its only parameter a handle returned from the GlobalAlloc function.
Copy the Data into the Global Memory
Once you've allocated the global memory and have obtained a pointer to it, you can copy the desired data
into that global memory buffer. How you do this will depend on what type of data you're transferring so I'll
go into more detail about this step in the three demos that follow this article.
Unlock the Global Memory
This is a very important, yet often overlooked step in the process. According to the Windows documentation
once you've allocated the memory and inserted the data to be made public to other processes, you are not to
touch this memory again. The reason is simple. Once you've made the Clipboard mechanism aware of the new data
(next step), Windows will now have control over that memory and any further tampering with it on your end might
very well invalidate the integrity of the data.
To unlock the global memory block, make a call to the GlobalUnlock API function. Here's the very simple
syntax for this function where the hMem parameter is the handle returned from the GlobalAlloc function.
BOOL GlobalUnlock(HGLOBAL hMem)
Open the Clipboard
The OpenClipboard function enables you to lock the Clipboard so that no other processes
can modify its contents as you are attempting to access that data. This function's syntax is as follows:
BOOL OpenClipboard(HWND hWndNewOwner)
The hWndNewOwner simply allows you to associated the open Clipboard with a given window. If you don't need to
do this (most don't), simply leave specify a value of NULL for this parameter, in which case, Windows will associate
the open Clipboard with the current task. All of the demos in this book will use this latter technique as I've
personally never seen too many situations where an association with a specific window was needed.
Empty the Clipboard
This step is needed to initialized the Clipboard and is accomplished via the EmptyClipboard function. When you call
this function, Windows releases the global memory associated with the Clipboard. If you'll remember, earlier I said
that Windows would be responsible for this task. Therefore, it is the responsibility of any application using the Clipboard
to call this function in order to prevent memory leaks.
BOOL EmptyClipboard()
Note that you do not need to call a function to determine if any data exists prior to calling this
function as this function will only return a nonzero value if the function fails. If it succeeds or if
the Clipboard doesn't contain any data, the function returns zero (representing success).
Set the Clipboard Data
Finally, at this step, you set the Clipboard data. As mentioned earlier, the Clipboard really doesn't
contain data, but simply maintains a global handle obtained (and populated) by your application. The function
to perform this step is the SetClipboardData function. This is also the function that enables you to
specify the format of the data being transferred (notice syntax below).
HANDLE SetClipboardData(UINT uFormat, HANDLE hMem)
The value you specify for the uFormat parameter must be either a standard Clipboard format (Table 2)
or a registered format. Registered formats are user-defined formats that enable you to specify formats for
application-specific data. We'll be getting into this format later on.
Table 2 - Standard Clipboard Formats
| uFormat value |
hMem value |
CF_BITMAP |
Handle to a bitmap (you'll see how to use this format in this article series' second demo) |
CF_DIB |
Handle to a BITMAPINFO structure followed by the bits that constitute the bitmap |
CF_DIBV5 |
Used in Windows 2000 only, this is also a handle to a BITMAPINFO structure where the subsequent bits
represent the bitmap color information and the bitmap image. |
CF_DIF |
Handle to a Software Arts' Data Interchange Format (SADIF) buffer |
CF_DSPBITMAP |
Handle to a bitmap display format associated with an application-specific format. The data being pointed to
must be data that can be displayed in bitmap format. |
CF_DSPENHMETAFILE |
Handle to an enhanced metafile display format associated with a private format. In this case, the data must
be displayable in enhanced metafile format in lieu of the privately formatted data. |
CF_DSPMETAFILEPICT |
Handle to a metafile-picture display format associated with a private format. The data being pointed to
must be displayable in metafile-picture format in lieu of the privately formatted data. |
CF_DSPTEXT |
Handle to text display format associated with a private format. The data must be displayable in text format
in lieu of the privately formatted data. |
CF_ENHMETAFILE |
Handle to an enhanced metafile (HENHMETAFILE) |
CF_GDIOBJFIRST through CF_GDIOBJLAST |
This range of integers represents application-defined GDI object clipboard formats. Note that the
handles represented by these values are not automatically deleted using the GlobalFree function
when the clipboard is emptied. Additionally, the hMem parameter is not a handle to a GDI object,
but is a handle allocated by the GlobalAlloc function with the GMEM_MOVEABLE flag. |
CF_HDROP |
Handle to type HDROP that identifies a list of files being transferred via the Clipboard -
typically used in drag & drop operations. The DragQueryFiles function is used to retrieve information
about these files. |
CF_LOCALE |
Handle to the locale identifier associated with the text in the clipboard |
CF_METAFILEPICT |
Handle to a metafile picture format (METAFILEPICT) structure |
CF_OEMTEXT |
Handle to a text format containing characters in the OEM character set. In this format, each line
must be terminated with a carriage return/linefeed (CR/LF) combination. A null character represents
end of data (EOD) using this format. |
CF_OWNERDISPLAY |
If an application specifies a uFormat value of CF_OWNERDISPLAY, the hMem value must be
NULL. In addition, the application is then responsible for displaying and updating the Clipboard
viewer window and handling the following messages: WM_ASKCBFORMATNAME, WM_HSCROLLCLIPBOARD,
WM_PAINTCLIPBOARD, WM_SIZECLIPBOARD, WM_VSCROLLCLIPBOARD. |
CF_PALETTE |
Handle to a color palette. |
CF_PENDATA |
Handle to data representing pen extensions for the Microsoft Windows for Pen Computing. |
CF_PRIVATEFIRST through CF_PRIVATELAST |
Much like using the CF_GDIOBJFIRST-CF_GDIOBJLAST range, these integers represent
a series of value for private Clipboard formats. Note once again that the data associated with these
handles is not freed automatically and as such it is the application's responsibility to do such. |
CF_RIFF |
Handle to an audio data format that is more complex than can be represented with the standard (WAV)
format where the uFormat parameter is set to CF_WAVE. |
CF_SYLK |
Handle to Microsoft Symbolic Link (SYLK) formatted data. |
CF_TEXT |
Handle to standard text. Using this format, each line must be terminated with a carriage return/linefeed
(CR-LF) combination. A null character denotes end of data (EOD). This is used for ANSI text whereas
CF_UNICODETEXT is used for UNICODE text. |
CF_WAVE |
Handle to the audio data in one of the standard wave formats: such as 11 kHz or 22 kHz pulse code modulation (PCM). |
CF_TIFF |
Handle to tagged-image file (TIFF) formatted data |
CF_UNICODETEXT |
For use with Windows NT/2000 or later, this format is used for UNICODE data as opposed to ANSI
text (which is represented by the CF_TEXT value). |
Close the Clipboard
When an application has finished examining or modifying the Clipboard data, the CloseClipboard
function is called. This has the effect of unlocking the Clipboard so that other applications can have access to it.
BOOL CloseClipboard()
Demo to Transfer Simple Text
The first demo we'll start with will illustrate how to transfer text to and from the Clipboard. While
this is not needed as much as it once was due to the Windows 2000/Me context menu (right-clicking over
any edit control will result in a menu containing the standard Clipboard functions) there are still some
very valid uses for this capability. One example is if you want to pass simple text-based data to another
application. However, in order to keep the example as clean as possible this demo will consist of a dialog
with two edit controls. One edit control will enable you to type text into it and copy that text to the
Clipboard while the second edit control will be a read-only control. A button will enable you to paste text
from the Clipboard into this second control. By keeping the demo relatively simple it's much easier for
you to focus on the specifics of working with the Clipboard and it's also more convenient when you want to
copy and paste the salient code from this demo into your own project files.
Creating the SimpleTextTransfer Demo Project
At this time, create a dialog-based application called SimpleTextTransfer. Once you've done that
modify the default dialog so that it looks like the one shown in Figure 2
Figure 2: Simple dialog to test using the Clipboard to transfer standard ANSI text.
After you've modified the dialog, you'll need to make the following changes to the dialog's controls.
- Set the Multiline property for both edit controls to True.
- Set the AutoVScroll property for both edit controls to True.
- Set the Vertical Scroll property for both edit controls to True.
- Set the Readonly property for "from Clipboard" edit control to True.
- Create a control member variable for each of the two edit controls. Name them
m_edtToClipboard
and m_edtFromClipboard, respectively.
Copying Text to the Clipboard
Once you've finished with the dialog controls' properties, add a event handler for the Copy button's
BN_CLICKED message and modify it so that it looks like the following. I've placed comments
throughout the code to make the code easy to understand.
void CSimpleTextTransferDlg::OnBnClickedBtncopy()
{
if (UpdateData())
{
CString strData;
m_edtToClipboard.GetWindowText(strData);
if (OpenClipboard())
{
EmptyClipboard();
HGLOBAL hClipboardData;
hClipboardData = GlobalAlloc(GMEM_DDESHARE,
strData.GetLength()+1);
char * pchData;
pchData = (char*)GlobalLock(hClipboardData);
strcpy(pchData, LPCSTR(strData));
GlobalUnlock(hClipboardData);
SetClipboardData(CF_TEXT,hClipboardData);
CloseClipboard();
}
}
}
Cutting Text to the Clipboard
As you might have guessed, the only difference between copying data to the Clipboard and cutting
it to the Clipboard is that in the latter case, the data is removed after the transfer takes place.
Therefore, at this point, add an event handler for the Cut button's BN_CLICKED message. Once
you've done that, modify that handler so that it looks as follows:
void CSimpleTextTransferDlg::OnBnClickedBtncut()
{
OnBnClickedBtncopy();
m_edtToClipboard.SetWindowText("");
}
Pasting Text from the Clipboard
At this point, the application can place data on the clipboard, but it doesn't yet allow for the
pasting of that data to the dialog. Therefore, we'll take care of that now.
As with the Copy and Cut buttons, add an event handler for the Paste button's BN_CLICKED message.
void CSimpleTextTransferDlg::OnBnClickedBtnpaste()
{
if (OpenClipboard())
{
HANDLE hClipboardData = GetClipboardData(CF_TEXT);
char *pchData = (char*)GlobalLock(hClipboardData);
CString strFromClipboard = pchData;
m_edtFromClipboard.SetWindowText(strFromClipboard);
GlobalUnlock(hClipboardData);
CloseClipboard();
}
}
Testing the SimpleTextTransfer Demo
At this point, compile and test the application. Your results should be similar to what you see in Figure 3.
Figure 3: With just a few lines of code, you can easily add Clipboard functionality to your application.
However, there is just one problem with our little test. If you click the <Alt><Print Screen> key
combination, this copies a bitmap of the current window to the Clipboard. You can test this by
opening the PaintBrush application and pasting the image into a new bitmap image. However, if
you copy an image to the Clipboard and then click the Paste button on the demo application, the
text in the "to clipboard" edit control is wiped out and nothing appears! This is because the demo
is attempting to use the data on the Clipboard without first determining if the data is valid for
this application.
In order to check as to the format of the data on the Clipboard, simply use the IsClipboardAvailable function.
BOOL IsClipboardFormatAvailable(UINT format)
The format parameter can be any value listed in Table 1. Here's the modified BN_CLICKED handler
for the dialog's Paste button. I've bolded the lines that have changed from the previous incarnation of this function.
void CSimpleTextTransferDlg::OnBnClickedBtnpaste()
{
if (OpenClipboard())
{
if (::IsClipboardFormatAvailable(CF_TEXT)
|| ::IsClipboardFormatAvailable(CF_OEMTEXT))
{
HANDLE hClipboardData = GetClipboardData(CF_TEXT);
char *pchData = (char*)GlobalLock(hClipboardData);
CString strFromClipboard = pchData;
m_edtFromClipboard.SetWindowText(strFromClipboard);
GlobalUnlock(hClipboardData);
}
else
{
AfxMessageBox("There is no text data (ANSI) on the Clipboard.");
}
CloseClipboard();
}
}
Now, if you run this application, copy a bitmap (or any data that is not defined as CF_TEXT or CF_OEMTEXT)
and press the demo's Paste button, you will see the message shown in Figure 4.
Figure 4: You should always check the format of the data on the Clipboard before attempting to use it.
Summary
In this first of a four-part series on programming the Windows Clipboard, you learned the basics of the using the Windows API to
transfer data to and from the Clipboard. You then put that new-found knowledge to work with a demo application using simple
ANSI text. In the next instalment of this series, I'll go into sending and receiving bitmap images from and to the Clipboard.
I'm a Senior Programming Writer in the Microsoft Windows Server organization where my focus is WMI, BITS, WinRM, and SMI-S.