Monitoring Clipboard Activity in C#

Welcome to this week’s installment of .NET Tips & Techniques! Each week, award-winning Architect and Lead Programmer Tom Archer demonstrates how to perform a practical .NET programming task using either C# or Managed C++ Extensions.

My MFC book Visual C++ .NET Bible includes a chapter on working with the Windows Clipboard from Visual C++. Quite a number of readers have asked about how to perform similar tasks in C#. One of those tasks—which the “Clipboard ring” in Microsoft Office has made popular—is specifying that your application is notified if the Clipboard changes. This week’s installment of .NET Tips & Techniques provides both an overview and a step-by-step example for accomplishing this with .NET and C#. That way, if you’re already familiar with how the Clipboard works (or you’re in a hurry) and just want to jump directly to the code, you can.

Figure 1: Example of Monitoring the Windows Clipboard for Any RTF or ASCII Text

Overview

Windows maintains a list, or chain, of windows that have requested to be notified when data on the Clipboard has been modified. Each time the Clipboard data is modified, the first window in this chain receives a message—WM_DRAWCLIPBOARD. The window then can query the Clipboard as to the type of data it contains (such as RTF, ASCII text, and so on) and the actual data. Because there is no managed (.NET) API for adding a window to this notification chain, you have to use the Win32 SetClipboardViewer function. While this is a fairly simple process, you should be aware of some general guidelines when using this function.

  • When calling the SetClipboardViewer function, you need to pass the handle of the window that will receive the WM_DRAWCLIPBOARD message. The SetClipboardViewer function returns the current first window in the chain. Your application should store this value—typically in a class member—because each window that receives the WM_DRAWCLIPBOARD message has to send that same message to the next window in the chain (via the SendMessage function).
  • Handle the WM_DRAWCLIPBOARD message. This can be done by providing a Form class overload of the WndProc method. You’ll see an example of doing this shortly.
  • Handle WM_CHANGECBCHAIN message. Because each window that handles the WM_DRAWCLIPBOARD message is responsible for sending that message to the next window in the chain, it must also know when the chain changes. The Clipboard sends the WM_CHANGECBCHAIN message when a window has removed itself from the chain.
  • Remove the window from the chain when finished. This task is accomplished via the Win32 ChangeClipboardChain function, and it can be done any time that Clipboard monitoring is no longer needed.

Step-by-step Instructions

  1. As mentioned in the Overview, you’ll need to call several Win32 functions—SetClipboardViewer, ChangeClipboardChain, and SendMessage—in your application. In order to do that from a .NET application, you first have to import those functions by using the DllImport attribute (which resides in the System.Runtime.InteropServices namespace). The following example imports these functions within the demo application’s Form class:
  2. using System.Runtime.InteropServices;
    ...
    public class Form1 : System.Windows.Forms.Form
    {
      [DllImport("User32.dll")]
      protected static extern int
                SetClipboardViewer(int hWndNewViewer);
    
      [DllImport("User32.dll", CharSet=CharSet.Auto)]
      public static extern bool
             ChangeClipboardChain(IntPtr hWndRemove,
                                  IntPtr hWndNewNext);
    
      [DllImport("user32.dll", CharSet=CharSet.Auto)]
      public static extern int SendMessage(IntPtr hwnd, int wMsg,
                                           IntPtr wParam,
                                           IntPtr lParam);
      ...
    
  3. Define a class member to hold the current first window in the Clipboard notification chain:
  4. public class Form1 : System.Windows.Forms.Form
    {
      ...
      IntPtr nextClipboardViewer;
    
  5. Call the SetClipboardViewer function. In the demo, I call this function in the form’s constructor:
  6. public Form1()
    {
      InitializeComponent();
      nextClipboardViewer = (IntPtr)SetClipboardViewer((int)
                             this.Handle);
      ..
    
  7. Within the Form class, override the WndProc method. As you can see here, I handle only two messages: WM_DRAWCLIPBOARD and WM_CHANGECBCHAIN. Note that I define two constants for these messages (where both values can be found in the Platform SDK’s winuser.h file.)

    In the WM_DRAWCLIPBOARD message-handling code, I call a helper function to display the text that is currently on the Clipboard and pass the same message on to the next window in the chain. (You can see the code to display RTF and ASCII text in the article’s demo code at the end of this column.)

    In the WM_CHANGECBCHAIN message-handling code, I check to see whether the window being removed from the Clipboard chain (passed in the Message.WParam member) is the next window in the chain. If it is, I then set the form’s next window member variable (nextClipboardViewer) to the next window in the chain (passed in the Message.LParam member):

    protected override void
              WndProc(ref System.Windows.Forms.Message m)
    {
      // defined in winuser.h
      const int WM_DRAWCLIPBOARD = 0x308;
      const int WM_CHANGECBCHAIN = 0x030D;
    
      switch(m.Msg)
      {
        case WM_DRAWCLIPBOARD:
          DisplayClipboardData();
          SendMessage(nextClipboardViewer, m.Msg, m.WParam,
                      m.LParam);
        break;
    
        case WM_CHANGECBCHAIN:
          if (m.WParam == nextClipboardViewer)
            nextClipboardViewer = m.LParam;
          else
            SendMessage(nextClipboardViewer, m.Msg, m.WParam,
                        m.LParam);
        break;
    
        default:
          base.WndProc(ref m);
        break;
      }
    }
    
  8. Finally, I remove the window from the Clipboard chain when the window class’s Dispose method is called by the .NET runtime:
  9. protected override void Dispose( bool disposing )
    {
      ChangeClipboardChain(this.Handle, nextClipboardViewer);
      ...
    

A Chain Is Only As Strong…

After following these few steps, your application will be notified of any changes to the text on the Clipboard. Like a lot of tasks in Windows development, it’s not very difficult once you know the right APIs to call. The key issue with working with the Clipboard is to make sure that you follow a few simple rules so that other applications in the Clipboard chain continue to perform correctly.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read