ClipboardInterop内容更改了两次

本文关键字:两次 ClipboardInterop | 更新日期: 2023-09-27 18:03:29

我正在编写一个监视剪贴板变化的WPF应用程序。

我写了下面的类:

public class ClipboardInterop : IDisposable
{
    public event EventHandler ClipboardContentChanged;
    private void OnClipboardContentChanged()
    {
        var handlers = ClipboardContentChanged;
        if (handlers != null)
        {
            handlers(this, new EventArgs());
        }
    }
    public static ClipboardInterop GetClipboardInterop(Window window)
    {
        var hwndSource = PresentationSource.FromVisual(window) as HwndSource;
        if (hwndSource == null) return null;
        return new ClipboardInterop(hwndSource);
    }
    private IntPtr _thisHandle;
    private IntPtr _nextHandle;
    private HwndSource _hwndSource;
    public bool IsListening { get; private set; }
    private ClipboardInterop(HwndSource hwndSource)
    {
        _hwndSource = hwndSource;
        _thisHandle = hwndSource.Handle;
        IsListening = false;
    }
    public bool StartViewingClipboard()
    {
        Win32.SetLastError(0);
        _nextHandle = Win32.SetClipboardViewer(_thisHandle);
        if (_nextHandle == IntPtr.Zero)
        {
            UInt32 eCode = Win32.GetLastError();
            if (eCode != 0)
            {
                return false;
            }
        }
        _hwndSource.AddHook(HwndSourceHook);
        IsListening = true;
        return true;
    }

    public bool StopViewingClipboard()
    {
        Win32.SetLastError(0);
        Win32.ChangeClipboardChain(_thisHandle, _nextHandle);
        UInt32 eCode = Win32.GetLastError();
        IsListening = false;
        return eCode == 0;
    }
    private IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        switch (msg)
        {
            case Win32.WM_CHANGECBCHAIN:
                _nextHandle = lParam;
                if (_nextHandle != IntPtr.Zero)
                {
                    Win32.SendMessage(_nextHandle, (UInt32)msg, wParam, lParam);
                }
                break;
            case Win32.WM_DRAWCLIPBOARD:
                OnClipboardContentChanged();
                if (_nextHandle != IntPtr.Zero)
                {
                    Win32.SendMessage(_nextHandle, (UInt32)msg, wParam, lParam);
                }
                break;
        }
        return IntPtr.Zero;
    }
    public void Dispose()
    {
        if (IsListening)
            StopViewingClipboard();
        _hwndSource = null;
        _nextHandle = IntPtr.Zero;
        _thisHandle = IntPtr.Zero;
    }
}

Win32.cs看起来像:

internal static class Win32
{
    /// <summary>
    ///     The WM_DRAWCLIPBOARD message notifies a clipboard viewer window that
    ///     the content of the clipboard has changed.
    /// </summary>
    internal const int WM_DRAWCLIPBOARD = 0x0308;
    /// <summary>
    ///     A clipboard viewer window receives the WM_CHANGECBCHAIN message when
    ///     another window is removing itself from the clipboard viewer chain.
    /// </summary>
    internal const int WM_CHANGECBCHAIN = 0x030D;
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr SetClipboardViewer(
        IntPtr hWndNewViewer);
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool ChangeClipboardChain(
        IntPtr hWndRemove,
        IntPtr hWndNewNext);
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr SendMessage(
        IntPtr hWnd,
        UInt32 msg,
        IntPtr wParam,
        IntPtr lParam);
    [DllImport("kernel32.dll")]
    public static extern void SetLastError(
        UInt32 errorCode);
    [DllImport("kernel32.dll")]
    public static extern UInt32 GetLastError();
}

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        Loaded="MainWindow_OnLoaded">
    <Button Content="Toggle" Click="ButtonBase_OnClick"></Button>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    private ClipboardInterop _clipboardInterop;
    public MainWindow()
    {
        InitializeComponent();
    }
    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        _clipboardInterop.StopViewingClipboard();
        _clipboardInterop.StartViewingClipboard();
    }
    private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
    {
        _clipboardInterop = ClipboardInterop.GetClipboardInterop(this);
        _clipboardInterop.StartViewingClipboard();
        _clipboardInterop.ClipboardContentChanged +=
            (o, args) => Debug.WriteLine(DateTime.Now.ToLongTimeString() + " Content changed");
    }
}

所以现在当我启动应用程序时一切正常,当我将文本复制到剪贴板时触发一次事件。当我单击该按钮并再次将文本复制到剪贴板时,将触发该事件两次。再单击一个按钮,事件将被触发三次。我不知道为什么会这样。有人能帮我一下吗?

ClipboardInterop内容更改了两次

我在这个帮助下修改了我的代码,显然我忘记了在StopViewingClipboard中删除钩子。

下面是固定的代码:

public class ClipboardInterop : IDisposable
{
    public event EventHandler ClipboardContentChanged;
    private void OnClipboardContentChanged()
    {
        var handlers = ClipboardContentChanged;
        if (handlers != null)
        {
            handlers(this, new EventArgs());
        }
    }
    public static ClipboardInterop GetClipboardInterop(Window window)
    {
        var wih = new WindowInteropHelper(window);
        var hwndSource = HwndSource.FromHwnd(wih.Handle);
        if (hwndSource == null)
        {
            return null;
        }
        return new ClipboardInterop(hwndSource);
    }
    private IntPtr _hWndNextViewer;
    private HwndSource _hWndSource;
    public bool IsViewing { get; private set; }
    private ClipboardInterop(HwndSource hwndSource)
    {
        _hWndSource = hwndSource;
        IsViewing = false;
    }
    public bool StartViewingClipboard()
    {
        Win32.SetLastError(0);
        _hWndNextViewer = Win32.SetClipboardViewer(_hWndSource.Handle);
        if (_hWndNextViewer == IntPtr.Zero)
        {
            UInt32 eCode = Win32.GetLastError();
            if (eCode != 0)
            {
                return false;
            }
        }
        _hWndSource.AddHook(WinProc);
        IsViewing = true;
        return true;
    }

    public bool StopViewingClipboard()
    {
        Win32.SetLastError(0);
        Win32.ChangeClipboardChain(_hWndSource.Handle, _hWndNextViewer);
        _hWndNextViewer = IntPtr.Zero;
        _hWndSource.RemoveHook(WinProc);
        UInt32 eCode = Win32.GetLastError();
        IsViewing = false;
        return eCode == 0;
    }
    private IntPtr WinProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        switch (msg)
        {
            case Win32.WM_CHANGECBCHAIN:
                if (wParam == _hWndNextViewer)
                {
                    _hWndNextViewer = lParam;
                }
                else if (_hWndNextViewer != IntPtr.Zero)
                {
                    Win32.SendMessage(_hWndNextViewer, msg, wParam, lParam);
                }
                break;
            case Win32.WM_DRAWCLIPBOARD:
                OnClipboardContentChanged();
                Win32.SendMessage(_hWndNextViewer, msg, wParam, lParam);
                break;
        }
        return IntPtr.Zero;
    }
    public void Dispose()
    {
        if (IsViewing)
            StopViewingClipboard();
        _hWndSource = null;
        _hWndNextViewer = IntPtr.Zero;
    }
}