クリップボード内のデータが変更されたときにそれを知る方法はないのでしょうか? もし知ることができればネットから効率よく情報を得ることができます。コピーしてペーストしなくてもコピーするだけで自動的にデータを保存することも可能になります。
C#でClipboard監視(Windows API) – Qiitaをみるとやり方が紹介されています。また原理的なことはビューアチェインをみるとわかりやすいです。
ではさっそくこれらを参考に作成してみましょう。
SetClipboardViewer() 関数
クリップボードに変更があるとビューアにWM_DRAWCLIPBOARD メッセージが通知されます。アプリを通知の対象にするためには手続きが必要です。それがSetClipboardViewer() 関数です。
Windows は1つのビューアにしかメッセージを通知しません。しかしビューアはひとつとは限りません。他のビューアに対してはどのように通知がおこなわれるのでしょうか?
SetClipboardViewer() 関数を実行するとビューアの次に登録されているビューアのハンドルを返します。SetClipboardViewer() が返したウィンドウにWM_DRAWCLIPBOARD メッセージを送信することで鎖にようにビューアプログラムがメッセージを流します。この構造をクリップボードビューアチェインと呼びます。
SetClipboardViewer() 関数を実行するということはクリップボードビューアチェインを意識しなければなりません。
ChangeClipboardChain() 関数
ビューアは、クリップボードビューアチェインから自分自身を削除することができます。終了するときや必要がなくなったときはChangeClipboardChain()でチェインから自分自身を削除してしまいましょう。
この関数には引数がふたつあります。ひとつめはチェインから削除したいウィンドウのハンドル、二つ目の引数は自分の次のチェインのハンドルを指定します。
ChangeClipboardChain()関数が呼ばれるとOSはチェインの最上のウィンドウにWM_CHANGECBCHAIN メッセージを送信します。
WM_CHANGECBCHAIN メッセージのサブメッセージはWPARAM にはチェインから外れるウィンドウのハンドルが、LPARAM には WPARAM のウィンドウ(チェインから外れるウィンドウ)の次のウィンドウのハンドルが格納されています。このメッセージをうまく処理しないとクリップボードビューアチェインがおかしくなります。
WM_CHANGECBCHAIN メッセージが送られてきたら、WPARAMの値を調べます。これが次のウィンドウのハンドルと同じであれば次のウィンドウはクリップボードビューアチェインから外れたことになります。そこで今後はLPARAM のハンドルに対してメッセージを送らなければなりません。
WPARAM が次のウィンドウでなければ、そのまま自分の次のウィンドウにWM_CHANGECBCHAIN メッセージを送るだけでOKです。
NativeWindow クラスを使ってみる
C#でClipboard監視(Windows API) – Qiitaでは、NativeWindow クラスを利用しています。
そのままサンプルプログラムをコピペするのでは面白くないのでちょっと変化をつけてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
using System.Windows.Forms; using System.Runtime.InteropServices; namespace ClipboardListenerLib { public class ClipboardListener : NativeWindow { [DllImport("user32")] public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer); [DllImport("user32")] public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext); [DllImport("user32")] public extern static int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); private const int WM_DRAWCLIPBOARD = 0x0308; private const int WM_CHANGECBCHAIN = 0x030D; private IntPtr nextHandle = IntPtr.Zero; public event EventHandler DrawClipboard; public ClipboardListener(Form f) { f.HandleCreated += OnHandleCreated; f.HandleDestroyed += OnHandleDestroyed; } internal void OnHandleCreated(object sender, EventArgs e) { // NativeWindowクラスへのForm登録(メッセージフック開始) AssignHandle(((Form)sender).Handle); // クリップボードチェインに登録 nextHandle = SetClipboardViewer(this.Handle); } internal void OnHandleDestroyed(object sender, EventArgs e) { // クリップボードチェインから削除 ChangeClipboardChain(this.Handle, nextHandle); // NativeWindowクラスの後始末(Formに対してのメッセージフック解除) ReleaseHandle(); } protected override void WndProc(ref Message msg) { if (msg.Msg == WM_DRAWCLIPBOARD) { if (nextHandle != IntPtr.Zero) { SendMessage(nextHandle, msg.Msg, msg.WParam, msg.LParam); } DrawClipboard?.Invoke(this, new EventArgs()); } if (msg.Msg == WM_CHANGECBCHAIN) { if (msg.WParam == nextHandle) { // WParamが次のウィンドウのハンドルと同じなら // 次のウィンドウはクリップボードビューアチェインから外れたことになる。 // 今後はLPARAM のハンドルに対してメッセージを送るため nextHandleを変更する nextHandle = msg.LParam; } else if (nextHandle != IntPtr.Zero) { // WPARAM が次のウィンドウでなければ、 // そのまま次のウィンドウにWM_CHANGECBCHAIN メッセージを送る SendMessage(nextHandle, msg.Msg, msg.WParam, msg.LParam); } } base.WndProc(ref msg); } } } |
ライブラリにすることで
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System; using System.Drawing; using System.Windows.Forms; using ClipboardListenerLib; public partial class Form1 : Form { public Form1() { InitializeComponent(); listener = new ClipboardListener(this); listener.DrawClipboard += Listener_DrawClipboard; } ClipboardListener listener; private void Listener_DrawClipboard(object sender, EventArgs e) { // クリップボードのデータが変化した。 } } |
あとはListener_DrawClipboardのなかでやりたいことをやります。
一例として・・・
クリップボードがイメージの場合、urlの場合は画像をファイルとして保存します。
GetFileName(int i)は保存用のファイル名をつくるメソッドです。またSaveFileFromUrl(string filePath, string url)はurlからjpgファイルを保存するためのメソッドです。
urlが本当にjpg画像なのか確認しないといけないのですが、省略しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private void Listener_DrawClipboard(object sender, EventArgs e) { if (Clipboard.ContainsImage()) { Image image = Clipboard.GetImage(); image.Save(GetFileName(num++), System.Drawing.Imaging.ImageFormat.Jpeg); } else if (Clipboard.ContainsText()) { string text = Clipboard.GetText(); if (text.Length > 5 && text.Substring(0, 4) == "http") { SaveFileFromUrl(GetFileName(num++), text); } } } |
1 2 3 4 5 |
int num = 1; string GetFileName(int i) { return String.Format("D:\\{0:000}.jpg", i); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public bool SaveFileFromUrl(string filePath, string url) { var ms = new MemoryStream(); try { var req = (HttpWebRequest)WebRequest.Create(url); var res = (HttpWebResponse)req.GetResponse(); var st = res.GetResponseStream(); byte[] buf = new byte[1000]; while (true) { int read = st.Read(buf, 0, buf.Length); if (read > 0) ms.Write(buf, 0, read); else break; } File.WriteAllBytes(filePath, ms.ToArray()); return true; } catch { return false; } } |
もっと簡単な驚愕の方法
文章を書いてから調べてみるともっと簡単な方法がありました。
Vista以降はAddClipboardFormatListener関数をつかったほうがよいです。チェイン管理をOSがやってくれます。WindowメッセージWM_DRAWCLIPBOARDとWM_CHANGECBCHAINを処理してチェインの維持をする必要はないようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System; using System.Windows.Forms; using System.Runtime.InteropServices; public partial class Form1 : Form { public Form1() { InitializeComponent(); } [DllImport("user32.dll", SetLastError = true)] private extern static void AddClipboardFormatListener(IntPtr hwnd); [DllImport("user32.dll", SetLastError = true)] private extern static void RemoveClipboardFormatListener(IntPtr hwnd); private void Form1_Load(object sender, EventArgs e) { AddClipboardFormatListener(Handle); } void OnClipboardUpdate() { // クリップボードのデータが変更された } private const int WM_CLIPBOARDUPDATE = 0x31D; protected override void WndProc(ref Message m) { if (m.Msg == WM_CLIPBOARDUPDATE) { OnClipboardUpdate(); m.Result = IntPtr.Zero; } else base.WndProc(ref m); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { RemoveClipboardFormatListener(Handle); } } |