【问题标题】:How do I monitor clipboard changes in C#?如何在 C# 中监视剪贴板更改?
【发布时间】:2010-10-11 22:00:51
【问题描述】:

是否有我可以通过 C# 访问的剪贴板更改或更新事件?

【问题讨论】:

  • 对于在 2021 年遇到这个问题的任何人,请忽略答案,它们都过于复杂,而且还没有准备好生产(即使是这样说的)。 -- 只需将SharpClipboard NuGet 包添加到您的项目中即可。
  • 我们为什么要这样做??

标签: c# events clipboard


【解决方案1】:

剪贴板查看器可能面临的另一个问题:它在一段时间后停止接收 WM_DRAWCLIPBOARD 消息(似乎剪贴板链以某种方式中断)。 我找到的唯一解决方案是在发现断链时重新注册剪贴板查看器。

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

【讨论】:

    【解决方案2】:

    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 中剪贴板监控方案的最佳选择。

    【讨论】:

      【解决方案3】:
              [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;
                  }
              }
      

      【讨论】:

        【解决方案4】:

        我在 WPF 中遇到了这个挑战,最终使用了下面描述的方法。对于 Windows 窗体,此答案的其他地方有很好的示例,例如 ClipboardHelper 控件。

        对于 WPF,我们无法覆盖 WndProc,因此我们必须使用窗口中的 Source 将其与 HwndSource AddHook 调用显式挂钩。剪贴板侦听器仍使用 AddClipboardFormatListener 本机互操作调用。

        本机方法:

        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 窗口中使用。 (当我们有足够的信息来使用原生钩子时):

        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 剪贴板更改的人有所帮助!

        【讨论】:

        【解决方案5】:

        有多种方法可以做到这一点,但这是我最喜欢的并且对我有用。我创建了一个类库,以便其他人可以添加项目并包含 DLL,然后只需调用它并在应用程序中的任何位置使用它。

        这个答案是在this one的帮助下做出的。

        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. 在您的其他项目中,右键单击解决方案并添加 -> 退出项目 -> ClipboardHelper.csproj
        2. 在您的项目中,转到并右键单击引用 -> 添加引用 -> 解决方案 -> 选择 ClipboardHelper。
        3. 在项目类型的类文件中使用 ClipboardHelper。
        4. 您现在可以输入 ClipboardMonitor.Start 或 .Stop 或 .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...
                  }
          }
          

        【讨论】:

          【解决方案6】:

          我认为你必须使用一些 p/invoke:

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

          this article on how to set up a clipboard monitor in 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;
                  // ... 
             }
          }
          

          (还有更多工作要做;沿剪贴板链传递内容并取消注册您的视图,但您可以从the article 获得)

          【讨论】:

          • 它仅适用于第一个打开的表单...假设我有 MyForm1 和 myForm2,所以我打开 myForm1,然后打开 MyForm2,事件 ClipboardChanged 将仅在 MyForm1 中引发。我的意思是,在 MDI 应用程序中...
          • 链接已失效。您知道的任何备份?仍然 +1。
          • 对于懒惰的人:设置一个以 1 毫秒计时的计时器。然后,每打勾,检查你的剪贴板内容是否改变。这些钩子在我的计算机上引发病毒和木马警报。
          • 它将每个 windows MSG 传递给表单,使调试代码变得如此困难
          • 同样,SharpClipboard 作为一个库可能会更有好处,因为它将相同的功能封装到一个精细的组件库中。然后,您可以访问其ClipboardChanged 事件并在剪切/复制时检测各种数据格式。
          【解决方案7】:

          好的,这是一篇旧帖子,但与当前的答案集相比,我们找到了一个看起来非常简单的解决方案。我们正在使用 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();
          }
          

          我们实际上是通过这种方式在我们自己的 Command 上控制 CanExecute。适用于我们需要的东西,也许它会帮助其他人。

          【讨论】:

          • 很好的解决方案,因为它非常简单......谢谢!
          • 这是针对启用或禁用粘贴命令的特定问题的绝佳解决方案。不幸的是,它没有涵盖特定的“文本更改”场景,例如在复制多行不同的文本时不会触发。
          【解决方案8】:

          我相信早期的解决方案之一不会在 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()
          • 无论如何。出于您列出的验证目的,您应该将 IntPtr.Zero 用于 NULL(注意它不等同于 C# null)stackoverflow.com/questions/1456861/…
          • ChangeClipboardChain 在所有 msdn 示例中退出时始终执行
          • 目的是将自己从剪贴板查看器链中删除
          【解决方案9】:

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

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

          【讨论】:

          • 干得好!但是,您的事件调用代码不是线程安全的。您应该创建一个本地副本,或者使用空委托初始化事件。您还忘记了 ClipboardChanged 定义中的“事件”关键字 :)
          • @ohadsc 感谢您的更正。据我所知,WndProc 是在 UI 线程上调用的。由于该类派生自 Control,因此客户端也应在 UI 线程上调用它。
          • 它仅适用于第一个打开的表单...假设我有 MyForm1 和 myForm2,所以我打开 myForm1,然后打开 MyForm2,事件 ClipboardChanged 将仅在 MyForm1 中引发...我的意思是,在 MDI 应用程序中...
          • 您对 SetClipboardViewer 的调用以某种方式设置了 Win32 错误代码 1400:“窗口句柄无效。”。但它仍然有效。这对我来说似乎有点奇怪。
          • SharpClipboard 作为一个库可能会更有好处,因为它将相同的功能封装到一个精细的组件库中。然后,您可以访问其ClipboardChanged 事件并在剪切/复制时检测各种数据格式。
          猜你喜欢
          • 2011-01-14
          • 2011-03-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-11-11
          • 2021-01-20
          • 1970-01-01
          • 2019-10-01
          相关资源
          最近更新 更多