PART V  DATA EXCHANGE AND LINKS
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Chapter 16  The Clipboard
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

The Windows clipboard allows data to be transferred from one program to
another. It is a relatively simple mechanism that doesn't require much
overhead in either the program that places data in the clipboard or the
program that later gets access to it.

Let's clear up one possible point of confusion right away: The CLIPBOARD
program that comes with Windows is not the clipboard. It is instead a
"clipboard viewer" that displays the current contents of the clipboard.
(We'll write our own simple clipboard viewer later in this chapter.) The
clipboard is simply a series of functions in Windows' USER module that
facilitate the exchange of memory blocks between programs.

Many programs that deal with documents or other data include an Edit menu
with the options Cut, Copy, and Paste. When a user selects Cut or Copy, the
program transfers data from the program to the clipboard. This data is in a
particular format, such as text, a bitmap, or a metafile. When a user
selects Paste from the menu, the program determines if the clipboard
contains data in a format that the program can use and, if so, transfers
data from the clipboard to the program.

Programs should not transfer data into or out of the clipboard without an
explicit instruction from the user. For example, a user who performs a Cut
or a Copy operation in one program should be able to assume that the data
will remain in the clipboard until the next Cut or Copy operation or until
the user employs a program--such as the BLOWUP2 presented in this
chapter--specifically designed to manipulate the clipboard.

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 standard clipboard formats that have WINDOWS.H
identifiers. These are:

  þ   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 global memory block and is
      transferred using the handle to the block. The memory block becomes
      the property of the clipboard, and the program that creates the block
      should not continue to use it.

  þ   CF_BITMAP--a Windows 2\-compatible 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_METAFILEPICT--a "metafile picture." This isn't exactly the same as
      a metafile (described in Chapter 13). Rather, it's a metafile with
      some additional information in the form of a small structure of type
      METAFILEPICT. A program transfers a metafile picture to the clipboard
      using the handle to a global memory block containing this structure.
      The four fields of the METAFILEPICT structure are mm (int), the
      mapping mode for the metafile; xExt (int) and yExt (int), in simple
      terms, the width and height of the metafile image; and hMF (HANDLE),
      the handle to the metafile. (I'll discuss the xExt and yExt fields in
      detail later in this chapter.) After a program transfers a metafile
      picture to the clipboard, it should not continue to use either the
      global memory block containing the METAFILEPICT structure or the
      metafile handle, because both will be under the control of the USER
      module.

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

  þ   CF_DIF--a global memory block containing data in the Data Interchange
      Format (DIF). This is a format devised by Software Arts for use with
      transferring data to the VisiCalc spreadsheet program. The format is
      now

      under the control of Lotus Corporation. This is also an ASCII format
      with lines terminated with carriage returns and linefeeds.

The CF_SYLK and CF_DIF formats are conceptually similar to the CF_TEXT
format. However, character strings containing SYLK or DIF data are not
necessarily NULL-terminated, because the formats define the end of the data.
(For descriptions of these two formats, see Jeff Walden, File Formats for
Popular PC Software, John Wiley & Sons, 1986.)

  þ   CF_TIFF--a global 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 (which describes bitmapped data) is
      available from Hewlett-Packard.

  þ   CF_OEMTEXT--a global memory block containing text data (simple to
      CF_TEXT) but using the OEM character set.

  þ   CF_DIB--a global memory block defining a device-independent bitmap.
      The global memory block begins with a BITMAPINFO structure followed by
      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 the
      bitmap.


Transferring Text to the Clipboard

Let's assume that you want to transfer a character string to the clipboard
and that you have a pointer (called pString) to this string. This can be a
near pointer if the text is stored in your program's local data segment or a
far pointer if the text is stored in a global data segment. You want to
transfer wLength bytes of this string.

First, allocate a moveable global memory block of wLength size. Include room
for a terminating NULL:

hGlobalMemory = GlobalAlloc (GHND, (DWORD) wLength + 1) ;

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

lpGlobalMemory = GlobalLock (hGlobalMemory) ;

Copy the character string into the global memory block:

for (n = 0 ; n < wLength ; n++)
     *lpGlobalMemory++ = *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 (hGlobalMemory) ;

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 global memory handle using the CF_TEXT identifier,
and close the clipboard:

SetClipboardData (CF_TEXT, hGlobalMemory) ;
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 when you exit the window
      procedure. Don't let control transfer to another program (perhaps by
      calling SendMessage or PeekMessage) while the clipboard is open.

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

  þ   After you call SetClipboardData, don't continue to use the global
      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 you must use the glo- bal handle that is returned from
      SetClipboardData. Unlock this 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 in Chapter 9 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 if 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:

hClipMemory = 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 if 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 a global memory segment that
belongs to your program. First, allocate a global memory block of the same
size as that referenced by hClipMemory:

hMyMemory = GlobalAlloc (GHND, GlobalSize (hClipMemory)) ;

Check for a NULL value from GlobalAlloc to determine if the block was really
allocated. If it was allocated, lock both handles and get pointers to the
beginning of the blocks:

lpClipMemory = GlobalLock (hClipMemory) ;
lpMyMemory = GlobalLock (hMyMemory) ;

Because the character string is NULL-terminated, you can transfer the data
using Windows' lstrcpy function:

lstrcpy (lpMyMemory, lpClipMemory) ;

Or you can use some simple C code:

while (*lpMyMemory++ = *lpClipMemory++) ;

Unlock both blocks:

GlobalUnlock (hClipMemory) ;
GlobalUnlock (hMyMemory) ;

Finally, close the clipboard:

CloseClipboard () ;

Now you have a global handle called hMyMemory that you can later lock to
access this data.


What the Clipboard Does

The clipboard works primarily by altering the memory allocation flags of
global memory blocks. When a program allocates a global memory block using
the GHND flag (a combination of the GMEM_MOVEABLE and GMEM_ZEROINIT flags),
the memory block is  marked as belonging to the program (more precisely, the
particular instance of the program). Normally, Windows deletes this memory
block when the instance terminates. When a program uses SetClipboardData to
give the global handle to the clipboard, Windows must transfer ownership of
the memory block from the program to itself. This action requires that
Windows modify the memory allocation flags of the global memory block by
calling:

GlobalReAlloc (hMem, 0L, GMEM_MODIFY | GMEM_DDESHARE) ;

The USER module establishes ownership of the memory block. After the
SetClipboardData call, the global memory handle no longer belongs to the
program that allocated it, and the block won't be freed when the program
terminates. The program that created the memory block can't continue to use
it except when the clipboard gives the program access to the block. Now the
USER module must explicitly free the memory block, which it does when a
program calls EmptyClipboard.

When a program calls GetClipboardData, Windows gives the program making the
call the handle to the global memory block and allows the program temporary
access to the memory block. The program can then copy this data into another
global memory block or a local memory block. Thus, the clipboard is really
just a manager of shared memory segments. One program gives the clipboard a
block of global memory. Other programs can get access to the block. The
clipboard retains ownership of it.


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. During the early stages of
programming for the clipboard, you should probably check this value, but the
check isn't crucial in a nonpreemptive multitasking environment. If every
program politely opens and then closes the clipboard during a single message
without giving control to other programs, then you'll never run into the
problem of being unable to open the clipboard.

I've already mentioned avoiding the use of SendMessage or PeekMessage while
the clipboard is open, but watch out for a more subtle problem involving
message boxes: If you can't allocate a global memory segment to copy the
contents of 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.


Using the Clipboard with Bitmaps

In using the CF_BITMAP format, you give the clipboard a handle to a bitmap
when calling SetClipboardData. The GetClipboardData function returns a
handle to a bitmap.

You may recall the BLOWUP1 program from Chapter 4, which allowed you to
block out any part of the display and copy it to BLOWUP1's client area.
BLOWUP1 used StretchBlt to expand or compress the size of the blocked-out
area so that it exactly fit the client area. The program had a problem,
however: If part of BLOWUP1's client area was destroyed (perhaps by an
overlapping window from another program), BLOWUP1 couldn't recreate it. We
could have solved that problem by creating a bitmap based on the size of the
area BLOWUP1 was copying. When BLOWUP1 needed to redisplay the bitmap in its
client area, it could have selected the bitmap into a memory device context
and used StretchBlt to copy it to the client area.

Let's go one step further and write a revised version of this program that
uses the clipboard to hold onto this bitmap. This approach provides two
advantages:

  þ   Any part of the display can now be copied in bitmap format and stored
      in the clipboard.

  þ   Any bitmap that is stored in the clipboard can be copied into and
      scaled to the size of the program's client area. You can then block
      out all or part of this display and copy that to the clipboard. This
      provides an easy manual approach to scaling or cropping bitmaps.


The Revised BLOWUP Program

The BLOWUP2 program is shown in Figure 16-1. You use it the same way as the
BLOWUP1 program. First, click the mouse in BLOWUP1's client area. The cursor
changes to a cross hair. Now place the cursor on one corner of the rectangle
you want to capture, press the mouse button, drag the mouse to the opposite
corner, and release the button.

 BLOWUP2.MAK

#-----------------------
# BLOWUP2.MAK make file
#-----------------------

blowup2.exe : blowup2.obj blowup2.def
     link blowup2, /align:16, NUL, /nod slibcew libw, blowup2
     rc blowup2.exe

blowup2.obj : blowup2.c
     cl -c -Gsw -Ow -W2 -Zp blowup2.c

 BLOWUP2.C

/*------------------------------------------------
   BLOWUP2.C -- Capture Screen Image to Clipboard
                (c) Charles Petzold, 1990
  ------------------------------------------------*/

#include 
#include 

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName [] = "Blowup2" ;
     HWND        hwnd ;
     MSG         msg ;
     WNDCLASS    wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = NULL ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, szAppName,
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

void InvertBlock (HWND hwnd, POINT org, POINT len)
     {
     HDC   hdc ;

     hdc = CreateDC ("DISPLAY", NULL, NULL, NULL) ;
     ClientToScreen (hwnd, &org) ;
     PatBlt (hdc, org.x, org.y, len.x, len.y, DSTINVERT) ;
     DeleteDC (hdc) ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static BOOL  bCapturing, bBlocking ;
     static POINT org, len ;
     static short cxClient, cyClient ;
     BITMAP       bm ;
     HDC          hdc, hdcMem ;
     HBITMAP      hBitmap ;
     PAINTSTRUCT  ps ;
     switch (message)
          {
          case WM_SIZE :
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;
               return 0 ;

          case WM_LBUTTONDOWN :
               if (!bCapturing)
                    {
                    bCapturing = TRUE ;
                    SetCapture (hwnd) ;
                    SetCursor (LoadCursor (NULL, IDC_CROSS)) ;
                    }
               else if (!bBlocking)
                    {
                    bBlocking = TRUE ;
                    org = MAKEPOINT (lParam) ;
                    }
               return 0 ;

          case WM_MOUSEMOVE :
               if (bCapturing)
                    SetCursor (LoadCursor (NULL, IDC_CROSS)) ;

               if (bBlocking)
                    {
                    len = MAKEPOINT (lParam) ;
                    len.x -= org.x ;
                    len.y -= org.y ;

                    InvertBlock (hwnd, org, len) ;
                    InvertBlock (hwnd, org, len) ;
                    }
               return 0 ;

          case WM_LBUTTONUP :
               if (!bBlocking)
                    break ;

               bCapturing = bBlocking = FALSE ;
               SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
               ReleaseCapture () ;

               if (len.x == 0 || len.y == 0)
                    break ;

               hdc = GetDC (hwnd) ;
               hdcMem = CreateCompatibleDC (hdc) ;
               hBitmap = CreateCompatibleBitmap (hdc,
                                    abs (len.x), abs (len.y)) ;
               if (hBitmap)
                    {
                    SelectObject (hdcMem, hBitmap) ;
                    StretchBlt (hdcMem, 0, 0, abs (len.x), abs (len.y),
                         hdc, org.x, org.y, len.x, len.y, SRCCOPY) ;

                    OpenClipboard (hwnd) ;
                    EmptyClipboard () ;
                    SetClipboardData (CF_BITMAP, hBitmap) ;
                    CloseClipboard () ;

                    InvalidateRect (hwnd, NULL, TRUE) ;
                    }
               else
                    MessageBeep (0) ;

               DeleteDC (hdcMem) ;
               ReleaseDC (hwnd, hdc) ;
               return 0 ;

          case WM_PAINT :
               InvalidateRect (hwnd, NULL, TRUE) ;
               hdc = BeginPaint (hwnd, &ps) ;
               OpenClipboard (hwnd) ;

               if (hBitmap = GetClipboardData (CF_BITMAP))
                    {
                    SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
                    hdcMem = CreateCompatibleDC (hdc) ;
                    SelectObject (hdcMem, hBitmap) ;
                    GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm) ;

                    SetStretchBltMode (hdc, COLORONCOLOR) ;
                    StretchBlt (hdc, 0, 0, cxClient, cyClient,
                                hdcMem, 0, 0, bm.bmWidth, bm.bmHeight,
                                                  SRCCOPY) ;

                    SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
                    DeleteDC (hdcMem) ;
                    }

               CloseClipboard () ;
               EndPaint (hwnd, &ps) ;
               return 0 ;

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

 BLOWUP2.DEF

;------------------------------------
; BLOWUP2.DEF module definition file
;------------------------------------

NAME           BLOWUP2

DESCRIPTION    'Capture Screen Image to Clipboard (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

In the earlier BLOWUP1 program, the blocked-out section of the display was
copied to BLOWUP1's client area. In the new version, the area of the display
is copied to a bitmap in a memory device context, and the bitmap is
transferred to the clipboard.

When you're blocking out an area of the display with the mouse, BLOWUP2
retains two structures of type POINT with the initial corner (org, for
"origin") and the width and height of the rectangle (len, for "length"). If
the org point isn't the upper left corner of the rectangle, then one or both
of the values in len will be negative. When the mouse button is released
(signaling to the program that the user has finished blocking out the
rectangle), BLOWUP2 creates a memory device context and a bitmap using the
absolute values of the lengths in the len point structure:

hdc = GetDC (hwnd) ;
hdcMem = CreateCompatibleDC (hdc) ;
hBitmap = CreateCompatibleBitmap (hdc,
                    abs (len.x), abs (len.y)) ;

If BLOWUP2 succeeds in creating this bitmap, the program selects the bitmap
into the memory device context and uses StretchBlt to copy the blocked-out
area of the display:

if (hBitmap)
     {
     SelectObject (hdcMem, hBitmap) ;
     StretchBlt (hdcMem, 0, 0, abs (len.x), abs (len.y),
          hdc, org.x, org.y, len.x, len.y, SRCCOPY) ;

Although we're using StretchBlt here, the image is not being stretched or
compressed. However, if you block out the rectangle from right to left, then
StretchBlt is needed to flip the image around a vertical axis. Similarly, if
you block it out from bottom to top, then StretchBlt turns it upside down.

The program then opens and empties the clipboard, transfers the bitmap to
the clipboard, and closes the clipboard:

     OpenClipboard (hwnd) ;
     EmptyClipboard () ;
     SetClipboardData (CF_BITMAP, hBitmap) ;
     CloseClipboard () ;

The bitmap is now the responsibility of the clipboard. Do not delete it! The
clipboard will delete the bitmap itself the next time it gets an
EmptyClipboard call.

Because the clipboard contains a new bitmap, BLOWUP2 invalidates its own
client area, as follows:

     InvalidateRect (hwnd, NULL, TRUE) ;
     }

If BLOWUP2 wasn't successful in creating a bitmap, it beeps:

else
     MessageBeep (0) ;

Finally, the memory device context is deleted, and the window's device
context is released:

DeleteDC (hdcMem) ;
ReleaseDC (hwnd, hdc) ;

When BLOWUP2 gets a WM_PAINT message, it opens the clipboard and checks to
see if a bitmap is available:

OpenClipboard (hwnd) ;

if (hBitmap = GetClipboardData (CF_BITMAP))
     {

If the clipboard contains a bitmap, BLOWUP2 creates a memory device context
and selects the bitmap from the clipboard into the device context:

     hdcMem = CreateCompatibleDC (hdc) ;
     SelectObject (hdcMem, hBitmap) ;

To copy the dimensions of this bitmap into a BITMAP structure, BLOWUP2 uses
GetObject:

     GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm) ;

It can then copy the bitmap to the client area, stretching it to the larger
or smaller dimensions:

     SetStretchBltMode (hdc, COLORONCOLOR) ;
     StretchBlt (hdc, 0, 0, xClient, yClient,
            hdcMem, 0, 0, bm.bmWidth, bm.bmHeight,
                              SRCCOPY) ;

The only cleanup involved is deleting the memory device context:

     DeleteDC (hdcMem) ;
     }

and closing the clipboard:

CloseClipboard () ;

The bitmap isn't deleted, because it belongs to the clipboard.

If we wanted to make an exact copy of the bitmap, we could use GetObject to
obtain the dimensions:

GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm) ;

and create a new bitmap and another memory device context:

hBitmap2 = CreateBitmapIndirect (&bm) ;
hdcMem2 = CreateCompatibleDC (hdc) ;
SelectObject (hdcMem2, hBitmap2) ;

A simple BitBlt copies the bitmap:

BitBlt (hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight,
        hdcMem, 0, 0, SRCCOPY) ;

You would then delete the two memory device contexts.

Although BLOWUP2 will display in its client area any bitmap that is
currently in the clipboard, it checks the contents of the clipboard only
when it gets a WM_PAINT message. For instance, if you draw something in the
Windows PAINTBRUSH program, block it out, and copy it to the clipboard,
BLOWUP2's client area won't show this new bitmap until BLOWUP2 gets a
WM_PAINT message. For this reason, BLOWUP2 isn't a true clipboard viewer.
We'll examine clipboard viewers later in this chapter.


The Metafile and the Metafile Picture

Using the clipboard to transfer metafiles from one program to another
involves complexities not present when dealing with text and bitmaps. You
can determine the length of a NULL-terminated string by simply searching for
the NULL terminator. You can determine the dimensions of a bitmap using
GetObject. But if you have a handle to a metafile, how can you determine how
large the image will be when you play the metafile? Unless you start digging
into the internals of the metafile itself, you can't.

Moreover, when a program obtains a metafile from the clipboard, it has the
most flexibility in working with it if the metafile has been designed to be
played in an MM_ISOTROPIC or MM_ANISOTROPIC mapping mode. The program that
receives the metafile can then scale the image by simply setting viewport
extents before playing the metafile. But if the mapping mode is set to
MM_ISOTROPIC or MM_ANISOTROPIC within the metafile, then the program that
receives the metafile is stuck. The program can make GDI calls only before
or after the metafile is played. It can't make a GDI call in the middle of a
metafile.

To solve these problems, metafile handles are not directly put into the
clipboard and retrieved by other programs. Instead, the metafile handle is
part of a "metafile picture," which is a structure of type METAFILEPICT.
This structure allows the program that obtains the metafile picture from the
clipboard to set the mapping mode and viewport extents itself before playing
the metafile.

The METAFILEPICT structure is 8 bytes long and has four fields: mm (int),
the mapping mode; xExt (int) and yExt (int), the width and height of the
metafile image; and hMF (WORD), the handle to the metafile. For all the
mapping modes except MM_ISOTROPIC and MM_ANISOTROPIC, the xExt and yExt
values are the size of the image in units of the mapping mode given by mm.
With this information, the program that copies the metafile picture
structure from the clipboard can determine how much display space the
metafile will encompass when it is played. The program that creates the
metafile can set these values to the largest x- and y-coordinates it uses in
the GDI drawing functions that enter the metafile.

For the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes, the xExt and yExt
fields function differently. You will recall from Chapter 11 that a program
uses the MM_ISOTROPIC or MM_ANISOTROPIC mapping mode when it wants to use
arbitrary logical units in GDI functions independent of the measurable size
of the image. A program uses MM_ISOTROPIC when it wants to maintain an
aspect ratio regardless of the size of the viewing surface and
MM_ANISOTROPIC when it doesn't care about the aspect ratio. You will also
recall from Chapter 11 that after a program sets the mapping mode to
MM_ISOTROPIC or MM_ANISOTROPIC, it generally makes calls to SetWindowExt and
SetViewportExt. The SetWindowExt call uses logical units to specify the
units the program wants to use when drawing. The SetViewportExt call uses
device units based on the size of the viewing surface (for instance, the
size of the window's client area).

If a program creates an MM_ISOTROPIC or MM_ANISOTROPIC metafile for the
clipboard, then the metafile should not itself contain a call to
SetViewportExt, because the device units in that call would be based on the
display surface of the program creating the metafile and not on the display
surface of the program that reads the metafile from the clipboard and plays
it. Instead, the xExt and yExt values should assist the program that obtains
the metafile from the clipboard in setting appropriate viewport extents for
play- ing the metafile. But the metafile itself contains a call to set the
window extent when the mapping mode is MM_ISOTROPIC or MM_ANISOTROPIC. The
coordinates of the GDI drawing functions within the metafile are based on
these window extents.

The program that creates the metafile and metafile picture follows these
rules:

  þ   The mm field of the METAFILEPICT structure is set to specify the
      mapping mode.

  þ   For mapping modes other than MM_ISOTROPIC and MM_ANISOTROPIC, the xExt
      and yExt fields are set to the width and height of  the image in units
      corresponding to the mm field. For metafiles to be  played in an
      MM_ISOTROPIC or MM_ANISOTROPIC environment, mat- ters get a little
      more complex. For MM_ANISOTROPIC, 0 values of xExt  and yExt are used
      when the program is suggesting neither a size nor an  aspect ratio for
      the image. For MM_ISOTROPIC or MM_ANISO- TROPIC, positive values of
      xExt and yExt indicate a suggested width  and height of the image in
      units of 0.01 mm (MM_HIMETRIC  units). For MM_ISOTROPIC, negative
      values of xExt and yExt indicate  a suggested aspect ratio of the
      image but not a suggested size.

  þ   For the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes, the metafile
      itself contains calls to SetWindowExt and (possibly) SetWindowOrg.
      That is, the program that creates the metafile calls these functions
      in the metafile device context. Generally, the metafile will not
      contain calls to SetMapMode, SetViewportExt, or SetViewportOrg.

  þ   The metafile should be a memory-based metafile, not a disk-based
      metafile.

Here's some sample code for a program creating a metafile and copying it to
the clipboard. If the metafile uses the MM_ISOTROPIC or MM_ANISOTROPIC
mapping mode, the first calls in the metafile should be to set the window
extent. (The window extent is fixed in the other mapping modes.) Regardless
of the mapping mode, the window origin can also be set:

hdcMeta = CreateMetaFile (NULL) ;
SetWindowExt (hdcMeta, ...) ;
SetWindowOrg (hdcMeta, ...) ;

The coordinates in the drawing functions of the metafile are based on these
window extents and the window origin. After the program uses GDI calls to
draw on the metafile device context, the metafile is closed to get a handle
to the metafile:

hmf = CloseMetaFile (hdcMeta) ;

The program also needs to define a far pointer to a structure of type
METAFILEPICT and allocate a block of global memory for this structure:

GLOBALHANDLE   hGMem ;
LPMETAFILEPICT lpMFP ;

[other program lines]hGMem = GlobalAlloc (GHND, (DWORD) sizeof
(METAFILEPICT)) ;

lpMFP = (LPMETAFILEPICT) GlobalLock (hGMem) ;

Next, the program sets the four fields of this structure.

lpMFP->mm   = MM_...  ;
lpMFP->xExt = ...  ;
lpMFP->yExt = ...  ;
lpMFP->hMF  = hmf  ;

GlobalUnlock (hGMem) ;

The program then transfers the global memory block containing the metafile
picture structure to the clipboard:

OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (CF_METAFILEPICT, hGMem) ;
CloseClipboard () ;

Following these calls, the hGMem handle (the memory block containing the
metafile picture structure) and the hmf handle (the metafile itself) become
invalid for the program  that created them.

Now for the hard part. When a program obtains a metafile from the clipboard
and plays this metafile, it must do the following:

  1.  The program uses the mm field of the metafile picture structure to set
      the mapping mode.

  2.  For mapping modes other than MM_ISOTROPIC or MM_ANISOTROPIC, the
      program uses the xExt and yExt values to set a clipping rectangle or
      simply to determine the size of the image. For the MM_ISOTROPIC and
      MM_ANISOTROPIC mapping modes, the program uses xExt and yExt  to set
      the viewport extents.

  3.  The program then plays the metafile.

Here's the code. You first open the clipboard, get the handle to the
metafile picture structure, and lock it:

OpenClipboard (hwnd) ;
hGMem = GetClipboardData (CF_METAFILEPICT) ;
lpMFP = (LPMETAFILEPICT) GlobalLock (hGMem) ;

You can then save the attributes of your current device context and set the
mapping mode to the mm value of the structure:

SaveDC (hdc) ;
SetMappingMode (lpMFP->mm) ;

If the mapping mode isn't MM_ISOTROPIC or MM_ANISOTROPIC, you can set a
clipping rectangle to the values of xExt and yExt. Because these values are
in logical units, you have to use LPtoDP to convert the coordinates to
device units for the clipping rectangle. Or you can simply save the values
so you know how large the image is.

For the MM_ISOTROPIC or MM_ANISOTROPIC mapping mode, you use xExt and yExt
to set the viewport extent. One possible function to perform this task is
shown below. This function assumes that cxClient and cyClient represent the
pixel height and width of the area in which you want the metafile to appear
if no suggested size is implied by xExt and yExt.

void PrepareMetaFile (HDC hdc, LPMETAFILEPICT lpmfp,
                         SHORT cxClient, SHORT cyClient)
     {
     long xlScale, ylScale, lScale ;

     SetMapMode (hdc, lpmfp->mm) ;

     if (lpmfp->mm == MM_ISOTROPIC || lpmfp->mm == MM_ANISOTROPIC)
          {
          if (lpmfp->xExt == 0)
               SetViewportExt (hdc, cxClient, cyClient) ;

          else if (lpmfp->xExt > 0)
               SetViewportExt (hdc,
                    (short) ((long) lpmfp->xExt *
                              GetDeviceCaps (hdc, HORZRES) /
                              GetDeviceCaps (hdc, HORZSIZE) / 100),
                    (short) ((long) lpmfp->yExt *
                              GetDeviceCaps (hdc, VERTRES) /
                              GetDeviceCaps (hdc, VERTSIZE) / 100)) ;

          else if (lpmfp->xExt < 0)
               {
               xlScale = 100L * (long) cxClient *
                              GetDeviceCaps (hdc, HORZSIZE) /
                              GetDeviceCaps (hdc, HORZRES) /
                                             -lpmfp->xExt ;
               ylScale = 100L * (long) cyClient *
                              GetDeviceCaps (hdc, VERTSIZE) /
                              GetDeviceCaps (hdc, VERTRES) /
                                             -lpmfp->yExt ;
               lScale = min (xlScale, ylScale) ;

               SetViewportExt (hdc,
                    (short) ((long) -lpmfp->xExt * lScale *
                              GetDeviceCaps (hdc, HORZRES) /
                              GetDeviceCaps (hdc, HORZSIZE) / 100),
                    (short) ((long) -lpmfp->yExt * lScale *
                              GetDeviceCaps (hdc, VERTRES) /
                              GetDeviceCaps (hdc, VERTSIZE) / 100)) ;
               }
          }
     }

This code assumes that both xExt and yExt are 0, greater than 0, or less
than 0, (which should be the case). If the extents are 0, no size or aspect
ratio is suggested. The viewport extents are set to the area in which you
want to display the metafile. Positive values of xExt and yExt are a
suggested image size in units of 0.01 mm. The GetDeviceCaps function assists
in determining the number of pixels per 0.01 mm, and this value is
multiplied by the extent values in the metafile picture structure. Negative
values of xExt and yExt indicate a suggested aspect ratio but not a
suggested size. The value lScale is first calculated based on the aspect
ratio of the size in millimeters corresponding to cxClient and cyClient.
This scaling factor is then used to set a viewport extent in pixels.

With this job out of the way, you can set a viewport origin if you want,
play the metafile, and return the device context to normal:

PlayMetaFile (lpMFP->hMF) ;
RestoreDC (hdc, -1) ;

Then you unlock the memory block and close the clipboard:

GlobalUnlock (hGMem) ;
CloseClipboard () ;



BEYOND SIMPLE CLIPBOARD USE

In using text and bitmaps, you've seen that transferring data to the
clipboard requires four calls after the data has been prepared:

OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (wFormat, hHandle) ;
CloseClipboard () ;

Getting access to this data requires three calls:

OpenClipboard (hwnd) ;
hHandle = GetClipboardData (wFormat) ;

[other program lines]CloseClipboard () ;

You can make a copy of the clipboard data or use it in some other manner
between the GetClipboardData and CloseClipboard calls. That approach may be
all you'll need for most purposes, but you can also use the clipboard in
more sophisticated ways.

Using Multiple Data Items

When you open the clipboard to put data into it, you must call
EmptyClipboard to signal Windows to free or delete the contents of the
clipboard. You can't add something to the existing contents of the
clipboard. So in this sense, the clipboard holds only one item at a time.

However, between the EmptyClipboard and the CloseClipboard calls, you can
call SetClipboardData several times, each time using a different clipboard
format. For instance, if you want to store a short string of text in the
clipboard, you can create a metafile device context and write that text to
the metafile. You can also create a bitmap large enough to hold the
character string, select the bitmap into a memory device context, and write
the string to the bitmap. In this way, you make that character string
available not only to programs that can read text from the clipboard but
also to programs that read bitmaps and metafiles from the clipboard.
Moreover, if you select a different font into the metafile device context or
memory device context before writing the text, programs that read bitmaps or
metafiles will use the string with this different font. (Of course, these
programs won't be able to recognize the metafile or bitmap as actually
containing a character string.)

If you want to write several handles to the clipboard, you call
SetClipboardData for each of them:

OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (CF_TEXT, hGMemText) ;
SetClipboardData (CF_BITMAP, hBitmap) ;
SetClipboardData (CF_METAFILEPICT, hGMemMFP) ;
CloseClipboard () ;

While these three formats of data are in the clipboard, an
IsClipboardFormatAvailable call with the CF_TEXT, CF_BITMAP, or
CF_METAFILEPICT argument will return TRUE. A program can get access to these
handles by calling:

hGMemText = GetClipboardData (CF_TEXT) ;

or:

hBitmap = GetClipboardData (CF_BITMAP) ;

or:

hGMemMFP = GetClipboardData (CF_METAFILEPICT) ;

The next time a program calls EmptyClipboard, Windows will free or delete
all three of the handles retained by the clipboard as well as the metafile
that is part of the METAFILEPICT structure.

A program can determine all the formats stored by the clipboard by first
opening the clipboard and then calling EnumClipboardFormats. Start off by
setting a variable wFormat to 0:

wFormat = 0 ;
OpenClipboard (hwnd) ;

Now make successive EnumClipboardFormats calls starting with the 0 value.
The function will return a positive wFormat value for each format currently
in the clipboard. When the function returns 0, you're done:

while (wFormat = EnumClipboardFormats (wFormat))
     {

[logic for each wFormat value]     }
CloseClipboard () ;

You can obtain the number of different formats currently in the clipboard by
calling:

nCount = CountClipboardFormats () ;


Delayed Rendering

When you put data into the clipboard, you generally make a copy of the data
and give the clipboard a handle to a global memory block that contains the
copy. For very large data items, this approach can waste memory. If the user
never pastes that data into another program, it will continue to occupy
memory space until it is replaced by something else.

You can avoid this problem by using a technique called "delayed rendering,"
in which your program doesn't actually supply the data until another program
needs it. Rather than give Windows a handle to the data, you simply use a
NULL in the SetClipboardData call:

OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (wFormat, NULL) ;
CloseClipboard () ;

You can have multiple SetClipboardData calls using different values of
wFormat. You can use NULL parameters with some of them and real handles with
others.

That's simple enough, but now the process gets a little more complex. When
another program calls GetClipboardData, Windows will check to see if the
handle for that format is NULL. If it is, Windows will send a message to the
"clipboard owner" (your program) asking for a real handle to the data. Your
program must then supply this handle.

More specifically, the "clipboard owner" is the last window that put data
into the clipboard. Windows can identify the clipboard owner, because the
OpenClipboard call requires a window handle. (Because programs can also call
OpenClipboard when reading data from the clipboard or enumerating clipboard
formats, it's really the EmptyClipboard call that establishes this window
handle as the clipboard owner.)

A program that uses delayed rendering has to process three messages in its
window procedure: WM_RENDERFORMAT, WM_RENDERALLFORMATS, and WM_DESTROY-
CLIPBOARD. Windows sends your window procedure a WM_RENDERFORMAT message
when another program calls GetClipboardData. The value of wParam is the
format requested. When you process the WM_RENDERFORMAT message, don't open
and empty the clipboard. Simply create a global memory block for the format
given by wParam, transfer the data to it, and call SetClipboardData with the
correct format and the global handle. Obviously, you'll need to retain
information in your program in order to construct this data properly when
processing WM_RENDERFORMAT. When another program calls EmptyClipboard,
Windows sends your program a WM_DESTROYCLIPBOARD message. This tells you
that the information to construct the clipboard data is no longer needed.
You are no longer the clipboard owner.

If your program terminates while it is still the clipboard owner, and the
clipboard still contains NULL data handles that your program set with
SetClipboardData, you'll receive a WM_RENDERALLFORMATS message. You should
open the clipboard, empty it, put the data in global memory blocks, and call
SetClipboardData for each format. Then close the clipboard. The
WM_RENDERALLFORMATS message is one of the last messages your window
procedure receives. It is followed by a WM_DESTROYCLIPBOARD message (because
you've rendered all the data) and then the normal WM_DESTROY.

If your program can transfer only one format of data to the clipboard (text,
for instance), you can combine the WM_RENDERALLFORMATS and WM_RENDERFORMAT
processing. The code will look something like this:

case WM_RENDERALLFORMATS :
     OpenClipboard (hwnd) ;
     EmptyClipboard () ;
                              // fall through
case WM_RENDERFORMAT :

[put text into global memory block]     SetClipboardData (CF_TEXT, hMem) ;

     if (iMessage == WM_RENDERALLFORMATS)
          CloseClipboard () ;
     return 0 ;

If your program uses several clipboard formats, then you will want to
process the WM_RENDERFORMAT message only for the format requested by wParam.
You don't need to process the WM_DESTROYCLIPBOARD message unless it is
burdensome for your program to retain the information necessary to construct
the data.


Private Data Formats

So far we've dealt with only the standard clipboard formats defined by
Windows. However, you may want to use the clipboard to store a "private data
format." The Windows WRITE program uses this technique to store text that
contains font and formatting information.

At first, this concept may seem nonsensical. If the purpose of the clipboard
is to transfer data between applications, why should the clipboard contain
data that only one application understands? The answer is simple: The
clipboard also exists to allow the transfer of data between different
instances of the same program, and these instances obviously understand the
same private formats.

There are several ways to use private data formats. The easiest involves
data that is ostensibly in one of the standard clipboard formats (text,
bitmap, or metafile) but that has meaning only to your program. In this
case, you use one of the following wFormat values in your SetClipboardData
and GetClipboardData calls: CF_DSPTEXT, CF_DSPBITMAP, or CF_DSPMETAFILEPICT.
DSP stands for "display"--these formats allow CLIPBOARD to display the data
as text, a bitmap, or a metafile. However, another program that calls
GetClipboardData using the normal CF_TEXT, CF_BITMAP, or CF_METAFILEPICT
format won't obtain this data.

If you use one of these formats to put data in the clipboard, you must also
use the same format to get the data out. But how do you know if the data is
from another instance of your program or from another program using one of
these formats? Here's one way: You can first obtain the clipboard owner by
calling:

hwndClipOwner = GetClipboardOwner () ;

You can then get the name of the window class of this window handle:

char szClassName [16] ;

[other program lines]GetClassName (hwndClipOwner, &szClassName, 16) ;

If the class name is the same as your program's, then the data was put into
the clipboard by another instance of your program.

The second way to use private formats involves the CF_OWNERDISPLAY flag. The
global memory handle to SetClipboardData is NULL:

SetClipboardData (CF_OWNERDISPLAY, NULL) ;

This is the method that WRITE uses to show formatted text in the client area
of the CLIPBOARD clipboard viewer. Obviously, CLIPBRD.EXE doesn't know how
to display this formatted text. When WRITE specifies the CF_OWNERDISPLAY
format, WRITE is taking responsibility for painting CLIPBOARD's client area.

Because the global memory handle is NULL, a program that calls
SetClipboardData with the CF_OWNERDISPLAY format (the clipboard owner) must
process the delayed rendering messages sent to the clipboard owner by
Windows as well as five additional messages. These five messages are sent by
the clipboard viewer to the clipboard owner:

  þ   WM_ASKCBFORMATNAME--The clipboard viewer sends this message to the
      clipboard owner to get a name for the format of the data. The lParam
      parameter is a pointer to a buffer, and wParam is the maximum number
      of characters for this buffer. The clipboard owner must copy the name
      of the clipboard format into this buffer.

  þ   WM_SIZECLIPBOARD--This message tells the clipboard owner that the size
      of the clipboard viewer's client area has changed. The wParam
      parameter is a handle to the clipboard viewer, and lParam is a pointer
      to a RECT structure containing the new size. If the RECT structure
      contains all zeros, the clipboard viewer is being destroyed or made an
      icon. Although CLIPBRD.EXE allows only one instance of itself to be
      running, other clipboard viewers can also send this message to the
      clipboard owner. Handling these multiple clipboard viewers isn't
      impossible for the clipboard owner (given that wParam identifies the
      particular viewer), but it isn't easy, either.

  þ   WM_PAINTCLIPBOARD--This message tells the clipboard owner to update
      the clipboard viewer's client area. Again, wParam is a handle to the
      clipboard viewer's window. The lParam parameter is a pointer to a
      PAINTSTRUCT structure. The clipboard owner can obtain a handle to the
      clipboard viewer's device context from the hdc field of this
      structure.

  þ   WM_HSCROLLCLIPBOARD and WM_VSCROLLCLIPBOARD--These messages inform the
      clipboard owner that a user has scrolled the clipboard viewer's scroll
      bars. The wParam parameter is a handle to the clipboard viewer's
      window, the low word of lParam is the scrolling request (the same as
      wParam in normal scroll bar messages), and the high word of lParam is
      the thumb position if the low word is SB_THUMBPOSITION. (This value is
      the same as the low word of lParam in a normal scroll bar message.)

Handling these messages may look like more trouble than it's worth. However,
the process does provide a benefit to the user: When copying text from WRITE
to the clipboard, the user will find it comforting to see the text still
formatted in CLIPBOARD's client area.

The third way to use private clipboard data formats is to register your own
clipboard format name. You supply a name for this format to Windows, and
Windows gives your program a number to use as the format parameter in
SetClipboardData and GetClipboardData. Programs that use this method
generally also copy data to the clipboard in one of the standard formats.
This approach allows CLIPBRD.EXE to display data in its client area (without
the hassles involved with CF_OWNERDISPLAY) and permits other programs to
copy data from the clipboard.

As an example, let's assume we've written a vector drawing program that
copies data to the clipboard in a bitmap format, a metafile format, and its
own registered clipboard format. CLIPBRD.EXE will display the metafile.
Other programs that can read bitmaps or metafiles from the clipboard will
obtain those formats. However, when the vector drawing program itself needs
to read data from the clipboard, it will copy the data in its own registered
format, because that format probably contains more information than the
bitmap or metafile.

A program registers a new clipboard format by calling:

wFormat = RegisterClipboardFormat (lpszFormatName) ;

The wFormat value is between 0xC000 and 0xFFFF. A clipboard viewer (or a
program that obtains all the current clipboard formats by calling
EnumClipboardFormats) can obtain the ASCII name of this format by calling:

GetClipboardFormatName (wFormat, lpsBuffer, nMaxCount) ;

Windows copies up to nMaxCount characters into lpsBuffer.

Programmers who use this method for copying data to the clipboard might want
to publicize the format name and the actual format of the data. If the
program becomes popular, other programs can then copy data in this format
from the clipboard.



BECOMING A CLIPBOARD VIEWER

A program that is notified of changes in the clipboard contents is called a
"clipboard viewer." The CLIPBOARD program that comes with Windows is a
clipboard viewer, but you can also write your own clipboard viewer program.
Clipboard viewers are notified of changes to the clipboard through messages
to the viewer's window procedure.

The Clipboard Viewer Chain

Any number of clipboard viewer applications can be running in Windows at the
same time, and they can all be notified of changes to the clipboard. From
Windows' perspective, however, there is only one clipboard viewer, which
I'll call the "current clipboard viewer." Windows maintains only one window
handle to identify the current clipboard viewer, and it sends messages only
to that window when the contents of the clipboard change.

Clipboard viewer applications have the responsibility of participating in
the "clipboard viewer chain" so that all running clipboard viewer programs
receive the messages that Windows sends to the current clipboard viewer.
When a program registers itself as a clipboard viewer, that program becomes
the current clipboard viewer. Windows gives that program the window handle
of the previous current clipboard viewer, and the program saves this handle.
When the program receives a clipboard viewer message, it sends that message
to the window procedure of the next program in the clipboard chain.


Clipboard Viewer Functions and Messages

A program can become part of the clipboard viewer chain by calling the
SetClipboardViewer function. If the primary purpose of the program is to
serve as a clipboard viewer, the program can call this function during
processing of the WM_CREATE message. The function returns the window handle
of the previous current clipboard viewer. The program should save that
handle in a static variable:

static HWND hwndNextViewer ;
[other program lines]
case WM_CREATE :
[other program lines]
     hwndNextViewer = SetClipboardViewer (hwnd) ;

If your program is the first program to become a clipboard viewer during the
Windows session, then hwndNextViewer will be NULL.

Windows sends a WM_DRAWCLIPBOARD message to the current clipboard viewer
(the most recent window to register itself as a clipboard viewer) whenever
the contents of the clipboard change. Each program in the clipboard viewer
chain should use SendMessage to pass this message to the next clipboard
viewer. The last program in the clipboard viewer chain (the first window to
register itself as a clipboard viewer) will have stored a NULL
hwndNextViewer value. If hwndNextViewer is NULL, the program simply returns
without sending the message to another program. (Don't confuse the
WM_DRAWCLIPBOARD and WM_PAINTCLIPBOARD messages. The WM_PAINTCLIPBOARD
message is sent by a clipboard viewer to programs that use the
CF_OWNERDISPLAY clipboard format. The WM_DRAWCLIPBOARD message is sent by
Windows to the current clipboard viewer.)

The easiest way to process the WM_DRAWCLIPBOARD message is to send the
message to the next clipboard viewer (unless hwndNextViewer is NULL) and
invalidate the client area of your window:

case WM_DRAWCLIPBOARD :
     if (hwndNextViewer)
          SendMessage (hwndNextViewer, iMessage, wParam, lParam) ;

     InvalidateRect (hwnd, NULL, TRUE) ;
     return 0 ;

During processing of the WM_PAINT message, you can read the contents of the
clipboard by using the normal OpenClipboard, GetClipboardData, and
CloseClipboard calls.

When a program wishes to remove itself from the clipboard viewer chain, it
must call ChangeClipboardChain. This function requires the window handle of
the program leaving the viewer chain and the window handle of the next
clipboard viewer:

ChangeClipboardChain (hwnd, hwndNextViewer) ;

When a program calls ChangeClipboardChain, Windows sends a
WM_CHANGECLIPBOARD message to the current clipboard viewer. The wParam
parameter is the handle of the window removing itself from the chain (the
first parameter to ChangeClipboardChain), and the low word of lParam is the
window handle of the next clipboard viewer after the one removing itself
from the chain (the second parameter to ChangeClipboardChain).

When your program receives a WM_CHANGECLIPBOARD message, you must therefore
check to see if wParam is equal to the value of hwndNextViewer that you've
saved. If it is, your program must set hwndNextViewer to the low word of
lParam. This action ensures that any future WM_DRAWCLIPBOARD messages you
get won't be sent to the window removing itself from the clipboard viewer
chain. If wParam isn't equal to hwndNext- Viewer, and hwndNextViewer isn't
NULL, send the message to the next clipboard viewer:

case WM_CHANGECBCHAIN :
     if (wParam == hwndNextViewer)
          hwndNextViewer = LOWORD (lParam) ;

     else if (hwndNextViewer)
          SendMessage (hwndNextViewer, iMessage, wParam, lParam) ;
     return 0 ;

You shouldn't need to include the else if statement, which checks
hwndNextViewer for a non-NULL value. A NULL hwndNextViewer value would
indicate that the program executing this code is the last viewer on the
chain, in which case the message should never have gotten this far.

If your program is still in the clipboard viewer chain when it is about to
terminate, you must remove your program from the chain. You can do this
during processing of the WM_DESTROY message by calling ChangeClipboardChain.

case WM_DESTROY :
     ChangeClipboardChain (hwnd, hwndNextViewer) ;
     PostQuitMessage (0) ;
     return 0 ;

Windows also has a function that allows a program to obtain the window
handle of the first clipboard viewer:

hwndViewer = GetClipboardViewer () ;

This function isn't normally needed. The return value can be NULL if there
is no current clipboard viewer.

Here's an example to illustrate how the clipboard viewer chain works. When
Windows first starts up, the current clipboard viewer is NULL:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Current clipboard viewer:               NULL

A program with a window handle of hwnd1 calls SetClipboardViewer. The
function returns NULL, which becomes the hwndNextViewer value in this
program:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Current clipboard viewer:               hwnd1
hwnd1's next viewer:                    NULL

A second program with a window handle of hwnd2 now calls SetClipboardViewer
and gets back hwnd1:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Current clipboard viewer:               hwnd2
hwnd2's next viewer:                    hwnd1
hwnd1's next viewer:                    NULL

A third program (hwnd3) and then a fourth (hwnd4) also call
SetClipboardViewer and get back hwnd2 and hwnd3:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Current clipboard viewer:               hwnd4
hwnd4's next viewer:                    hwnd3
hwnd3's next viewer:                    hwnd2
hwnd2's next viewer:                    hwnd1
hwnd1's next viewer:                    NULL

When the contents of the clipboard change, Windows sends a WM_DRAWCLIPBOARD
message to hwnd4, hwnd4 sends the message to hwnd3, hwnd3 sends it to hwnd2,
hwnd2 sends it to hwnd1, and hwnd1 returns.

Now hwnd2 decides to remove itself from the chain by calling:

ChangeClipboardChain (hwnd2, hwnd1) ;

Windows sends hwnd4 a WM_CHANGECBCHAIN message with wParam equal to hwnd2
and the low word of lParam equal to hwnd1. Because hwnd4^s next viewer is
hwnd3, hwnd4 sends this message to hwnd3. Now hwnd3 notes that wParam is
equal to its next viewer (hwnd2), so it sets its next viewer equal to the
low word of lParam (hwnd1) and returns. The mission is accomplished. The
clipboard viewer chain now looks like this:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Current clipboard viewer:               hwnd4
hwnd4's next viewer:                    hwnd3
hwnd3's next viewer:                    hwnd1
hwnd1's next viewer:                    NULL


A Simple Clipboard Viewer

Clipboard viewers don't have to be as sophisticated as CLIPBRD.EXE. A
clipboard viewer can, for instance, display only one clipboard format. The
CLIPVIEW program, shown in Figure 16-2, is a clipboard viewer that displays
only the CF_TEXT format.

 CLIPVIEW.MAK

#------------------------
# CLIPVIEW.MAK make file
#------------------------

clipview.exe : clipview.obj clipview.def
     link clipview, /align:16, NUL, /nod slibcew libw, clipview
     rc clipview.exe

clipview.obj : clipview.c
     cl -c -Gsw -Ow -W2 -Zp clipview.c

 CLIPVIEW.C

/*-----------------------------------------
   CLIPVIEW.C -- Simple Clipboard Viewer
                 (c) Charles Petzold, 1990
  -----------------------------------------*/

#include 

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static    char szAppName [] = "ClipView" ;
     HWND      hwnd ;
     MSG       msg ;
     WNDCLASS  wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = NULL ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Simple Clipboard Viewer (Text Only)",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static HWND hwndNextViewer ;
     HANDLE      hGMem ;
     HDC         hdc ;
     LPSTR       lpGMem ;
     PAINTSTRUCT ps ;
     RECT        rect ;

     switch (message)
          {
          case WM_CREATE :
               hwndNextViewer = SetClipboardViewer (hwnd) ;
               return 0 ;

          case WM_CHANGECBCHAIN :
               if (wParam == hwndNextViewer)
                    hwndNextViewer = LOWORD (lParam) ;

               else if (hwndNextViewer)
                    SendMessage (hwndNextViewer, message, wParam, lParam) ;

               return 0 ;

          case WM_DRAWCLIPBOARD :
               if (hwndNextViewer)
                    SendMessage (hwndNextViewer, message, wParam, lParam) ;

               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

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

               if (hGMem = GetClipboardData (CF_TEXT))
                    {
                    lpGMem = GlobalLock (hGMem) ;
                    DrawText (hdc, lpGMem, -1, &rect, DT_EXPANDTABS) ;
                    GlobalUnlock (hGMem) ;
                    }

               CloseClipboard () ;
               EndPaint (hwnd, &ps) ;
               return 0 ;
          case WM_DESTROY :
               ChangeClipboardChain (hwnd, hwndNextViewer) ;
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 CLIPVIEW.DEF

;-------------------------------------
; CLIPVIEW.DEF module definition file
;-------------------------------------

NAME           CLIPVIEW

DESCRIPTION    'Simple Clipboard Viewer (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

CLIPVIEW processes WM_CREATE, WM_CHANGECBCHAIN, WM_DRAWCLIPBOARD, and
WM_DESTROY messages as discussed above. The WM_PAINT message simply opens
the clipboard and uses GetClipboardData with a format of CF_TEXT. If the
function returns a global memory handle, CLIPVIEW locks it and uses DrawText
to display the text in its client area.

A clipboard viewer that handles data formats beyond the five standard
formats (as CLIPBRD.EXE does) has additional work to do, such as displaying
the names of all the formats currently in the clipboard. You can do this by
calling EnumClipboardFormats and obtaining the names of the nonstandard
formats from GetClipboardFormatName. A clipboard viewer that uses the
CF_OWNERDISPLAY format must send these four messages to the clipboard to
display the data:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
WM_PAINTCLIPBOARD             WM_VSCROLLCLIPBOARD
WM_SIZECLIPBOARD              WM_HSCROLLCLIPBOARD

If you want to write such a clipboard viewer, you have to obtain the window
handle of the clipboard owner using GetClipboardOwner and send that window
these messages when you need to update the clipboard viewer's client area.