c# - ClipboardInterop 内容更改触发两次

标签 c# wpf winapi interop

我正在编写一个监视剪贴板更改的 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();
}

主窗口.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>

主窗口.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");
    }
}

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

最佳答案

好吧,我在 this 的帮助下修改了我的代码,显然我忘记删除 StopViewingClipboard 中的钩子(Hook)。

固定代码如下:

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

关于c# - ClipboardInterop 内容更改触发两次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15333746/

相关文章:

c# - 如何安全地重定向到引用页面?

c# - 用户控件位置

c# - TabControl.SelectedItem 不是 TabItem? RelativeSource 绑定(bind)失败

winapi - 带有 MOVEFILE_DELAY_UNTIL_REBOOT 的 MoveFileEx 删除而不是移动

c - 为什么我们需要给背景画笔颜色加1?

C#:无法将类型 A 转换为类型 B,其中 A = B

c# - 自定义配置部分 - 删除不需要的集合标签

c# - 如何以编程方式将 WPF 控件的颜色设置为系统颜色,以便它在配色方案更改时更新?

c# - ProgressBar 不更新

c++ - 构建DLL文件时,生成的LIB文件是否包含DLL名称?