Отслеживание изменения содержимого буфера обмена

Есть Ctrl+Shift+C глобальный HotKey а-ля Ctrl+C со своим обработчиком. Отслеживать нажатие хоткеев - есть такой класс, а вот как по нажатию этого хоткея копировалось в буфер все что выделено (текст например) во внешних приложениях?

То есть, читаю pdf-ку, выделяю нужный мне текст, нажимаю хоткей (свой), выделенный текст копируется в буфер, дальше я с ним провожу свои манипуляции.

Может как-то можно подписаться на изменение содержимого буфера?


Ответы (1 шт):

Автор решения: aepot

Как отслеживать изменение буфера обмена в WPF

Самый простой вариант отслеживания изменения буфера обмена - Windows сообщение WM_CLIPBOARDUPDATE. Это сообщение поддерживается начиная с Windows Vista, для более старых версий Windows потребуется другая реализация для устаревшего Windows сообщения WM_DRAWCLIPBOARD, она будет выглядеть немного посложнее.

Потребуется вот такой класс

public sealed class ClipboardMonitor : IDisposable
{
    private const int WM_CLIPBOARDUPDATE = 0x031D;
    private readonly HwndSourceHook _hook;
    private readonly HwndSource _hwndSource;
    private bool _disposed;

    /// <summary>
    /// Возникает в случае, если содержимое буфера обмена изменилось.
    /// </summary>
    public event EventHandler ClipboardChanged;

    public ClipboardMonitor(HwndSource hwndSource)
    {
        _hwndSource = hwndSource ?? throw new ArgumentNullException(nameof(hwndSource));
        if (!NativeMethods.AddClipboardFormatListener(_hwndSource.Handle))
            throw new Win32Exception(Marshal.GetLastWin32Error());
        _hook = WndProc;
        _hwndSource.AddHook(_hook);
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        switch (msg)
        {
            case WM_CLIPBOARDUPDATE:
                ClipboardChanged?.Invoke(this, EventArgs.Empty);
                handled = true;
                break;
            default:
                handled = false;
                break;
        }
        return IntPtr.Zero;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (_disposed)
            return;
        _disposed = true;
        if (disposing)
            _hwndSource.RemoveHook(_hook);
        if (!NativeMethods.RemoveClipboardFormatListener(_hwndSource.Handle) && disposing)
            throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    ~ClipboardMonitor() => Dispose(false);
}

Win API

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

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

Экземпляр этого класса следует создать после инициализации окна, и уничтожать при закрытии окна.

События

<Window x:Class="WpfApp1.MainWindow"
        ...
        SourceInitialized="Window_SourceInitialized" Closing="Window_Closing">
    <!-- ... -->
</Window>

Обработчики

private ClipboardMonitor _cm;

private void Window_SourceInitialized(object sender, EventArgs e)
{
    HwndSource hwnd = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
    _cm = new ClipboardMonitor(hwnd);
    _cm.ClipboardChanged += OnClipboardChanged;
}

private void Window_Closing(object sender, CancelEventArgs e)
{
    _cm.Dispose();
}

private void OnClipboardChanged(object sender, EventArgs e)
{
    // буфер обмена изменился
}

Вместо Window.SourceInitialized можно использовать событие Window.Loaded, зависит от ваших требований к решению. SourceInitialized возникает раньше, и это самое раннее событие, где можно успешно получить HwndSource.

→ Ссылка