94

C# からアクセスできるクリップボードの変更または更新されたイベントはありますか?

4

10 に答える 10

82

完全を期すために、プロダクション コードで使用しているコントロールを次に示します。デザイナーからドラッグしてダブルクリックするだけで、イベント ハンドラーを作成できます。

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;

namespace ClipboardAssist {

// Must inherit Control, not Component, in order to have Handle
[DefaultEvent("ClipboardChanged")]
public partial class ClipboardMonitor : Control 
{
    IntPtr nextClipboardViewer;

    public ClipboardMonitor()
    {
        this.BackColor = Color.Red;
        this.Visible = false;

        nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
    }

    /// <summary>
    /// Clipboard contents changed.
    /// </summary>
    public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged;

    protected override void Dispose(bool disposing)
    {
        ChangeClipboardChain(this.Handle, nextClipboardViewer);
    }

    [DllImport("User32.dll")]
    protected static extern int SetClipboardViewer(int hWndNewViewer);

    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);

    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
        // defined in winuser.h
        const int WM_DRAWCLIPBOARD = 0x308;
        const int WM_CHANGECBCHAIN = 0x030D;

        switch (m.Msg)
        {
            case WM_DRAWCLIPBOARD:
                OnClipboardChanged();
                SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                break;

            case WM_CHANGECBCHAIN:
                if (m.WParam == nextClipboardViewer)
                    nextClipboardViewer = m.LParam;
                else
                    SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                break;

            default:
                base.WndProc(ref m);
                break;
        }
    }

    void OnClipboardChanged()
    {
        try
        {
            IDataObject iData = Clipboard.GetDataObject();
            if (ClipboardChanged != null)
            {
                ClipboardChanged(this, new ClipboardChangedEventArgs(iData));
            }

        }
        catch (Exception e)
        {
            // Swallow or pop-up, not sure
            // Trace.Write(e.ToString());
            MessageBox.Show(e.ToString());
        }
    }
}

public class ClipboardChangedEventArgs : EventArgs
{
    public readonly IDataObject DataObject;

    public ClipboardChangedEventArgs(IDataObject dataObject)
    {
        DataObject = dataObject;
    }
}
}
于 2009-09-08T14:05:02.727 に答える
75

p/invoke を使用する必要があると思います。

[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);

C# でクリップボード モニターを設定する方法については、この記事を参照してください。

基本的には、アプリをクリップボード ビューアーとして登録します。

_ClipboardViewerNext = SetClipboardViewer(this.Handle);

そして、WM_DRAWCLIPBOARDメッセージを受け取ります。これは、オーバーライドすることで処理できますWndProc

protected override void WndProc(ref Message m)
{
    switch ((Win32.Msgs)m.Msg)
    {
        case Win32.Msgs.WM_DRAWCLIPBOARD:
        // Handle clipboard changed
        break;
        // ... 
   }
}

(やるべきことは他にもあります。クリップボード チェーンに沿って物を渡し、ビューの登録を解除しますが、それは記事から取得できます)

于 2009-03-07T09:24:08.330 に答える
29

私は WPF でこの課題に直面し、以下に説明するアプローチを使用することになりました。Windows フォームの場合、ClipboardHelper コントロールなど、この回答の他の場所に優れた例があります。

WPF の場合、WndProc をオーバーライドできないため、ウィンドウからソースを使用して HwndSource AddHook 呼び出しで明示的にフックする必要があります。クリップボード リスナーは、引き続き AddClipboardFormatListener ネイティブ相互運用呼び出しを使用します。

ネイティブ メソッド:

internal static class NativeMethods
{
    // See http://msdn.microsoft.com/en-us/library/ms649021%28v=vs.85%29.aspx
    public const int WM_CLIPBOARDUPDATE = 0x031D;
    public static IntPtr HWND_MESSAGE = new IntPtr(-3);

    // See http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool AddClipboardFormatListener(IntPtr hwnd);
}

クリップボード マネージャー クラス:

using System.Windows;
using System.Windows.Interop;

public class ClipboardManager
{
    public event EventHandler ClipboardChanged;

    public ClipboardManager(Window windowSource)
    {
        HwndSource source = PresentationSource.FromVisual(windowSource) as HwndSource;
        if(source == null)
        {
            throw new ArgumentException(
                "Window source MUST be initialized first, such as in the Window's OnSourceInitialized handler."
                , nameof(windowSource));
        }

        source.AddHook(WndProc);

        // get window handle for interop
        IntPtr windowHandle = new WindowInteropHelper(windowSource).Handle;

        // register for clipboard events
        NativeMethods.AddClipboardFormatListener(windowHandle);
    }

    private void OnClipboardChanged()
    {
        ClipboardChanged?.Invoke(this, EventArgs.Empty);
    }

    private static readonly IntPtr WndProcSuccess = IntPtr.Zero;

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == NativeMethods.WM_CLIPBOARDUPDATE)
        {
            OnClipboardChanged();
            handled = true;
        }

        return WndProcSuccess;
    }
}

これは、OnSourceInitialized 以降で Window.Loaded イベントや操作中にイベントを追加することで、WPF ウィンドウで使用されます。(ネイティブフックを使用するのに十分な情報がある場合):

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        // Initialize the clipboard now that we have a window soruce to use
        var windowClipboardManager = new ClipboardManager(this);
        windowClipboardManager.ClipboardChanged += ClipboardChanged;
    }

    private void ClipboardChanged(object sender, EventArgs e)
    {
        // Handle your clipboard update here, debug logging example:
        if (Clipboard.ContainsText())
        {
            Debug.WriteLine(Clipboard.GetText());
        }
    }
}

Ctrl-C キーを押すと、ゲームがクリップボードを介してアイテム情報を公開するため、Path of Exile アイテム アナライザー プロジェクトでこのアプローチを使用しています。

https://github.com/ColinDabritz/PoeItemAnalyzer

これが、WPF クリップボードの変更処理を行っている人に役立つことを願っています!

于 2015-10-08T14:19:27.753 に答える
13

これは古い投稿ですが、現在の一連の回答に比べて非常に単純な解決策を見つけました。私たちは WPF を使用しており、クリップボードにテキストが含まれている場合、独自のカスタム コマンド (ContextMenu 内) を有効または無効にしたいと考えていました。すでに ApplicationCommands.Cut、Copy、Paste があり、これらのコマンドはクリップボードの変更に正しく応答します。そのため、次の EventHandler を追加しました。

ApplicationCommands.Paste.CanExecuteChanged += new EventHandler(Paste_CanExecuteChanged);

private void Paste_CanExecuteChanged(object sender, EventArgs e) {
  ourVariable= Clipboard.ContainsText();
}

実際には、この方法で独自の Command で CanExecute を制御しています。私たちが必要としていたもので機能し、他の人を助けるかもしれません.

于 2014-01-15T19:17:17.077 に答える
6

以前の解決策の1つは、disposeメソッドでnullをチェックしないと思います。

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;

namespace ClipboardAssist {

// Must inherit Control, not Component, in order to have Handle
[DefaultEvent("ClipboardChanged")]
public partial class ClipboardMonitor : Control 
{
    IntPtr nextClipboardViewer;

    public ClipboardMonitor()
    {
        this.BackColor = Color.Red;
        this.Visible = false;

        nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
    }

    /// <summary>
    /// Clipboard contents changed.
    /// </summary>
    public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged;

    protected override void Dispose(bool disposing)
    {
        if(nextClipboardViewer != null)
            ChangeClipboardChain(this.Handle, nextClipboardViewer);
    }

    [DllImport("User32.dll")]
    protected static extern int SetClipboardViewer(int hWndNewViewer);

    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);

    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
        // defined in winuser.h
        const int WM_DRAWCLIPBOARD = 0x308;
        const int WM_CHANGECBCHAIN = 0x030D;

        switch (m.Msg)
        {
            case WM_DRAWCLIPBOARD:
                OnClipboardChanged();
                SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                break;

            case WM_CHANGECBCHAIN:
                if (m.WParam == nextClipboardViewer)
                    nextClipboardViewer = m.LParam;
                else
                    SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
                break;

            default:
                base.WndProc(ref m);
                break;
        }
    }

    void OnClipboardChanged()
    {
        try
        {
            IDataObject iData = Clipboard.GetDataObject();
            if (ClipboardChanged != null)
            {
                ClipboardChanged(this, new ClipboardChangedEventArgs(iData));
            }

        }
        catch (Exception e)
        {
            // Swallow or pop-up, not sure
            // Trace.Write(e.ToString());
            MessageBox.Show(e.ToString());
        }
    }
}

    public class ClipboardChangedEventArgs : EventArgs
    {
        public readonly IDataObject DataObject;

        public ClipboardChangedEventArgs(IDataObject dataObject)
        {
            DataObject = dataObject;
        }
    }
}
于 2011-09-06T21:09:36.290 に答える