Есть ли измененное или обновленное событие буфера обмена, через которое я могу получить доступ через С#?
Событие буфера обмена С#
Ответ 1
Я думаю, вам придется использовать p/invoke:
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
Смотрите эту статью о том, как настроить монитор буфера обмена в С#
В основном вы регистрируете свое приложение в качестве средства просмотра буфера обмена, используя
_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;
        // ... 
   }
}
(Там еще предстоит сделать: прохождение вещей по цепочке буфера обмена и отмена регистрации вашего представления, но вы можете получить это от статьи)
Ответ 2
Для полноты, здесь элемент управления, который я использую в производственном коде. Просто перетащите из конструктора и дважды щелкните, чтобы создать обработчик событий.
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;
    }
}
}
		Ответ 3
У меня была эта проблема в WPF и в итоге я использовал описанный ниже подход. Для оконных форм есть отличные примеры в другом месте этого ответа, такие как элемент управления ClipboardHelper.
Для WPF мы не можем переопределить WndProc, поэтому мы должны явно подключить его с помощью вызова HwndSource AddHook, используя Source из окна. Слушатель буфера обмена по-прежнему использует встроенный вызов вызова 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 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;
    }
}
Это используется в окне 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());
        }
    }
}
Я использую этот подход в проекте анализа элемента Path of Exile, поскольку игра выдает информацию о позиции через буфер обмена, когда вы нажимаете Ctrl-C.
https://github.com/ColinDabritz/PoeItemAnalyzer
Я надеюсь, что это поможет кому-то с обработкой изменения буфера обмена WPF!
Ответ 4
Хорошо, так что это старый пост, но мы нашли решение, которое кажется очень простым по сравнению с текущим набором ответов. Мы используем 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 в нашей собственной команде таким образом. Работы для того, что нам нужно, и, возможно, это поможет другим.
Ответ 5
Существует несколько способов сделать это, но это мой любимый и работает для меня. Я создал библиотеку классов, чтобы другие могли добавить проект и включить DLL, а затем просто вызвать его и использовать его там, где они хотят в своих приложениях.
Этот ответ был сделан с помощью этого.
- Создайте проект библиотеки классов и назовите его ClipboardHelper.
 - Замените имя Class1 на ClipboardMonitor.
 - Добавьте в него код ниже.
 - Добавить ссылку 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,
    }
}
- В ваших других проектах щелкните правой кнопкой мыши по решению и Add → Exiting Project → ClipboardHelper.csproj
 - В своем проекте перейдите по ссылке и щелкните правой кнопкой мыши ссылку → Добавить ссылку → Решение → Выбрать буфер обмена.
 - В вашем файле класса типа проекта с помощью ClipboardHelper.
 -  
Теперь вы можете ввести 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
Я полагаю, что одно из ранних решений не проверяет нуль на методе dispose:
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;
        }
    }
}
		Ответ 7
 здесь является хорошим примером использования AddClipboardFormatListener.
Чтобы сделать это, нам нужно вывести значения AddClipboardFormatListener и RemoveClipboardFormatListener.
/// <summary>
/// Places the given window in the system-maintained clipboard format listener list.
/// </summary>
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AddClipboardFormatListener(IntPtr hwnd);
/// <summary>
/// Removes the given window from the system-maintained clipboard format listener list.
/// </summary>
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
/// <summary>
/// Sent when the contents of the clipboard have changed.
/// </summary>
private const int WM_CLIPBOARDUPDATE = 0x031D;
Затем нам нужно добавить наше окно в список прослушивателя формата буфера обмена, вызвав метод AddClipboardFormatListener с помощью нашего дескриптора Windows в качестве параметра. Поместите следующий код в конструктор формы основного окна или любые его события загрузки.
AddClipboardFormatListener(this.Handle);    // Add our window to the clipboard format listener list.
Отмените метод WndProc, чтобы мы могли поймать при отправке  WM_CLIPBOARDUPDATE.
protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);
    if (m.Msg == WM_CLIPBOARDUPDATE)
    {
        IDataObject iData = Clipboard.GetDataObject();      // Clipboard data.
        /* Depending on the clipboard current data format we can process the data differently.
         * Feel free to add more checks if you want to process more formats. */
        if (iData.GetDataPresent(DataFormats.Text))
        {
            string text = (string)iData.GetData(DataFormats.Text);
            // do something with it
        }
        else if (iData.GetDataPresent(DataFormats.Bitmap))
        {
            Bitmap image = (Bitmap)iData.GetData(DataFormats.Bitmap);
            // do something with it
        }
    }
}
И, наконец, обязательно удалите главное окно из списка прослушивателя формата буфера обмена, прежде чем закрывать форму.
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    RemoveClipboardFormatListener(this.Handle);     // Remove our window from the clipboard format listener list.
}
		Ответ 8
        [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;
            }
        }
		Ответ 9
 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 executable name.
    Debug.WriteLine(e.SourceApplication.Name);
    // Gets the application window title.
    Debug.WriteLine(e.SourceApplication.Title);
    // Gets the application process ID.
    Debug.WriteLine(e.SourceApplication.ID.ToString());
    // Gets the application executable path.
    Debug.WriteLine(e.SourceApplication.Path);
}
  Есть и другие события, такие как событие MonitorChanged которое прослушивается всякий раз, когда отключен мониторинг буфера обмена, то есть вы можете включить или отключить мониторинг буфера обмена во время выполнения.
В дополнение ко всему этому, поскольку он является компонентом, вы можете использовать его в представлении конструктора, перетаскивая его в форму Windows, что упрощает настройку его параметров и работу со встроенными событиями.
SharpClipboard, похоже, является наилучшим вариантом для сценариев мониторинга буфера обмена в .NET.