VC++ 6.0 ebook chapter index
homepage aranna.altervista.org
Free counters!

Simple Use of the Clipboard

We'll begin by looking at the code involved for transferring data to the clipboard (Cut and Copy) and getting access to clipboard data (Paste).

The Standard Clipboard Data Formats

Windows supports various predefined clipboard formats that have identifiers beginning with the prefix CF defined in WINUSER.H.

First, there are three types of text data that can be stored in the clipboard, and another related clipboard format:

There are two additional clipboard formats that are conceptually similar to the CF_TEXT format (that is, they are text-based), but they are not necessarily NULL-terminated, because the formats define the end of the data. These formats are rarely used these days:

There are three clipboard formats used in conjunction with bitmaps, which are rectangular arrays of bits that correspond to the pixels of an output device. Bitmaps and these bitmap clipboard formats are discussed in more detail in Chapters 14 and 15:

It is also possible to store bitmap data in the clipboard in the industry-standard TIFF format:

There are two metafile formats that I'll describe in more detail in Chapter 18. A metafile is a collection of drawing commands stored in a binary form:

And finally there are also a few other miscellaneous clipboard formats:

Memory Allocation

When your program transfers something to the clipboard, it must allocate a memory block and essentially hand it over to the clipboard. When we've needed to allocate memory in earlier programs in this book, we've simply used the malloc function that is supported by the standard C run-time library. However, because the memory blocks stored by the clipboard must be shared among applications running under Windows, the malloc function is inadequate for this task.

Instead, we must dredge up memory allocation functions that were designed back in the dark ages of Windows, in the days when the operating system ran in a 16-bit real-mode memory architecture. These functions are still supported and you can still use them, but they are not often needed.

To allocate a memory block using the Windows API, you can call

hGlobal = GlobalAlloc (uiFlags, dwSize) ;

The function takes two parameters: a possible series of flags and a size in bytes of the allocated block. The function returns a handle of type HGLOBAL, called a "handle to a global memory block" or a "global handle." A NULL return value indicates that sufficient memory was not available for the allocation.

Although the two parameters to GlobalAlloc are defined a bit differently, they are both 32-bit unsigned integers. If you set the first parameter to zero, you effectively use the flag GMEM_FIXED. In this case, the global handle that GlobalAlloc returns is actually a pointer to the allocated memory block.

You can also use the flag GMEM_ZEROINIT if you'd like every byte in the memory block to be initially set to zero. The succinct GPTR flag combines the GMEM_FIXED and GMEM_ZEROINIT flags as defined in the Windows header files:

     #define GPTR (GMEM_FIXED | GMEM_ZEROINIT)

There is also a reallocation function:

hGlobal = GlobalReAlloc (hGlobal, dwSize, uiFlags) ;

You can use the GMEM_ZEROINIT flag to zero out the new bytes if the memory block is being enlarged.

Here's the function to obtain the size of the memory block:

dwSize = GlobalSize (hGlobal) ;

and the function to free it:

GlobalFree (hGlobal) ;

In the early 16-bit versions of Windows, the GMEM_FIXED flag was strongly discouraged because Windows could not move the block in physical memory. In the 32-bit versions of Windows, the GMEM_FIXED flag is normal because it returns a virtual address and the operating system can move the block in physical memory by altering the page table. When programming for the 16-bit versions of Windows, using the flag GMEM_MOVEABLE in GlobalAlloc was instead recommended. (Note that most dictionaries prefer the spelling "movable" over "moveable," so that's how I'll spell the word otherwise.) There's also a shorthand identifier identified in the Windows header files to additionally zero out the movable memory:

#define GHND (GMEM_MOVEABLE | GMEM_ZEROINIT)

The GMEM_MOVEABLE flag allows Windows to move a memory block in virtual memory. This doesn't necessarily mean that the memory block will be moved in physical memory, but the address that the application uses to read and write to the block can change.

Although GMEM_MOVEABLE was the rule in 16-bit versions of Windows, it is generally less useful now. However, if your application frequently allocates, reallocates, and frees memory blocks of various sizes, the virtual address space of your application can become fragmented. Conceivably, you could run out of virtual memory addresses. If this is a potential problem, then you'll want to use movable memory, and here's how to do it.

First define a pointer (for example, to an int type) and a variable of type GLOBALHANDLE:

     int * p ;
     GLOBALHANDLE hGlobal ;

Then allocate the memory. For example:

     hGlobal = GlobalAlloc (GHND, 1024) ;

As with any Windows handle, don't worry too much about what the number really means. Just store it. When you need to access that memory block, call

     p = (int *) GlobalLock (hGlobal) ;

This translates the handle into a pointer. During the time that the block is locked, Windows will fix the address in virtual memory. It will not move. When you are finished accessing the block, call

     GlobalUnlock (hGlobal) ;

This gives Windows the freedom to move the block in virtual memory. To be really compulsively correct about this process (and to experience the torments of early Windows programmers), you should lock and unlock the memory block in the course of a single message.

When you want to free the memory, call GlobalFree with the handle rather than the pointer. If you don't currently have access to the handle, use the function

     hGlobal = GlobalHandle (p) ;

You can lock a memory block multiple times before unlocking it. Windows maintains a lock count, and each lock requires a corresponding unlock before the block is free to be moved. When Windows moves a block in virtual memory, it doesn't need to copy the bytes from one location to another—it needs only manipulate the page tables. In general, in the 32-bit versions of Windows the only real reason for allocating a movable block for your own program's use is to prevent fragmentation of virtual memory. When using the clipboard, you should also use movable memory.

When allocating memory for the clipboard, you should use the GlobalAlloc function with both the GMEM_MOVEABLE and the GMEM_SHARE flags. The GMEM_SHARE flag makes the block available to other Windows applications.

Transferring Text to the Clipboard

Let's assume that you want to transfer an ANSI character string to the clipboard. You have a pointer (called pString) to this string, and you want to transfer iLength characters that might or might not be NULL-terminated.

You must first use GlobalAlloc to allocate a memory block of sufficient size to hold the character string. Include room for a terminating NULL:

hGlobal = GlobalAlloc (GHND | GMEM_SHARE, iLength + 1) ;

The value of hGlobal will be NULL if the block could not be allocated. If the allocation is successful, lock the block to get a pointer to it:

pGlobal = GlobalLock (hGlobal) ;

Copy the character string into the global memory block:

for (i = 0 ; i < wLength ; i++)
     *pGlobal++ = *pString++ ;

You don't need to add the terminating NULL because the GHND flag for GlobalAlloc zeroes out the entire memory block during allocation. Unlock the block:

GlobalUnlock (hGlobal) ;

Now you have a global memory handle that references a memory block containing the NULL-terminated text. To get this into the clipboard, open the clipboard and empty it:

OpenClipboard (hwnd) ;
EmptyClipboard () ;

Give the clipboard the memory handle by using the CF_TEXT identifier, and close the clipboard:

SetClipboardData (CF_TEXT, hGlobal) ;
CloseClipboard () ;

You're done.

Here are some rules concerning this process:

Getting Text from the Clipboard

Getting text from the clipboard is only a little more complex than transferring text to the clipboard. You must first determine whether the clipboard does in fact contain data in the CF_TEXT format. One of the easiest methods is to use the call

bAvailable = IsClipboardFormatAvailable (CF_TEXT) ;

This function returns TRUE (nonzero) if the clipboard contains CF_TEXT data. We used this function in the POPPAD2 program in Chapter 10 to determine whether the Paste item on the Edit menu should be enabled or grayed. IsClipboardFormatAvailable is one of the few clipboard functions that you can use without first opening the clipboard. However, if you later open the clipboard to get this text, you should also check again (using the same function or one of the other methods) to determine whether the CF_TEXT data is still in the clipboard.

To transfer the text out, first open the clipboard:

OpenClipboard (hwnd) ;

Obtain the handle to the global memory block referencing the text:

hGlobal = GetClipboardData (CF_TEXT) ;

This handle will be NULL if the clipboard doesn't contain data in the CF_TEXT format. This is another way to determine whether the clipboard contains text. If GetClipboardData returns NULL, close the clipboard without doing anything else.

The handle you receive from GetClipboardData doesn't belong to your program—it belongs to the clipboard. The handle is valid only between the GetClipboardData and CloseClipboard calls. You can't free that handle or alter the data it references. If you need to have continued access to the data, you should make a copy of the memory block.

Here's one method for copying the data into your program. Just allocate a pointer to a block of the same size as the clipboard data block:

pText = (char *) malloc (GlobalSize (hGlobal)) ;

Recall that hGlobal was the global handle obtained from the GetClipboardData call. Now lock the handle to get a pointer to the clipboard block:

pGlobal = GlobalLock (hGlobal) ;

Now just copy the data:

strcpy (pText, pGlobal) ;

Or you can use some simple C code:

while (*pText++ = *pGlobal++) ;

Unlock the block before closing the clipboard:

GlobalUnlock (hGlobal) ;
CloseClipboard () ;

Now you have a pointer called pText that references the program's own copy of the text.

Opening and Closing the Clipboard

Only one program can have the clipboard open at any time. The purpose of the OpenClipboard call is to prevent the clipboard contents from changing while a program is using the clipboard. OpenClipboard returns a BOOL value indicating whether the clipboard was successfully opened. It will not be opened if another application failed to close it. If every program politely opens and then closes the clipboard as quickly as possible responding to a user command, you'll probably never run into the problem of being unable to open the clipboard.

In the world of impolite programs and preemptive multitasking, some problems could arise. Even if your program hasn't lost input focus between the time it put something into the clipboard and the time the user invokes a Paste option, don't assume that what you've put in there is still there. A background process could have accessed the clipboard during that time.

Watch out for a more subtle problem involving message boxes: If you can't allocate enough memory to copy something to the clipboard, then you might want to display a message box. If this message box isn't system modal, however, the user can switch to another application while the message box is displayed. You should either make the message box system modal or close the clipboard before you display the message box.

You can also run into problems if you leave the clipboard open while you display a dialog box. Edit fields in a dialog box use the clipboard for cutting and pasting text.

The Clipboard and Unicode

So far I've been discussing using the clipboard solely with ANSI (one byte per character) text. This is the format when you use the CF_TEXT identifier. You may be wondering about CF_OEMTEXT and CF_UNICODETEXT.

I have some good news: you only need to call SetClipboardData and GetClipboardData with your preferred text format and Windows will handle all text conversions in the clipboard. For example, under Windows NT if a program uses SetClipboardData with a CF_TEXT clipboard data type, programs can also call GetClipboardData using CF_OEMTEXT. Similarly, the clipboard can convert CF_OEMTEXT data to CF_TEXT.

Under Windows NT, conversions occur between CF_UNICODETEXT, CF_TEXT, and CF_OEMTEXT. A program should call SetClipboardData using whatever text format is most convenient for the program. Similarly, a program should call GetClipboardData using whatever text form is desired by the program. As you know, the programs shown in this book are written so that they can be compiled with and without the UNICODE identifier. If your programs are like that, you'll probably implement code that calls SetClipboardData and GetClipboardData using CF_UNICODETEXT if the UNICODE identifier is defined and CF_TEXT if it is not.

The CLIPTEXT program shown in Figure 12-1 demonstrates one way this can be done.

Figure 12-1. The CLIPTEXT program.

CLIPTEXT.C

/*-----------------------------------------
   CLIPTEXT.C -- The Clipboard and Text
                 (c) Charles Petzold, 1998
  -----------------------------------------*/

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

#ifdef UNICODE

#define CF_TCHAR CF_UNICODETEXT
TCHAR szDefaultText[] = TEXT ("Default Text - Unicode Version") ;
TCHAR szCaption[]     = TEXT ("Clipboard Text Transfers - Unicode Version") ;

#else

#define CF_TCHAR CF_TEXT
TCHAR szDefaultText[] = TEXT ("Default Text - ANSI Version") ;
TCHAR szCaption[]     = TEXT ("Clipboard Text Transfers - ANSI Version") ;

#endif

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("ClipText") ;
     HACCEL       hAccel ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;
     
     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = szAppName ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, szCaption,
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

     hAccel = LoadAccelerators (hInstance, szAppName) ;

     while (GetMessage (&msg, NULL, 0, 0))
     {
          if (!TranslateAccelerator (hwnd, hAccel, &msg))
          {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
          }
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static PTSTR pText ;
     BOOL         bEnable ;
     HGLOBAL      hGlobal ;
     HDC          hdc ;
     PTSTR        pGlobal ;
     PAINTSTRUCT  ps ;
     RECT         rect ;
     
     switch (message)
     {
     case WM_CREATE:
          SendMessage (hwnd, WM_COMMAND, IDM_EDIT_RESET, 0) ;
          return 0 ;

    case WM_INITMENUPOPUP:
          EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE,
               IsClipboardFormatAvailable (CF_TCHAR) ? MF_ENABLED : MF_GRAYED) ;

          bEnable = pText ? MF_ENABLED : MF_GRAYED ;

          EnableMenuItem ((HMENU) wParam, IDM_EDIT_CUT,   bEnable) ;
          EnableMenuItem ((HMENU) wParam, IDM_EDIT_COPY,  bEnable) ;
          EnableMenuItem ((HMENU) wParam, IDM_EDIT_CLEAR, bEnable) ;
          break ;
          
     case WM_COMMAND:
          switch (LOWORD (wParam))
          {
          case IDM_EDIT_PASTE:
               OpenClipboard (hwnd) ;

               if (hGlobal = GetClipboardData (CF_TCHAR))
               {
                    pGlobal = GlobalLock (hGlobal) ;
                    if (pText)
                    {
                         free (pText) ;
                         pText = NULL ;
                    }
                    pText = malloc (GlobalSize (hGlobal)) ;
                    lstrcpy (pText, pGlobal) ;
                    InvalidateRect (hwnd, NULL, TRUE) ;
               }
               CloseClipboard () ;
               return 0 ;

          case IDM_EDIT_CUT:
          case IDM_EDIT_COPY:
               if (!pText)
                    return 0 ;

               hGlobal = GlobalAlloc (GHND | GMEM_SHARE, 
                                      (lstrlen (pText) + 1) * sizeof (TCHAR)) ;
               pGlobal = GlobalLock (hGlobal) ;
               lstrcpy (pGlobal, pText) ;
               GlobalUnlock (hGlobal) ;

               OpenClipboard (hwnd) ;
               EmptyClipboard () ;
               SetClipboardData (CF_TCHAR, hGlobal) ;
               CloseClipboard () ;

               if (LOWORD (wParam) == IDM_EDIT_COPY)
                    return 0 ;        
                                             // fall through for IDM_EDIT_CUT
          case IDM_EDIT_CLEAR:
               if (pText)
               {
                    free (pText) ;
                    pText = NULL ;
               }
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case IDM_EDIT_RESET:
               if (pText)
               {
                    free (pText) ;
                    pText = NULL ;
               }
               pText = malloc ((lstrlen (szDefaultText) + 1) * sizeof (TCHAR)) ;
               lstrcpy (pText, szDefaultText) ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;
          }
          break ;

     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;

          GetClientRect (hwnd, &rect) ;
          
          if (pText != NULL)
               DrawText (hdc, pText, -1, &rect, DT_EXPANDTABS | DT_WORDBREAK) ;

          EndPaint (hwnd, &ps) ;
          return 0 ;
          
     case WM_DESTROY:
          if (pText)
               free (pText) ;

          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

CLIPTEXT.RC (excerpts)

//Microsoft Developer Studio generated resource script.

#include "resource.h"
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
// Menu

CLIPTEXT MENU DISCARDABLE 
BEGIN
    POPUP "&Edit"
    BEGIN
        MENUITEM "Cu&t\tCtrl+X",                IDM_EDIT_CUT
        MENUITEM "&Copy\tCtrl+C",               IDM_EDIT_COPY
        MENUITEM "&Paste\tCtrl+V",              IDM_EDIT_PASTE
        MENUITEM "De&lete\tDel",                IDM_EDIT_CLEAR
        MENUITEM SEPARATOR
        MENUITEM "&Reset",                      IDM_EDIT_RESET
    END
END

/////////////////////////////////////////////////////////////////////////////
// Accelerator

CLIPTEXT ACCELERATORS DISCARDABLE 
BEGIN
    "C",            IDM_EDIT_COPY,          VIRTKEY, CONTROL, NOINVERT
    "V",            IDM_EDIT_PASTE,         VIRTKEY, CONTROL, NOINVERT
    VK_DELETE,      IDM_EDIT_CLEAR,         VIRTKEY, NOINVERT
    "X",            IDM_EDIT_CUT,           VIRTKEY, CONTROL, NOINVERT
END

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.
// Used by ClipText.rc

#define IDM_EDIT_CUT                    40001
#define IDM_EDIT_COPY                   40002
#define IDM_EDIT_PASTE                  40003
#define IDM_EDIT_CLEAR                  40004
#define IDM_EDIT_RESET                  40005

The idea here is that you can run both the Unicode and ANSI versions of this program under Windows NT and see how the clipboard translates between the two character sets. Notice the #ifdef statement at the top of CLIPTEXT.C. If the UNICODE identifier is defined, then CF_TCHAR (a generic text clipboard format name I made up) is equal to CF_UNICODETEXT; if not, it's equal to CF_TEXT. The IsClipboardFormatAvailable, GetClipboardData, and SetClipboardData function calls later in the program all use this CF_TCHAR name to specify the data type.

At the outset of the program (and whenever you select the Reset option from the Edit menu), the static variable pText contains a pointer to the Unicode string "Default Text - Unicode version" in the Unicode version of the program and "Default Text - ANSI version" in the non-Unicode version. You can use the Cut or Copy command to transfer this text string to the clipboard, and you can use the Cut or Delete command to delete the string from the program. The Paste command copies any text contents of the clipboard to pText. The pText string is displayed on the program's client area during the WM_PAINT message.

If you first select the Copy command from the Unicode version of CLIPTEXT and then the Paste command from the non-Unicode version, you can see that the text has been converted from Unicode to ANSI. Similarly, if you do the opposite commands, the text is converted from ANSI to Unicode.