The following class monitors the clipboard and raises an event whenever the contents change. The monitoring starts when the class is created and ends when Dispose
is called.
To achieve this a message-only window (a term that comes from the Win32 API) is created using an instance of HwndSource
class (since I am using WPF). The window's handle is then registered via AddClipboardFormatListener
to receive a message whenever the clipboard changes. The window's messages are processed by WndProc
which raises the event when the WM_CLIPBOARDUPDATE
message comes.
In other solutions for monitoring the clipboard I saw this was done on the actual main window of the application, but I wanted to separate the logic of monitoring the clipboard form the View and keep a clean MVVM. In an application I would instantiate the ClipboardMonitor
in the ViewModel.
I am interested if there may be any hidden pitfalls where this approach may go wrong.
public sealed class ClipboardMonitor : IDisposable
{
private static class NativeMethods
{
/// <summary>
/// Places the given window in the system-maintained clipboard format listener list.
/// </summary>
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AddClipboardFormatListener(IntPtr hwnd);
/// <summary>
/// Removes the given window from the system-maintained clipboard format listener list.
/// </summary>
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
/// <summary>
/// Sent when the contents of the clipboard have changed.
/// </summary>
public const int WM_CLIPBOARDUPDATE = 0x031D;
/// <summary>
/// To find message-only windows, specify HWND_MESSAGE in the hwndParent parameter of the FindWindowEx function.
/// </summary>
public static IntPtr HWND_MESSAGE = new IntPtr(-3);
}
private HwndSource hwndSource = new HwndSource(0, 0, 0, 0, 0, 0, 0, null, NativeMethods.HWND_MESSAGE);
public ClipboardMonitor()
{
hwndSource.AddHook(WndProc);
NativeMethods.AddClipboardFormatListener(hwndSource.Handle);
}
public void Dispose()
{
NativeMethods.RemoveClipboardFormatListener(hwndSource.Handle);
hwndSource.RemoveHook(WndProc);
hwndSource.Dispose();
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == NativeMethods.WM_CLIPBOARDUPDATE)
{
OnClipboardContentChanged?.Invoke(this, EventArgs.Empty);
}
return IntPtr.Zero;
}
/// <summary>
/// Occurs when the clipboard content changes.
/// </summary>
public event EventHandler OnClipboardContentChanged;
}