コピーしたデータは基本的に直前のものしか使えません。コピーやカットをすると以前にクリップボードに保存されていたデータは消えてしまいます。このようなデータの履歴をとっておいて、また使いたいときに使えるようにしておくと便利かもしれません。そこでクリップボードの履歴をとるアプリを作ってみようと思います。
私が使っているのはCliborです。
クリップボード履歴や定型文の管理 履歴は最大10,000件 FIFO・migemo他 シンプルで高機能 Win10対応
だから別に自分でつくる必要はないのですが、あえて(四角い)車輪の再発明をすることにします。
テキストがコピーされたことを知るには?
まずテキストがコピーされたことを知るにはどうすればいいのでしょうか?
これが使えそうです。
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 const int WM_CLIPBOARDUPDATE = 0x31D; private void Form1_Load(object sender, EventArgs e) { AddClipboardFormatListener(Handle); } 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); } void OnClipboardUpdate() { // クリップボードのデータが変更された時の処理 } } |
API関数のAddClipboardFormatListener関数とRemoveClipboardFormatListener関数。そしてWM_CLIPBOARDUPDATEメッセージを使えばクリップボードの内容が変更されたことがわかります。あとはクリップボードの内容がテキストであるかどうか調べて、テキストであれば必要な処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
List<string> vs = new List<string>(); void OnClipboardUpdate() { // クリップボードのデータが変更された if (Clipboard.ContainsText()) { string str = Clipboard.GetText(); vs.Insert(0, str); string itemText = str.Replace("\n", "\\n"); itemText = itemText.Replace("\t", "\\t"); itemText = itemText.Replace(" ", ""); itemText = itemText.Replace(" ", ""); listBox1.Items.Insert(0, itemText); } } |
これで履歴をとることはできます。
つぎに履歴からテキストを選択できるようにする必要があります。
フォームの左側にリストボックスを表示し、アイテムを選択したらリッチテキストボックスにテキストの内容を表示させています。そしてペーストボタンをクリックすればクリップボードにテキストがコピーされます。
1 2 3 4 5 6 7 8 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { int i = listBox1.SelectedIndex; Clipboard.SetText(vs[i]); } } |
ただちょっと不便です。テキストエディタを使っていてウィンドウを切り替えるというのは地味に面倒です。そこでショートカットキーを登録しておいて、これを押したらフォームが現れるようにすると少しは使いやすくなるかもしれません。
ホットキーを登録してみる
ホットキーを登録するためにはAPI関数のRegisterHotKeyを使います。また必要なくなったら削除する必要があります。このときに必要になるのが UnregisterHotKey関数です。
アプリケーションが開始されたら登録して終了時に解除するようにする。これでいいでしょう。
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 |
public partial class Form1 : Form { private void Form1_Load(object sender, EventArgs e) { AddClipboardFormatListener(Handle); MyRegisterHotKey(); } void MyRegisterHotKey() { for (int i = 0x0000; i <= 0xbfff; i++) { if (RegisterHotKey(this.Handle, i, MOD_KEY.CONTROL | MOD_KEY.ALT, Keys.V) != 0) { id = i; break; } } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { UnregisterHotKey(this.Handle, id); RemoveClipboardFormatListener(Handle); } } |
アプリケーション開始時に、自作メソッドのMyRegisterHotKeyでCtrlキーとAltキーを押しながらVキーを押したときにフォームが表示されるようにしています。WM_HOTKEYメーセージを捕捉したら登録したホットキーが押されたということなので、自作メソッド OnPressHotkeyを呼び出しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { protected override void WndProc(ref Message m) { if (m.Msg == WM_HOTKEY) { if ((int)m.WParam == id) { OnPressHotkey(); m.Result = IntPtr.Zero; } } if (m.Msg == WM_CLIPBOARDUPDATE) { // 省略 } base.WndProc(ref m); } } |
OnPressHotkeyメソッドがやっていることはフォームが見えるように最上位に表示させることです。
1 2 3 4 5 6 7 8 |
public partial class Form1 : Form { void OnPressHotkey() { this.TopMost = true; this.TopMost = false; } } |
ペーストも自動でできるようにする
これでCtrlキー+Altキー+Vでフォームが前面に表示されるのですが、ボタンを押したらクリップボードに文字列がコピーされて、そのあとメニュー[ペースト]を選択したり、Ctrl+Vを押すなどの方法でペーストをするのでは、これも面倒くさいです。ボタンを押したら自動でペーストまでやってくれると便利です。
そこでペーストも自動でできるようにするにはどうすればいいのでしょうか? ホットキーが押されたときに編集されていたウィンドウがどれなのか記憶させることができればペーストも自動でできるようになりそうです。
OnPressHotkey()を少し変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd); IntPtr _oldForeground = IntPtr.Zero; void OnPressHotkey() { _oldForeground = GetForegroundWindow(); this.TopMost = true; this.TopMost = false; } } |
まずホットキーが押されたらフォームを最前面にもってくるまえに現在最前列にあるアプリのウィンドウハンドルを記憶しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { int i = listBox1.SelectedIndex; Clipboard.SetText(vs[i]); if (SetForegroundWindow(_oldForeground)) { SendKeys.Send("^V"); } } } |
そしてボタンが押されたら、まずテキストをクリップボードに転送したあと、保存しておいた_oldForegroundをAPI関数 SetForegroundWindow関数をつかってそのウィンドウをアクティブにします。それからペーストのためのキーボード操作をアクティブなウィンドウに送信します。
またこのときもクリップボードの内容が変化することになりますが、この履歴は必要ないと思います。そこで以下のような処理はどうでしょうか?
クリップボードが変化しても無視をするフラグとしてignoreClipbordChangeというフィールド変数を用意します。そして
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 partial class Form1 : Form { bool ignoreClipbordChange = false; private void button1_Click(object sender, EventArgs e) { int i = listBox1.SelectedIndex; ignoreClipbordChange = true; Clipboard.SetText(vs[i]); Timer t = new Timer(); t.Tick += T_Tick; t.Interval = 1000; t.Start(); if (_oldForeground != IntPtr.Zero && SetForegroundWindow(_oldForeground)) { SendKeys.Send("^V"); } } void T_Tick(object sender, EventArgs e) { ignoreClipbordChange = false; Timer t = (Timer)sender; t.Stop(); t.Dispose(); } } |