针对 XP 及以前的监视剪贴板更改的方法就不讲了,因为 XP 已严重过时。本篇老周介绍的方法面向 Vista 以上的系统。
在托管应用程序中监听剪贴板更新行为必须用到 Win 32 API ,具体做法,我先简单说一下。
首先,调用 AddClipboardFormatListener 函数来向窗口注册监听行为,它需要一个窗口句柄作为传入参数,该句柄所指的窗口即是监听剪贴板更新的窗口。
然后,当剪贴板的内容被更新,处理程序会收到一条 WM_CLIPBOARDUPDATE 消息。我们在应用程序中,只要收到这条消息,就说明剪贴板的内容已被更新。
WM_CLIPBOARDUPDATE 消息的宏定义如下:
#define WM_CLIPBOARDUPDATE 0x031D
这个消息的 wParam 和 lParam 参数都不曾使用,所以我们不必理会这两个参数值。如果用户已处理该消息,应当返回 0。
AddClipboardFormatListener 函数的原型如下:
BOOL WINAPI AddClipboardFormatListener(
_In_ HWND hwnd
);
在托管代码中调用它,要先进行导入。
[DllImport("User32.dll")] static extern bool AddClipboardFormatListener(IntPtr hwnd);
好,基本理论说完了,下面我们来看看如何在WPF程序中监听剪贴板更新。
由于此功能实为WPF与 Win32 的交互操作,因此,要用到 HwndSource 类,这个类公开了一个 AddHook 方法,调用这个方法可以添加一个 HwndSourceHook 委托实例,当窗口接收到消息时,就会调用这个委托。
该委托的定义如下。
delegate IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled);
你一定会猛然发现,这个委托很像 WinProc 函数指针。msg 参数就是被拦截到的消息。在与该委托绑定的方法中,我们可以对收到的消息进行筛选,因为我们这里只关心 WM_CLIPBOARDUPDATE 消息,其他的咱们不管。
private IntPtr OnHooked(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_CLIPBOARDUPDATE) { ClipboardUpdated?.Invoke(this, EventArgs.Empty); return IntPtr.Zero; } return IntPtr.Zero; }
ClipboardUpdated 事件是我封装时定义的,这是为了方便引发。
public event EventHandler ClipboardUpdated;
还有一件事,各位会发现,HwndSource 实例创建时需要与一个窗口的句柄绑定,那么,如何获取到 Window 实例的句柄呢,这就要用到一个帮助类—— WindowInteropHelper。有了它,想得到窗口的句柄就很简单了。
WindowInteropHelper helper = new WindowInteropHelper(window); _hwndSource = HwndSource.FromHwnd(helper.Handle);
在添加 hook 处理之前,一定要记得调用 AddClipboardFormatListener 函数为窗口注册监听行为。
bool r = AddClipboardFormatListener(_hwndSource.Handle);
要是监听行为注册成功,就可以添加 hook 了。
if (r) { _hwndSource.AddHook(new HwndSourceHook(OnHooked)); }
那么,咱们封装的这些代码如何用到窗口代码中呢。Window 有一个 SourceInitialized 事件,当句柄初始化完成就会发生。我们可以重写 OnSourceInitialized 方法,然后在方法中使用我们上面封装的代码。
ClipboardHooker m_clipboardHooker; protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); m_clipboardHooker = new ClipboardHooker(this); m_clipboardHooker.ClipboardUpdated += OnClipboardUpdated; } private void OnClipboardUpdated(object sender, EventArgs e) { tb.Text = "老板,有人修改了剪贴板。"; IDataObject data = Clipboard.GetDataObject(); string[] fs = data.GetFormats(); tb.Text += $"\n数据格式:{string.Join("、", fs)}"; }
只要监听到剪贴板被更新,那么要获取剪贴板上的数据就很容易了,因为System.Windows下面已经有一个 Clipboard 类,它有一堆静态方法,可以直接读写剪贴板上的内容。
运行程序后,随便复制点东东到剪贴板中,就会看到程序有反应了。
好了,本文就扯到这里了。