午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

クリップボードの更新を監視、AddClipboardFormatListener

WPFでAddClipboardFormatListenerを使ってクリップボードの更新を監視
クリップボードが更新されて画像があったらListBoxに追加するアプリ

f:id:gogowaten:20190922132135p:plain
alt+PrintScreenでVisual Studioのウィンドウを取り込んだところ


github.com

f:id:gogowaten:20190922131039p:plain
XAMLデザイン画面


MainWindow.xaml

<Window x:Class="_20190922_クリップボード監視2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20190922_クリップボード監視2"
        mc:Ignorable="d"
        Title="MainWindow" Height="550" Width="800">
  <Grid>
    <StackPanel Name="MyStackPanel">
      <Button x:Name="MyButtonStart" Content="Start" Click="MyButtonStart_Click"/>
      <Button x:Name="MyButtonStop" Content="Stop" Click="MyButtonStop_Click"/>
      <ListBox Name="MyListBox" Height="450"/>
    </StackPanel>
  </Grid>
</Window>


MainWindow.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Interop;


namespace _20190922_クリップボード監視2
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        ClipboardWatcher clipboardWatcher = null;
        
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += MainWindow_Loaded;
            this.Closing += MainWindow_Closing;
        }

        private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            clipboardWatcher.Stop();
        }

        //アプリ起動直後
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //Clipboardwatcher作成
            //ウィンドウハンドルを渡す
            clipboardWatcher = new ClipboardWatcher(new WindowInteropHelper(this).Handle);
            //クリップボード内容変更イベントに関連付け
            clipboardWatcher.DrawClipboard += ClipboardWatcher_DrawClipboard;
        }

        //private void ClipboardWatcher_DrawClipboard(object sender, EventArgs e)
        //{
        //    if (Clipboard.ContainsImage())
        //    {
        //        var img = new Image();
        //        img.Source = Clipboard.GetImage();//たまにエラーになる
        //        MyListBox.Items.Add(img);
        //    }
        //}

     

        //クリップボード内容変更イベント時の処理
        private void ClipboardWatcher_DrawClipboard(object sender, EventArgs e)
        {
            AddImage();
        }

        //画像があったらlistBoxに追加        
        //たまにエラーになるけど繰り返すと取得できるから
        //回数制限をつけて回している
        //それでも取得できなかったらエラーメッセージ表示
        private void AddImage()
        {
            if (Clipboard.ContainsImage())
            {
                var img = new Image();
                int count = 1;
                int limit = 5;//取得試行回数制限、1以上指定、5あれば十分
                do
                {
                    try
                    {
                        img.Source = Clipboard.GetImage();//たまにエラーになる
                        MyListBox.Items.Add(img);
                    }
                    catch (Exception ex)
                    {
                        if (count == limit)
                        {
                            string str = $"{ex.Message}\n" +
                                $"画像の取得に失敗\n" +
                                $"{limit}回試行";
                            MessageBox.Show(str);
                        }
                    }
                    finally
                    {
                        count++;
                    }
                } while (limit >= count && img.Source == null);
            }
        }

        ////クリップボードに画像があったら取得
        ////エラーが出なければこれでいい
        //private void AddImage2()
        //{
        //    if (Clipboard.ContainsImage())
        //    {
        //        BitmapSource img = Clipboard.GetImage();//たまにエラーになる
        //    }
        //}



        //クリップボード監視開始
        private void MyButtonStart_Click(object sender, RoutedEventArgs e)
        {
            clipboardWatcher.Start();
            MessageBox.Show("start");
        }

        //クリップボード監視中止
        private void MyButtonStop_Click(object sender, RoutedEventArgs e)
        {
            clipboardWatcher.Stop();
            MessageBox.Show("stop");
        }
    }


    /// <summary>
    /// AddClipboardFormatListenerを使ったクリップボード監視
    /// クリップボード更新されたらDrawClipboardイベント起動
    /// </summary>
    public class ClipboardWatcher
    {
        [DllImport("user32.dll")]
        private static extern bool AddClipboardFormatListener(IntPtr hWnd);
        [DllImport("user32.dll")]
        private static extern bool RemoveClipboardFormatListener(IntPtr hWnd);

        private const int WM_DRAWCLIPBOARD = 0x031D;

        IntPtr handle;
        HwndSource hwndSource = null;


        public event EventHandler DrawClipboard;
        //イベント起動
        private void raiseDrawClipboard()
        {
            DrawClipboard?.Invoke(this, EventArgs.Empty);
        }
        //↑は↓と同じ意味
        //private void raiseDrawClipboard()
        //{
        //    if (DrawClipboard != null)
        //    {
        //        DrawClipboard(this, EventArgs.Empty);
        //    }
        //}


        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == WM_DRAWCLIPBOARD)
            {
                this.raiseDrawClipboard();
                handled = true;
            }
            return IntPtr.Zero;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="handle">System.Windows.Interop.WindowInteropHelper(this).Handleとかで取得</param>
        public ClipboardWatcher(IntPtr handle)
        {
            hwndSource = HwndSource.FromHwnd(handle);
            hwndSource.AddHook(WndProc);
            this.handle = handle;
            //AddClipboardFormatListener(handle);
        }

        //クリップボード監視開始
        public void Start()
        {
            AddClipboardFormatListener(handle);
        }

        //クリップボード監視停止
        public void Stop()
        {
            RemoveClipboardFormatListener(handle);
        }
    }
}


126行目以降がクリップボードの監視するクラスで、これを使っているのが1行目からのアプリ


なぜかエラーになる

//94:
        ////クリップボードに画像があったら取得
        ////エラーが出なければこれでいい
        //private void AddImage2()
        //{
        //    if (Clipboard.ContainsImage())
        //    {
        //        BitmapSource img = Clipboard.GetImage();//たまにエラーになる
        //    }
        //}

これだと5回に1回位エラーになるので

f:id:gogowaten:20190922134747p:plain
クリップボードからの画像取得に失敗

書き換えたのが

//58:
        //画像があったらlistBoxに追加        
        //たまにエラーになるけど繰り返すと取得できるから
        //回数制限をつけて回している
        //それでも取得できなかったらエラーメッセージ表示
        private void AddImage()
        {
            if (Clipboard.ContainsImage())
            {
                var img = new Image();
                int count = 1;
                int limit = 5;//取得試行回数制限、1以上指定、5あれば十分
                do
                {
                    try
                    {
                        img.Source = Clipboard.GetImage();//たまにエラーになる
                        MyListBox.Items.Add(img);
                    }
                    catch (Exception ex)
                    {
                        if (count == limit)
                        {
                            string str = $"{ex.Message}\n" +
                                $"画像の取得に失敗\n" +
                                $"{limit}回試行";
                            MessageBox.Show(str);
                        }
                    }
                    finally
                    {
                        count++;
                    }
                } while (limit >= count && img.Source == null);
            }
        }

do~whileで回している
3回試行でほとんどエラーが出なくなったので、少し余裕をもたせて5回試行にしている
根本的な解決になっていないけど、わからん

追記ここから
不具合、クリップボードに変更がなくても5分間隔で変更通知が来る
どこが原因なのかわからん
ってことで対処してみた

gogowaten.hatenablog.com

gogowaten.hatenablog.com 解決!別のアプリの設定が原因だった

追記ここまで2019/10/06


参照したところ
WPFでWndProcイベントをキャプチャーする (WPFプログラミング) www.ipentec.com

WPFアプリケーションでウィンドウプロシージャをフックする - Qiita qiita.com

オレメモ: クリップボードを監視してみる。 chikaratakanashi.blogspot.com

C#|WPF|クリップボードの変更を監視する | 貧脚レーサーのサボり日記 anis774.net




関連記事 gogowaten.hatenablog.com

gogowaten.hatenablog.com

これをWPFC#で作り直したい

今回のを使ったアプリ
gogowaten.hatenablog.com