1

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

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

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

aepot
  • 49,560
  • Немного по-другому делается, регистрируете например Ctrl+1, по сработке симулируете Ctrl+C, после чего читаете буфер, производите изменения в данных, затем заносите в буфер измененные данные, затим симулируете Ctrl+V. Выглядит так как текст в редакторе просто заменяется, или вам не это надо? – aepot Aug 23 '22 at 17:35
  • Если вопрос только про то, можно ли технически отслеживать изменение буфера - можно, за это отвечает Windows сообщение WM_DRAWCLIPBOARD или WM_CLIPBOARDUPDATE. Какое использовать, зависит от того, какую минимальюную винду вы хотите поддерживать. Первое совместимо по-моему даже с Win95, второе работает начиная с Win Vista. Реализовать проще именно второе. Вам какое? – aepot Aug 23 '22 at 18:53
  • Переходите на UWP, там есть событие Clipboard.ContentChanged (шутка) – Alexander Petrov Aug 23 '22 at 19:43
  • Я когда впервые столкнулся с подобной задачей, просто опрашивал буфер обмена по таймеру. Работает прекрасно, систему не напрягает. Но лучше, конечно, использовать WinAPI, как предлагает aepot. – Alexander Petrov Aug 23 '22 at 19:44
  • @AlexanderPetrov сам до сегодняшнего дня в своих тулзах сидел на WM_DRAWCLIPBOARD, вот по случаю переписал на WM_CLIPBOARDUPDATE. :) – aepot Aug 23 '22 at 22:28
  • Вот вопрос как эмулировать нажатие Ctrl+C? Причем это же надо эмулировать в сторонних приложениях. Думаю мне как раз подойдет этот способ с использованием своего хоткея. – kukuikar Aug 24 '22 at 11:13
  • Нашел как сделать эмуляцию нажатий через AutoHotkey.Interop, но наверняка есть более элегантный способ это сделать – kukuikar Aug 24 '22 at 11:51
  • Может как-то можно подписаться на изменение содержимого буфера? это вы в шутку заставили меня полвечера ответ писать? Этот вопрос читается вполне однозначно. Если считаете ответ полезным, то можете принять его, поставив зеленую галочку слева от ответа. Если у вас другой вопрос - задайте его отдельно. Здесь есть правило: один вопрос - один ответ, не допускается несколько вопросов на 1 пост. Если ваш вопрос только про генерацию нажатия клавиш - то он дубликат, потому что такой ответ уже есть, вы просто не очень внимательно искали. – aepot Aug 25 '22 at 07:22
  • Отметите ответ принятым? Жалко удалять, вдруг кому пригодится. – aepot Aug 25 '22 at 17:57
  • 1
    нет, нет, ответ полезен и я его использовал. Спасибо большое - ответил как решение. – kukuikar Aug 26 '22 at 08:36

1 Answers1

0

Как отслеживать изменение буфера обмена в 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.

aepot
  • 49,560