本文介绍了如何获取已更新剪贴板的应用程序的进程 ID 或名称?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用 C# 创建一个剪贴板管理器,但我不时遇到某些应用程序将剪贴板设置为空的情况.

I am creating a clipboard manager in C# and from time to time I experience that the clipboard is being set empty by some application.

这发生在例如Excel 取消选择刚刚复制的内容时,所以我需要弄清楚剪贴板是否为空,但是如何获取更新剪贴板的应用程序名称?

This happens in e.g. Excel when deselecting what is just copied, so I need to figure out if the clipboard is empty but how can I get the application name that updates the clipboard?

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

I hope that I somehow can get the HWnd handle of the application that updates the clipboard so I can then lookup the process behind it with this code:

[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;
    }
}

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

I would have hoped that I could use the HWnd from the Message object, but this seems to be my own application - probably to notify the application with this process ID:

如果我能以任何其他方式得到它,那么这当然也完全可以,但我希望对此有任何见解:-)

If I can get it any other way then this would of course be fully okay also but I would appreciate any insights on this :-)

解决方案

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

Based on @Jimi's answer then this is very simple. I can just add the below 3 lines to my original code:

// 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();

推荐答案

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

You can call GetClipboardOwner() to get the handle of the Window that last set or cleared the Clipboard (the operation that triggered the notification).

[...] 一般而言,剪贴板所有者是上次在剪贴板中放置数据的窗口.
EmptyClipboard 函数分配剪贴板所有权.

在某些特殊情况下,进程将空句柄传递给 OpenClipboard():阅读此函数的备注部分和 EmptyClipboard 功能.

There are special cases when a Process passes a null handle to OpenClipboard(): read the Remarks section of this function and the EmptyClipboard function.

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


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


► Here I'm using a NativeWindow derived class to setup a Clipboard listener. The Window that process the Clipboard update messages is created initializing a CreateParams object and passing this parameter to the NativeWindow.CreateHandle(CreateParams) method, to create an invisible Window.
Then override WndProc of the initialized NativeWindow, to receive WM_CLIPBOARDUPDATE notifications.

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

The AddClipboardFormatListener function is used to place the Window in the system Clipboard listeners chain.

ClipboardUpdateMonitor 类在收到剪贴板通知时生成事件.事件中传递的自定义 ClipboardChangedEventArgs 对象包含剪贴板所有者的句柄,由 GetClipboardOwner()ThreadId 返回ProcessIdGetWindowThreadProcessId() 返回 和进程名称,由 Process 标识.GetProcessById().

► The ClipboardUpdateMonitor class generates an event when a Clipboard notification is received. The custom ClipboardChangedEventArgs object passed in the event contains the Handle of the Clipboard Owner, as returned by GetClipboardOwner(), the ThreadId and ProcessId returned by GetWindowThreadProcessId() and the Process name, identified by Process.GetProcessById().

您可以像这样设置 ClipboardUpdateMonitor 对象:

You can setup a ClipboardUpdateMonitor object like this:

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 对象,用于携带收集的有关剪贴板所有者的信息:

Custom EventArgs object used to carry the information collected about the Clipboard Owner:

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;
}

这篇关于如何获取已更新剪贴板的应用程序的进程 ID 或名称?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-31 09:22