Win32 Series - Simple Use of the Clipboard

http://www-user.tu-chemnitz.de/~heha/petzold/

 

 

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:

  • CF_TEXT A NULL-terminated ANSI character-set character string containing  a carriage return and a linefeed character at the end of each line. This is the  simplest form of clipboard data. The data to be transferred to the clipboard is stored  in a memory block and is transferred using the handle to the block. (I'll  discuss this concept shortly.) The memory block becomes the property of the  clipboard, and the program that creates the block should not continue to use it.

  • CF_OEMTEXT A memory block containing text data (similar to CF_TEXT)  but using the OEM character set. Windows programs usually don't need to  worry about this; it comes into play when using the clipboard in conjunction  with MSDOS programs running in a window.

  • CF_UNICODETEXT A memory block containing Unicode text. Like  CF_TEXT, each line is terminated with a carriage return and linefeed character, and a  NULL character (two zero bytes) indicates the end of the data. CF_UNICODETEXT  is supported under Windows NT only.

  • CF_LOCALE A handle to a locale identifier indicating the locale associated  with clipboard text.

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:

  • CF_SYLK A memory block containing data in the Microsoft "Symbolic  Link" format. This format is used for exchanging data between Microsoft's  Multiplan, Chart, and Excel programs. It is an ASCII format with each line terminated  with a carriage return and a linefeed.

  • CF_DIF A memory block containing data in the Data Interchange Format  (DIF). This is a format devised by Software Arts for use in transferring data to  the VisiCalc spreadsheet program. This is also an ASCII format with lines  terminated with carriage returns and linefeeds.

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 Chapters14 and 15:

  • CF_BITMAP A device-dependent bitmap. The bitmap is transferred to  the clipboard using the bitmap handle. Again, a program should not continue  to use this bitmap after giving it to the clipboard.

  • CF_DIB A memory block defining a device-independent bitmap, as  described inChapter 15. The memory block begins with a bitmap information  structure followed by a possible color table and the bitmap bits.

  • CF_PALETTE A handle to a color palette. This is generally used in  conjunction with CF_DIB for defining a color palette used by a  device-dependent bitmap.

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

  • CF_TIFF A memory block containing data in the Tag Image File Format  (TIFF). This is a format devised by Microsoft, Aldus Corporation, and  Hewlett-Packard Company in conjunction with some hardware manufacturers. The format  is available from the Hewlett-Packard Web site.

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:

  • CF_METAFILEPICT A "metafile picture" based on the old metafile support  of Windows.

  • CF_ENHMETAFILE A handle to an enhanced metafile supported under the  32-bit versions of Windows.

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

  • CF_PENDATA Used in conjunction with the pen extensions to Windows.

  • CF_WAVE A sound (waveform) file.

  • CF_RIFF Multimedia data in the Resource Interchange File Format.

  • CF_HDROP A list of files used in conjunction with drag-and-drop services.

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  inGlobalAlloc 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 (calledpString) 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:

  • Call OpenClipboard and  CloseClipboard while processing a single  message. Don't leave the clipboard open any longer than necessary.

  • Don't give the clipboard a locked memory handle.

  • After you call SetClipboardData, don't continue to use the memory block. It  no longer belongs to your program, and you should treat the handle as invalid.  If you need to continue to access the data, make another copy of it or read it  from the clipboard (as described in the next section). You can also continue to  reference the block between the SetClipboardData call and the  CloseClipboard call, but don't use the global handle you passed to the SetClipboardData function. This function also  returns a global handle that you can use instead. Lock  this handle to access the memory. Unlock the handle before you call CloseClipboard.

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 inChapter 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 IsClipboardFormatAvailableGetClipboardData, 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 variablepText 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.

 

你可能感兴趣的:(Win32 Series - Simple Use of the Clipboard)