首页 > 解决方案 > 如何获取已更新剪贴板的应用程序的进程 ID 或名称?

问题描述

我在 C# 中创建一个剪贴板管理器,有时我会遇到一些应用程序将剪贴板设置为空。

当取消选择刚刚复制的内容时,例如 Excel 中会发生这种情况,因此我需要确定剪贴板是否为空,但是如何获取更新剪贴板的应用程序名称

我希望我能以某种方式获得HWnd更新剪贴板的应用程序的句柄,这样我就可以使用以下代码查找其背后的进程:

[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
...

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case WM_CLIPBOARDUPDATE:
            // How to get the "handle" HWnd?
            IntPtr handle = ??? <============= HOW TO GET THIS ONE ???

            // Get the process ID from the HWnd
            uint processId = 0;
            GetWindowThreadProcessId(handle, out processId);

            // Get the process name from the process ID
            string processName = Process.GetProcessById((int)processId).ProcessName;

            Console.WriteLine("Clipboard UPDATE event from [" + processName + "]");
            break;
        }
        default:
            base.WndProc(ref m);
            break;
    }
}

我希望我可以使用HWndfromMessage对象,但这似乎是我自己的应用程序 - 可能是用这个进程 ID 通知应用程序:

这是我在更改剪贴板时从 C# 中的快速视图中得到的

如果我能以任何其他方式得到它,那么这当然也完全没问题,但我会很感激对此的任何见解:-)


解决方案

根据@Jimi 的回答,这很简单。我可以将以下 3 行添加到我的原始代码中:

// Import the "GetClipboardOwner" function from the User32 library
[DllImport("user32.dll")]
public static extern IntPtr GetClipboardOwner();
...

// Replace the original line with "HOW TO GET THIS ONE" with this line below - this will give the HWnd handle for the application that has changed the clipboard:
IntPtr handle = GetClipboardOwner();

标签: c#.netwinformsprocessclipboard

解决方案


您可以调用GetClipboardOwner()来获取上次设置或清除剪贴板(触发通知的操作)的窗口句柄。

[...] 通常,剪贴板所有者是最后在剪贴板中放置数据的窗口。
EmptyClipboard 函数分配剪贴板所有权。

当 Process 将空句柄传递给OpenClipboard()时存在特殊情况:请阅读此函数的备注部分和EmptyClipboard函数。

在调用 EmptyClipboard 之前,应用程序必须使用 OpenClipboard 函数打开剪贴板。如果应用程序在打开剪贴板时指定了 NULL 窗口句柄,则 EmptyClipboard 会成功,但会将剪贴板所有者设置为 NULL。请注意,这会导致 SetClipboardData 失败。


▶ 这里我使用NativeWindow派生类来设置剪贴板监听器。创建处理剪贴板更新消息的窗口,初始化一个CreateParams对象并将此参数传递给NativeWindow.CreateHandle(CreateParams)方法,以创建一个不可见的窗口。
然后重写WndProc初始化的 NativeWindow,接收WM_CLIPBOARDUPDATE通知。

AddClipboardFormatListener函数用于将 Window 放置在系统剪贴板侦听器链中

ClipboardUpdateMonitor当收到剪贴板通知时,该类会生成一个事件。事件中传递的自定义ClipboardChangedEventArgs对象包含剪贴板所有者的句柄,由GetClipboardOwner(),ThreadIdGetWindowThreadProcessId()ProcessId返回,以及由Process.GetProcessById()标识的进程名称。

你可以像这样设置一个ClipboardUpdateMonitor对象:
这个类也可以在Program.cs

private ClipboardUpdateMonitor clipboardMonitor = null;
// [...]

clipboardMonitor = new ClipboardUpdateMonitor();
clipboardMonitor.ClipboardChangedNotify += this.ClipboardChanged;
// [...]

private void ClipboardChanged(object sender, ClipboardChangedEventArgs e)
{
    Console.WriteLine(e.ProcessId);
    Console.WriteLine(e.ProcessName);
    Console.WriteLine(e.ThreadId);
}

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Forms;

public sealed class ClipboardUpdateMonitor : IDisposable
{
    private bool isDisposed = false;
    private static ClipboardWindow window = null;
    public event EventHandler<ClipboardChangedEventArgs> ClipboardChangedNotify;

    public ClipboardUpdateMonitor()
    {
        window = new ClipboardWindow();
        if (!NativeMethods.AddClipboardFormatListener(window.Handle)) {
            throw new TypeInitializationException(nameof(ClipboardWindow), 
                new Exception("ClipboardFormatListener could not be initialized"));
        }
        window.ClipboardChanged += ClipboardChangedEvent;
    }

    private void ClipboardChangedEvent(object sender, ClipboardChangedEventArgs e) 
        => ClipboardChangedNotify?.Invoke(this, e);

    public void Dispose()
    {
        if (!isDisposed) {
            // Cannot allow to throw exceptions here: add more checks to verify that 
            // the NativeWindow still exists and its handle is a valid handle
            NativeMethods.RemoveClipboardFormatListener(window.Handle);
            window?.DestroyHandle();
            isDisposed = true;
        }
    }

    ~ClipboardUpdateMonitor() => Dispose();

    private class ClipboardWindow : NativeWindow
    {
        public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged;
        public ClipboardWindow() {
            new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
            var cp = new CreateParams();

            cp.Caption = "ClipboardWindow";
            cp.Height = 100;
            cp.Width = 100;

            cp.Parent = IntPtr.Zero;
            cp.Style = NativeMethods.WS_CLIPCHILDREN;
            cp.ExStyle = NativeMethods.WS_EX_CONTROLPARENT | NativeMethods.WS_EX_TOOLWINDOW;
            this.CreateHandle(cp);
        }
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg) {
                case NativeMethods.WM_CLIPBOARDUPDATE:
                    IntPtr owner = NativeMethods.GetClipboardOwner();
                    var threadId = NativeMethods.GetWindowThreadProcessId(owner, out uint processId);
                    string processName = string.Empty;
                    if (processId != 0) {
                        using (var proc = Process.GetProcessById((int)processId)) { 
                            processName = proc?.ProcessName;
                        }
                    }
                    ClipboardChanged?.Invoke(null, new ClipboardChangedEventArgs(processId, processName, threadId));
                    m.Result = IntPtr.Zero;
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
    }
}

EventArgs用于携带收集到的有关剪贴板所有者的信息的自定义对象:

public class ClipboardChangedEventArgs : EventArgs
{
    public ClipboardChangedEventArgs(uint processId, string processName, uint threadId)
    {
        this.ProcessId = processId;
        this.ProcessName = processName;
        this.ThreadId = threadId;
    }
    public uint ProcessId { get; }
    public string ProcessName { get; }
    public uint ThreadId { get; }
}

NativeMethods班级:

internal static class NativeMethods
{
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool AddClipboardFormatListener(IntPtr hwnd);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);

    [DllImport("user32.dll")]
    internal static extern IntPtr GetClipboardOwner();

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    internal const int WM_CLIPBOARDUPDATE = 0x031D;

    internal const int WS_CLIPCHILDREN = 0x02000000;
    internal const int WS_EX_TOOLWINDOW = 0x00000080;
    internal const int WS_EX_CONTROLPARENT = 0x00010000;
}

推荐阅读