Win32 Series - Becoming a Clipboard Viewer

慕朝明
2023-12-01

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

 

 

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, thenhwndNextViewer 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  NULLhwndNextViewer 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 normalOpenClipboardGetClipboardData, and CloseClipboard calls.

When a program wants to remove itself from the clipboard viewer chain, it must  callChangeClipboardChain. 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 ifwParam is equal to the value of hwndNextViewer that you've saved. If it is, your program must set hwndNextViewer tolParam. 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. IfwParam 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 callSetClipboardViewer and get backhwnd2 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 tohwnd4hwnd4 sends the message to hwnd3hwnd3 sends it tohwnd2hwnd2 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 tohwnd2 and lParam equal to hwnd1. Because  hwnd4's next viewer ishwnd3hwnd4 sends this message to hwnd3. Now  hwnd3 notes thatwParam 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_PAINTCLIPBOARDWM_VSCROLLCLIPBOARD
WM_SIZECLIPBOARDWM_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.

 

 类似资料:

相关阅读

相关文章

相关问答