如何在C#中监控剪贴板变化?

99

在C#中是否有可访问的剪贴板更改或更新事件?


4
如果有人在2021年偶然看到这个问题,请忽略答案,它们都太复杂了,而且不适合生产环境(即使有些答案声称自己是)。--只需将 SharpClipboard NuGet 包添加到您的项目中即可。 - BrainSlugs83
为什么我们应该这样做? - TheComputerWizard
@BrainSlugs83 谢谢你的NuGet提示 ;) 真是太棒了! - Fredy
@BrainSlug88,你的答案在2022年已经不正确了——发布了控制台应用程序的解决方法。 - smaudet
10个回答

83

为了完整性,这是我在生产代码中使用的控件。只需从设计师处拖动并双击创建事件处理程序即可。

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;

namespace ClipboardAssist {

// Must inherit Control, not Component, in order to have Handle
[DefaultEvent("ClipboardChanged")]
public partial class ClipboardMonitor : Control 
{
    IntPtr nextClipboardViewer;

    public ClipboardMonitor()
    {
        this.BackColor = Color.Red;
        this.Visible = false;

        nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
    }

    /// <summary>
    /// Clipboard contents changed.
    /// </summary>
    public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged;

    protected override void Dispose(bool disposing)
    {
        ChangeClipboardChain(this.Handle, nextClipboardViewer);
    }

    [DllImport("User32.dll")]
    protected static extern int SetClipboardViewer(int hWndNewViewer);

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

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);

    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
        // defined in winuser.h
        const int WM_DRAWCLIPBOARD = 0x308;
        const int WM_CHANGECBCHAIN = 0x030D;

        switch (m.Msg)
        {
            case WM_DRAWCLIPBOARD:
                OnClipboardChanged();
                SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                break;

            case WM_CHANGECBCHAIN:
                if (m.WParam == nextClipboardViewer)
                    nextClipboardViewer = m.LParam;
                else
                    SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                break;

            default:
                base.WndProc(ref m);
                break;
        }
    }

    void OnClipboardChanged()
    {
        try
        {
            IDataObject iData = Clipboard.GetDataObject();
            if (ClipboardChanged != null)
            {
                ClipboardChanged(this, new ClipboardChangedEventArgs(iData));
            }

        }
        catch (Exception e)
        {
            // Swallow or pop-up, not sure
            // Trace.Write(e.ToString());
            MessageBox.Show(e.ToString());
        }
    }
}

public class ClipboardChangedEventArgs : EventArgs
{
    public readonly IDataObject DataObject;

    public ClipboardChangedEventArgs(IDataObject dataObject)
    {
        DataObject = dataObject;
    }
}
}

2
干得好!你的事件调用代码不是线程安全的。你应该创建一个局部副本,或者使用空委托初始化该事件。在 ClipboardChanged 的定义中,你还忘记了 'event' 关键字 :) - Ohad Schneider
1
@ohadsc 感谢您的更正。据我所知,WndProc 在 UI 线程上被调用。由于该类派生自 Control,客户端也应在 UI 线程上调用它。 - dbkk
它只在第一个打开的表单上工作...比如我有MyForm1和myForm2,我先打开了MyForm1,然后再打开MyForm2,那么ClipboardChanged事件只会在MyForm1中被触发...我的意思是,在MDI应用程序中... - serhio
你的SetClipboardViewer调用会设置Win32错误代码1400:“无效的窗口句柄”。但它仍然可以工作。这对我来说有点奇怪。 - metacircle
这似乎与dispose有问题。我认为它应该看起来像这样: protected override void Dispose(bool disposing) { if (nextClipboardViewer != IntPtr.Zero) { ChangeClipboardChain(this.Handle, nextClipboardViewer); nextClipboardViewer = IntPtr.Zero; } base.Dispose(disposing); } - Sprotty
2
作为一个库,SharpClipboard 可以更有益,因为它将相同的功能封装到一个精美的组件库中。然后,您可以访问其 ClipboardChanged 事件,并在剪切/复制时检测各种数据格式。 - Willy Kimura

77

我认为你需要使用一些p/invoke:

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

请参考这篇文章中的C#代码,了解如何设置剪贴板监视器

基本上,您需要使用以下代码将应用程序注册为剪贴板查看器:

_ClipboardViewerNext = SetClipboardViewer(this.Handle);

接着您会收到WM_DRAWCLIPBOARD消息,您可以通过重写WndProc来处理它:

protected override void WndProc(ref Message m)
{
    switch ((Win32.Msgs)m.Msg)
    {
        case Win32.Msgs.WM_DRAWCLIPBOARD:
        // Handle clipboard changed
        break;
        // ... 
   }
}

还有更多需要完成的工作,例如将东西沿着剪贴板链传递并注销您的视图,但您可以从文章中获取这些信息。


它只在第一个打开的表单上工作...比如我有MyForm1和myForm2,我先打开了MyForm1,然后打开了MyForm2,那么ClipboardChanged事件只会在MyForm1中被触发。我的意思是,在MDI应用程序中... - serhio
链接已失效。您知道有没有备份?尽管如此还是点赞。 - Patrick Hofman
1
懒人专用:设置一个每1毫秒滴答一次的计时器。然后,每次滴答时检查您的剪贴板内容是否更改。这些钩子会在我的计算机上引发病毒和木马警报。 - C4d
1
它将每个Windows MSG传递到表单中,使得调试代码非常困难。 - anon
同样地,SharpClipboard 作为一个库可以更有利,因为它将相同的功能封装在一个精美的组件库中。您可以访问其ClipboardChanged事件,并在剪切/复制时检测各种数据格式。 - Willy Kimura

32

在WPF中我遇到了这个问题,最终采用下面描述的方法解决。对于Windows Forms来说,在本答案的其他地方有出色的示例,例如ClipboardHelper控件。

在WPF中,我们无法覆盖WndProc,因此必须使用窗口的Source通过HwndSource AddHook调用来显式地挂钩它。剪贴板监听器仍然使用AddClipboardFormatListener本机Interop调用。

本机方法:

internal static class NativeMethods
{
    // See http://msdn.microsoft.com/en-us/library/ms649021%28v=vs.85%29.aspx
    public const int WM_CLIPBOARDUPDATE = 0x031D;
    public static IntPtr HWND_MESSAGE = new IntPtr(-3);

    // See http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool AddClipboardFormatListener(IntPtr hwnd);
}

剪贴板管理器类:

using System.Windows;
using System.Windows.Interop;

public class ClipboardManager
{
    public event EventHandler ClipboardChanged;

    public ClipboardManager(Window windowSource)
    {
        HwndSource source = PresentationSource.FromVisual(windowSource) as HwndSource;
        if(source == null)
        {
            throw new ArgumentException(
                "Window source MUST be initialized first, such as in the Window's OnSourceInitialized handler."
                , nameof(windowSource));
        }

        source.AddHook(WndProc);

        // get window handle for interop
        IntPtr windowHandle = new WindowInteropHelper(windowSource).Handle;

        // register for clipboard events
        NativeMethods.AddClipboardFormatListener(windowHandle);
    }

    private void OnClipboardChanged()
    {
        ClipboardChanged?.Invoke(this, EventArgs.Empty);
    }

    private static readonly IntPtr WndProcSuccess = IntPtr.Zero;

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == NativeMethods.WM_CLIPBOARDUPDATE)
        {
            OnClipboardChanged();
            handled = true;
        }

        return WndProcSuccess;
    }
}

通过在OnSourceInitialized事件中添加该事件或稍后添加,例如Window.Loaded事件或在操作期间添加到WPF窗口中使用(当我们有足够的信息来使用本地钩子):

这可以在WPF窗口中使用,在OnSourceInitialized事件中添加该事件或稍后添加,例如在Window.Loaded事件或操作期间。 (当我们有足够的信息来使用本机挂钩):

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        // Initialize the clipboard now that we have a window soruce to use
        var windowClipboardManager = new ClipboardManager(this);
        windowClipboardManager.ClipboardChanged += ClipboardChanged;
    }

    private void ClipboardChanged(object sender, EventArgs e)
    {
        // Handle your clipboard update here, debug logging example:
        if (Clipboard.ContainsText())
        {
            Debug.WriteLine(Clipboard.GetText());
        }
    }
}

我在“流亡之路”物品分析项目中使用此方法,因为游戏在按下Ctrl-C时通过剪贴板公开物品信息。

https://github.com/ColinDabritz/PoeItemAnalyzer

希望这能对需要处理WPF剪贴板变化的人有所帮助!


1
如果有人不知道ClipboardChanged?.Invoke是什么意思,请参见在C# 6中使用新的Null条件运算符,第“其他场景”一节。 - marbel82

14

好的,这是一篇旧帖子,但我们找到了一个看起来比当前答案组简单得多的解决方案。我们正在使用WPF,并希望在ContextMenu中启用和禁用我们自己的自定义命令(如果剪贴板包含文本)。已经有ApplicationCommands.Cut、Copy和Paste,这些命令对剪贴板变化做出正确响应。所以我们只需添加以下EventHandler。

ApplicationCommands.Paste.CanExecuteChanged += new EventHandler(Paste_CanExecuteChanged);

private void Paste_CanExecuteChanged(object sender, EventArgs e) {
  ourVariable= Clipboard.ContainsText();
}

我们实际上是通过这种方式来控制自己的命令的CanExecute。 对于我们需要的内容有效,并且也许会帮助其他人。


非常棒的解决方案,因为它非常简单...谢谢! - okieh
1
这是一个非常好的解决方案,可以启用或禁用粘贴命令,但不幸的是它并没有涵盖“文本已更改”的特定场景,并且在复制多个不同行的文本时也无法触发。 - Colin Dabritz

12
有多种方法可以做到这一点,但这是我最喜欢的方法,并且对我有效。我创建了一个类库,以便其他人可以添加项目并包含DLL,然后在他们的应用程序中随时调用它并使用它。
此答案是在这个帖子的帮助下完成的。
  1. 创建类库项目并将其命名为ClipboardHelper。
  2. 将Class1名称替换为ClipboardMonitor。
  3. 将以下代码添加到其中。
  4. 添加System.Windows.Forms引用。
更多步骤请参考代码。
using System;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;

namespace ClipboardHelper
{
    public static class ClipboardMonitor
    {
        public delegate void OnClipboardChangeEventHandler(ClipboardFormat format, object data);
        public static event OnClipboardChangeEventHandler OnClipboardChange;

        public static void Start()
        {
            ClipboardWatcher.Start();
            ClipboardWatcher.OnClipboardChange += (ClipboardFormat format, object data) =>
            {
                if (OnClipboardChange != null)
                    OnClipboardChange(format, data);
            };
        }

        public static void Stop()
        {
            OnClipboardChange = null;
            ClipboardWatcher.Stop();
        }

        class ClipboardWatcher : Form
        {
            // static instance of this form
            private static ClipboardWatcher mInstance;

            // needed to dispose this form
            static IntPtr nextClipboardViewer;

            public delegate void OnClipboardChangeEventHandler(ClipboardFormat format, object data);
            public static event OnClipboardChangeEventHandler OnClipboardChange;

            // start listening
            public static void Start()
            {
                // we can only have one instance if this class
                if (mInstance != null)
                    return;

                var t = new Thread(new ParameterizedThreadStart(x => Application.Run(new ClipboardWatcher())));
                t.SetApartmentState(ApartmentState.STA); // give the [STAThread] attribute
                t.Start();
            }

            // stop listening (dispose form)
            public static void Stop()
            {
                mInstance.Invoke(new MethodInvoker(() =>
                {
                    ChangeClipboardChain(mInstance.Handle, nextClipboardViewer);
                }));
                mInstance.Invoke(new MethodInvoker(mInstance.Close));

                mInstance.Dispose();

                mInstance = null;
            }

            // on load: (hide this window)
            protected override void SetVisibleCore(bool value)
            {
                CreateHandle();

                mInstance = this;

                nextClipboardViewer = SetClipboardViewer(mInstance.Handle);

                base.SetVisibleCore(false);
            }

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

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

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            private static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);

            // defined in winuser.h
            const int WM_DRAWCLIPBOARD = 0x308;
            const int WM_CHANGECBCHAIN = 0x030D;

            protected override void WndProc(ref Message m)
            {
                switch (m.Msg)
                {
                    case WM_DRAWCLIPBOARD:
                        ClipChanged();
                        SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                        break;

                    case WM_CHANGECBCHAIN:
                        if (m.WParam == nextClipboardViewer)
                            nextClipboardViewer = m.LParam;
                        else
                            SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                        break;

                    default:
                        base.WndProc(ref m);
                        break;
                }
            }

            static readonly string[] formats = Enum.GetNames(typeof(ClipboardFormat));

            private void ClipChanged()
            {
                IDataObject iData = Clipboard.GetDataObject();

                ClipboardFormat? format = null;

                foreach (var f in formats)
                {
                    if (iData.GetDataPresent(f))
                    {
                        format = (ClipboardFormat)Enum.Parse(typeof(ClipboardFormat), f);
                        break;
                    }
                }

                object data = iData.GetData(format.ToString());

                if (data == null || format == null)
                    return;

                if (OnClipboardChange != null)
                    OnClipboardChange((ClipboardFormat)format, data);
            }
        }
    }

    public enum ClipboardFormat : byte
    {
        /// <summary>Specifies the standard ANSI text format. This static field is read-only.
        /// </summary>
        /// <filterpriority>1</filterpriority>
        Text,
        /// <summary>Specifies the standard Windows Unicode text format. This static field
        /// is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        UnicodeText,
        /// <summary>Specifies the Windows device-independent bitmap (DIB) format. This static
        /// field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        Dib,
        /// <summary>Specifies a Windows bitmap format. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        Bitmap,
        /// <summary>Specifies the Windows enhanced metafile format. This static field is
        /// read-only.</summary>
        /// <filterpriority>1</filterpriority>
        EnhancedMetafile,
        /// <summary>Specifies the Windows metafile format, which Windows Forms does not
        /// directly use. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        MetafilePict,
        /// <summary>Specifies the Windows symbolic link format, which Windows Forms does
        /// not directly use. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        SymbolicLink,
        /// <summary>Specifies the Windows Data Interchange Format (DIF), which Windows Forms
        /// does not directly use. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        Dif,
        /// <summary>Specifies the Tagged Image File Format (TIFF), which Windows Forms does
        /// not directly use. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        Tiff,
        /// <summary>Specifies the standard Windows original equipment manufacturer (OEM)
        /// text format. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        OemText,
        /// <summary>Specifies the Windows palette format. This static field is read-only.
        /// </summary>
        /// <filterpriority>1</filterpriority>
        Palette,
        /// <summary>Specifies the Windows pen data format, which consists of pen strokes
        /// for handwriting software, Windows Forms does not use this format. This static
        /// field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        PenData,
        /// <summary>Specifies the Resource Interchange File Format (RIFF) audio format,
        /// which Windows Forms does not directly use. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        Riff,
        /// <summary>Specifies the wave audio format, which Windows Forms does not directly
        /// use. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        WaveAudio,
        /// <summary>Specifies the Windows file drop format, which Windows Forms does not
        /// directly use. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        FileDrop,
        /// <summary>Specifies the Windows culture format, which Windows Forms does not directly
        /// use. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        Locale,
        /// <summary>Specifies text consisting of HTML data. This static field is read-only.
        /// </summary>
        /// <filterpriority>1</filterpriority>
        Html,
        /// <summary>Specifies text consisting of Rich Text Format (RTF) data. This static
        /// field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        Rtf,
        /// <summary>Specifies a comma-separated value (CSV) format, which is a common interchange
        /// format used by spreadsheets. This format is not used directly by Windows Forms.
        /// This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        CommaSeparatedValue,
        /// <summary>Specifies the Windows Forms string class format, which Windows Forms
        /// uses to store string objects. This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        StringFormat,
        /// <summary>Specifies a format that encapsulates any type of Windows Forms object.
        /// This static field is read-only.</summary>
        /// <filterpriority>1</filterpriority>
        Serializable,
    }
}
  1. In your other projects right click on solution and Add -> Exiting Project -> ClipboardHelper.csproj
  2. On your project go to and right click References -> Add Reference -> Solution -> Select ClipboardHelper.
  3. In your class file of the project type using ClipboardHelper.
  4. You may now type ClipboardMonitor.Start or .Stop or .OnClipboardChanged

    using ClipboardHelper;
    
    namespace Something.Something.DarkSide
    {
        public class MainWindow
        {
    
            public MainWindow()
            {
                InitializeComponent();
    
                Loaded += MainWindow_Loaded;
            }
    
            void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                ClipboardMonitor.OnClipboardChange += ClipboardMonitor_OnClipboardChange;
                ClipboardMonitor.Start();
            }               
    
            private void ClipboardMonitor_OnClipboardChange(ClipboardFormat format, object data)
            {
                // Do Something...
            }
    }
    

10
作为一个库,SharpClipboard 的好处在于它将相同的功能封装到一个优秀的组件库中。您可以访问其ClipboardChanged事件,并在剪切/复制时检测各种数据格式。
您可以选择要监视的不同数据格式:
var clipboard = new SharpClipboard();

clipboard.ObservableFormats.Texts = true;
clipboard.ObservableFormats.Files = true;
clipboard.ObservableFormats.Images = true;
clipboard.ObservableFormats.Others = true;

这里有一个示例使用它的 ClipboardChanged 事件:
private void ClipboardChanged(Object sender, ClipboardChangedEventArgs e)
{
    // Is the content copied of text type?
    if (e.ContentType == SharpClipboard.ContentTypes.Text)
    {
        // Get the cut/copied text.
        Debug.WriteLine(clipboard.ClipboardText);
    }

    // Is the content copied of image type?
    else if (e.ContentType == SharpClipboard.ContentTypes.Image)
    {
        // Get the cut/copied image.
        Image img = clipboard.ClipboardImage;
    }

    // Is the content copied of file type?
    else if (e.ContentType == SharpClipboard.ContentTypes.Files)
    {
        // Get the cut/copied file/files.
        Debug.WriteLine(clipboard.ClipboardFiles.ToArray());

        // ...or use 'ClipboardFile' to get a single copied file.
        Debug.WriteLine(clipboard.ClipboardFile);
    }

    // If the cut/copied content is complex, use 'Other'.
    else if (e.ContentType == SharpClipboard.ContentTypes.Other)
    {
        // Do something with 'e.Content' here...
    }
}

您还可以找出剪切/复制事件发生的应用程序以及其详细信息:
private void ClipboardChanged(Object sender, SharpClipboard.ClipboardChangedEventArgs e)
{
    // Gets the application's executable name.
    Debug.WriteLine(e.SourceApplication.Name);
    // Gets the application's window title.
    Debug.WriteLine(e.SourceApplication.Title);
    // Gets the application's process ID.
    Debug.WriteLine(e.SourceApplication.ID.ToString());
    // Gets the application's executable path.
    Debug.WriteLine(e.SourceApplication.Path);
}

还有其他事件,比如MonitorChanged事件,它监听剪贴板监视被禁用的情况,这意味着您可以在运行时启用或禁用监视剪贴板。

除此之外,由于它是一个组件,您可以通过将其拖放到Windows窗体中的设计视图中使用它,使任何人都能轻松自定义其选项并使用其内置事件。

SharpClipboard似乎是.NET中监视剪贴板场景的最佳选择。


很遗憾,除了Windows之外,它无法在其他任何平台上编译。 - rokejulianlockhart

6

我认为早期的解决方案在dispose方法上没有检查null:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;

namespace ClipboardAssist {

// Must inherit Control, not Component, in order to have Handle
[DefaultEvent("ClipboardChanged")]
public partial class ClipboardMonitor : Control 
{
    IntPtr nextClipboardViewer;

    public ClipboardMonitor()
    {
        this.BackColor = Color.Red;
        this.Visible = false;

        nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
    }

    /// <summary>
    /// Clipboard contents changed.
    /// </summary>
    public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged;

    protected override void Dispose(bool disposing)
    {
        if(nextClipboardViewer != null)
            ChangeClipboardChain(this.Handle, nextClipboardViewer);
    }

    [DllImport("User32.dll")]
    protected static extern int SetClipboardViewer(int hWndNewViewer);

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

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);

    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
        // defined in winuser.h
        const int WM_DRAWCLIPBOARD = 0x308;
        const int WM_CHANGECBCHAIN = 0x030D;

        switch (m.Msg)
        {
            case WM_DRAWCLIPBOARD:
                OnClipboardChanged();
                SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                break;

            case WM_CHANGECBCHAIN:
                if (m.WParam == nextClipboardViewer)
                    nextClipboardViewer = m.LParam;
                else
                    SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                break;

            default:
                base.WndProc(ref m);
                break;
        }
    }

    void OnClipboardChanged()
    {
        try
        {
            IDataObject iData = Clipboard.GetDataObject();
            if (ClipboardChanged != null)
            {
                ClipboardChanged(this, new ClipboardChangedEventArgs(iData));
            }

        }
        catch (Exception e)
        {
            // Swallow or pop-up, not sure
            // Trace.Write(e.ToString());
            MessageBox.Show(e.ToString());
        }
    }
}

    public class ClipboardChangedEventArgs : EventArgs
    {
        public readonly IDataObject DataObject;

        public ClipboardChangedEventArgs(IDataObject dataObject)
        {
            DataObject = dataObject;
        }
    }
}

它从未为空,因为构造函数已设置它。我惟一会做不同的是在dispose方法中调用base.Dispose() - jedmao
无论如何,对于像您列出的验证目的,应使用IntPtr.Zero代替NULL(请注意,它与C# null不等效)。https://dev59.com/cnM_5IYBdhLWcg3wQQtd - walter
1
在所有 MSDN 示例中,ChangeClipboardChain 总是在退出时执行。 - walter
目的是将其从剪贴板查看器链中移除。 - walter

0

在剪贴板查看器中,您可能会遇到另一个问题:在一段时间后,它停止接收WM_DRAWCLIPBOARD消息(似乎剪贴板链已经被某种方式打破)。 我找到的唯一解决方案是,如果发现了断开的链路,则重新注册剪贴板查看器。

为了满足我的需求,我创建了nuget包https://github.com/magicmanam/windows-clipboard-viewer,它包装了所有需要的Windows消息处理,并提供了刷新剪贴板查看器的方法。该包的描述包含使用示例。


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

        private void Form1_Load(object sender, EventArgs e)
        {
            _ClipboardViewerNext = SetClipboardViewer(this.Handle);
        }

        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            const int WM_DRAWCLIPBOARD = 0x308;

            switch (m.Msg)
            {
                case WM_DRAWCLIPBOARD:
                    //Clipboard is Change 
                    //your code..............
                    break; 
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

-1

还有一个答案(在2022年我知道!):

如果您在控制台应用程序中,您需要使用[STAThread]才能监视剪贴板(并导入System.Windows.Forms以获取Clipboard类)。

SharpClipboard特别是在[STAThread]环境中也不会工作。

using System;
using System.Windows.Forms; 
class Main {
    [STAThread]
    static void Main() {
        if (Clipboard.ContainsText()) {
            string text = Clipboard.GetText();
            Console.Writeline(text);
        }
    }
}

编辑:

也许可以钩取DLL函数来访问剪贴板,而无需导入System.Windows.Forms。


这并没有真正回答问题——是否有剪贴板更新或更改事件?当有人将某些内容复制到Windows剪贴板时,我如何获得回调到我的代码? - Christopher Hamkins
@ChristopherHamkins,确实如此,它并没有回答关于“C#中的事件”,但我认为它确实允许监控,也就是说你可以自己生成事件。我认为其他所有答案都是从C# GUI的角度来写的,而这个只是一个CLI应用程序。所以它确实回答了“如何在C#中监控剪贴板更改”的问题。 - smaudet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接