ClipboardInterop content changed fires twice

亡梦爱人 提交于 2019-12-12 18:36:07

问题


I am writing a WPF application which monitors Clipboard changes.

I wrote the following class:

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 looks like:

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

So now when I start the application all is fine, the event is fired once when I copy text to the clipboard. When I click the button, and copy text to clipboard again, the event is fired twice. Another button click and the event is fired three times. I don't know why this is happening. Can anybody help me out?


回答1:


Well I modified my code with the help of this, and obviously I forgot to remove the hook in StopViewingClipboard.

Here's the fixed code:

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


来源:https://stackoverflow.com/questions/15333746/clipboardinterop-content-changed-fires-twice

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!