Observe your clipboard – like a BOSS!

Observing the users clipboard in C#… yeha, no big deal i thought in the first place. .NET comes with this nice Clipboard class, that has a bunch of static methods for reading and writing all kinds of clipboard data. But that’s it. No events to hook, no way to get notified when the content of the clipboard changes. dafuq?

Time to ask google, but the result is pretty unpleasant. Actual there are two ways to observe the users clipboard and both of them rely on win32 API calls and need a window handle…

Solution #1
This one works for all version of Windows but ist the more ugly one. The observers are organized as a liked list. But the catch is, every observer needs to manage the reference of it’s follower on it’s own. So after adding yourself as a listener / observer / viewer you get the handle of the next observer. After receiving a WM_DRAWCLIPBOARD message through the WndProc method, you must forward the message using SendMessage. When a application unregisters itself, you receive a WM_DRAWCLIPBOARD message and must probably re-organize your reference to the next observer. Long story short, no matter if your code is correct, if someone else has written bad code, the whole list gets messed up and your application is going down.

private const int WM_DRAWCLIPBOARD = 0x308;
private const int WM_CHANGECBCHAIN = 0x030D;

[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);

[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);

Solution #2
It took some a long time, till Microsoft realized that this was horrible API design. On Windows Vista they came up with an alternative. This one is straight forward. Using Add/RemoveClipboardFormatListener you register your window (yeha, you always need one…) for receiving WM_CLIPBOARDUPDATE messages. That’s it.

private const int WM_CLIPBOARDUPDATE = 0x031D;

[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool AddClipboardFormatListener(IntPtr hWndListener);

[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool RemoveClipboardFormatListener(IntPtr hWndListener);

Solution #3
Something like this… *smiliethatpukesoverakeyboard*

var observerThread = new Thread(() =>
{
    string lastText;
    while (!pleaseKillMe)
    {
        if (!lastText.Equals(Clipboard.GetText()))
        {
            /* notify */
            lastText = Clipboard.GetText();
        }
    }
});
observerThread.SetApartmentState(ApartmentState.STA);
observerThread.Start();

While using solution #3 in the first place, i could not bear how dead ugly it was. So i took solution 1+2, added a dummy window (what is nearly as ugly as solution #3…), an automatic version switch to use the more sable & sexy API on newer windows machines and packed it all together into a small reusable component. Now observing the clipboard is as easy as i thought in the first place :)

var clipboardObserver = new ClipboardObserver();
clipboardObserver.ClipboardTextChanged += newText => Console.WriteLine(newText);

Want to save an hour of your time and simply use the component instead or reinventing it? Or extend it to listen on other clipboard data such as Audio / Image / DataObject?

Download VS10 Solution

Advertisements

Tags: , , , , ,

About jlorek

code fast - die young

Trackbacks / Pingbacks

  1. [C#/PowerShell] Clipboard Watcher | mnaoumov.NET - 30. August 2013

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: