Get a site

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

Becoming a Clipboard Viewer

A program that is notified of changes in the clipboard contents is called a "clipboard viewer." You get a clipboard viewer with Windows, 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, message, 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 wants 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_CHANGECBCHAIN message to the current clipboard viewer. The wParam parameter is the handle of the window removing itself from the chain (that is, the first parameter to ChangeClipboardChain), and lParam is the window handle of the next clipboard viewer after the one removing itself from the chain (the second argument to ChangeClipboardChain).

When your program receives a WM_CHANGECBCHAIN 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 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 hwndNextViewer and hwndNextViewer isn't NULL, send the message to the next clipboard viewer:

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

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

You shouldn't really 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 it 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 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 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 the one supplied with Windows. A clipboard viewer can, for instance, display a single clipboard format. The CLIPVIEW program, shown in Figure 12-2, is a clipboard viewer that displays only the CF_TEXT format.

Figure 12-2. The CLIPVIEW program.

CLIPVIEW.C

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

#include <windows.h>


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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("ClipView") ;
     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  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, 
                          TEXT ("Simple Clipboard Viewer (Text Only)"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HWND hwndNextViewer ;
     HGLOBAL     hGlobal ;
     HDC         hdc ;
     PTSTR       pGlobal ;
     PAINTSTRUCT ps ;
     RECT        rect ;
     
     switch (message)
     {

     case WM_CREATE:
          hwndNextViewer = SetClipboardViewer (hwnd) ;
          return 0 ;
          
     case WM_CHANGECBCHAIN:
          if ((HWND) wParam == hwndNextViewer)
               hwndNextViewer = (HWND) 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) ;
          
#ifdef UNICODE
          hGlobal = GetClipboardData (CF_UNICODETEXT) ;
#else
          hGlobal = GetClipboardData (CF_TEXT) ;
#endif
          
          if (hGlobal != NULL)
          {
               pGlobal = (PTSTR) GlobalLock (hGlobal) ;
               DrawText (hdc, pGlobal, -1, &rect, DT_EXPANDTABS) ;
               GlobalUnlock (hGlobal) ;
          }
          
          CloseClipboard () ;
          EndPaint (hwnd, &ps) ;
          return 0 ;
          
     case WM_DESTROY:
          ChangeClipboardChain (hwnd, hwndNextViewer) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

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 standard formats (as the one supplied with Windows 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 the following four messages to the clipboard owner 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.