ではクリップボードの履歴から文字列をペーストすることができるようにしましたが、メニューをキーボードで選択することはできませんでした。マウスに持ち替えるのは面倒なのでキー操作で選択できるようにします。
それから履歴の上限はない、履歴をとるとらないの選択ができないので必要のないものまで履歴をとってしまったり、必要なものをすぐに選ぶことができないという問題もありました。
さらにパスワードらしきもの(文字列に全角文字や改行がない場合)は機械的に履歴にとらないようにしてきましたが、ソースコードも半角英数ばかりなので履歴を取ることができませんでした。パスワードらしきものがクリップボードに転送された場合はユーザーにどのように処理をするのか選択できるようにします。
キーフックをして他のアプリケーションでキーが押されたとき、もしコンテキストメニューが表示されているのであれば自作メソッド SelectMenuItemFromKeyCode(KeyEventArg e)を呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { private void KeyboardHook_KeyDownEvent(object sender, KeyEventArg e) { if(e.KeyCode == 160 || e.KeyCode == 161) { bool b = DateTime.Now < dtLastKeyDowm.AddSeconds(1); dtLastKeyDowm = DateTime.Now; if(b) ShowMenu(); } else { // コンテキストメニューが表示されているときに何らかのキーが押された if(contextMenuStrip.Visible) { SelectMenuItemFromKeyCode(e); } } } } |
自作メソッド SelectMenuItemFromKeyCode(KeyEventArg e)は以下のような内容になっています。
引数からどのキーが押されたかを調べて「1」ならコンテキストメニューの最初のアイテム、「2」なら2番目のアイテムが選択されたのと同じ処理をおこなわせます。
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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
public partial class Form1 : Form { void SelectMenuItemFromKeyCode(KeyEventArg e) { KeysConverter kc = new KeysConverter(); string str = kc.ConvertToString(e.KeyCode); string pasteString = ""; if(str == "1") { if(CopiedStrings.Count > 0) pasteString = CopiedStrings[0]; } else if(str == "2") { if(CopiedStrings.Count > 1) pasteString = CopiedStrings[1]; } else if(str == "3") { if(CopiedStrings.Count > 2) pasteString = CopiedStrings[2]; } else if(str == "4") { if(CopiedStrings.Count > 3) pasteString = CopiedStrings[3]; } else if(str == "5") { if(CopiedStrings.Count > 4) pasteString = CopiedStrings[4]; } else if(str == "6") { if(CopiedStrings.Count > 5) pasteString = CopiedStrings[5]; } else if(str == "7") { if(CopiedStrings.Count > 6) pasteString = CopiedStrings[6]; } else if(str == "8") { if(CopiedStrings.Count > 7) pasteString = CopiedStrings[7]; } else if(str == "9") { if(CopiedStrings.Count > 8) pasteString = CopiedStrings[8]; } else if(str == "A") { if(CopiedStrings.Count > 9) pasteString = CopiedStrings[9]; } else if(str == "B") { if(CopiedStrings.Count > 10) pasteString = CopiedStrings[10]; } else if(str == "C") { if(CopiedStrings.Count > 11) pasteString = CopiedStrings[11]; } else if(str == "D") { if(CopiedStrings.Count > 12) pasteString = CopiedStrings[12]; } else if(str == "E") { if(CopiedStrings.Count > 13) pasteString = CopiedStrings[13]; } else if(str == "F") { if(CopiedStrings.Count > 14) pasteString = CopiedStrings[14]; } else if(str == "G") { if(CopiedStrings.Count > 15) pasteString = CopiedStrings[15]; } else if(str == "H") { if(CopiedStrings.Count > 16) pasteString = CopiedStrings[16]; } else if(str == "I") { if(CopiedStrings.Count > 17) pasteString = CopiedStrings[17]; } else if(str == "J") { if(CopiedStrings.Count > 18) pasteString = CopiedStrings[18]; } else if(str == "K") { if(CopiedStrings.Count > 19) pasteString = CopiedStrings[19]; } contextMenuStrip.Visible = false; if(pasteString != "") { // 通常のキー操作をおこなわせない(後述) e.Cancel = true; isIgnoreUpdateClipboard = true; Clipboard.SetText(pasteString); SendKeys.Send("^(v)"); Timer timer = new Timer(); timer.Interval = 1000; timer.Tick += Timer_Tick; timer.Start(); } } } |
それからどのキーを押したらどんな文字列がペーストされるのかをコンテキストメニューに表示したほうが使いやすいと思われます。そこでShowMenu()メソッドを以下のように改良します。
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 |
public partial class Form1 : Form { void ShowMenu() { contextMenuStrip.Items.Clear(); int count = CopiedStrings.Count; if(count == 0) { contextMenuStrip.Items.Add("[キャンセル]クリップボードの履歴はありません"); Point pos = Cursor.Position; contextMenuStrip.Opacity = 0.9; contextMenuStrip.BackColor = Color.LightBlue; contextMenuStrip.Show(pos); return; } string[] key = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", }; for(int i =0; i< count; i++) { string itemText = GetMenuText(CopiedStrings[i]); if(i < key.Length) itemText = key[i] + ": " +itemText; contextMenuStrip.Items.Add(itemText, null, OnEvent); } contextMenuStrip.Items.Add(new ToolStripSeparator()); contextMenuStrip.Items.Add("[キャンセル]"); Point p = Cursor.Position; contextMenuStrip.Opacity = 0.9; contextMenuStrip.BackColor = Color.LightBlue; contextMenuStrip.Show(p); } } |
またコンテキストメニューの項目を選択するために「1」とか「a」のキーが押されたら、その文字もエディタに入力されてしまいます。そこでこの場合は通常の処理はおこなわれないようにする必要があります。そのためにはフックプロシージャーのなかで CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam)を呼ばずにIntPtr(1)を返せばよいのです。
そこでKeyboardHookクラスとKeyEventArgクラスを少し変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class KeyEventArg : EventArgs { public int KeyCode { get; } public KeyEventArg(int keyCode) { KeyCode = keyCode; } // このプロパティを追加した public bool Cancel { get; set; } = false; } |
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 |
class KeyboardHook { protected bool OnKeyDownEvent(int keyCode) { KeyEventArg args = new KeyEventArg(keyCode); KeyDownEvent?.Invoke(this, args); // もし KeyEventArg.Cancelプロパティがtrueなら // HookProcedure(int nCode, IntPtr wParam, IntPtr lParam)メソッドのなかで // CallNextHookEx(hookId, nCode, wParam, lParam)を呼び出さない if(args.Cancel) return true; else return false; } public IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam) { bool cancel = false; // 本来の動作をさせない if(nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)) { var kb = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)); var vkCode = (int)kb.vkCode; cancel = OnKeyDownEvent(vkCode); } else if(nCode >= 0 && (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP)) { var kb = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)); var vkCode = (int)kb.vkCode; cancel = OnKeyUpEvent(vkCode); } if(cancel) return new IntPtr(1); else return CallNextHookEx(hookId, nCode, wParam, lParam); } } |
次に履歴をとるとらないの選択ができない問題を解消します。また履歴の最大件数も制御できるようにします。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); AddClipboardFormatListener(Handle); keyboardHook.KeyDownEvent += KeyboardHook_KeyDownEvent; keyboardHook.Hook(); InitNotifyIcon(); InitCheckedListBox(); InitNumericUpDown(); } void InitNumericUpDown() { numericUpDown1.Value = 10; numericUpDown1.Minimum = 1; } void OnClipboardUpdate() { // [クリップボードの履歴を取らない]がチェックされているときは無視 if(isIgnoreUpdateClipboard || checkBoxIgnoreUpdateClipboard.Checked) return; // クリップボード内にある変更されたデータはテキスト if(Clipboard.ContainsText()) { string str = Clipboard.GetText(); if(!IsPassWord(str)) { InsertFirst(str); // 上限を超えている場合は先頭のn件のみ取得。 // リストの項目も変更する int max = (int)numericUpDown1.Value; if(CopiedStrings.Count > max) { CopiedStrings = CopiedStrings.Take(max).ToList(); checkedListBox1.Items.Clear(); foreach(string str1 in CopiedStrings) checkedListBox1.Items.Add(GetMenuText(str1)); } } else { // パスワードらしきものがクリップボードに転送されたときの処理 OnSetClipboardPassWord(str); } } } } |
次にパスワードらしきものがクリップボードに転送された場合、ユーザーにどのように処理をするのか選択させるための機能を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { void OnSetClipboardPassWord(string str) { if(DialogResult.Yes == MessageBox.Show("パスワードらしき文字列がクリップボードに転送されました。履歴を保存しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question)) { InsertFirst(str); int max = (int)numericUpDown1.Value; if(CopiedStrings.Count > max) { CopiedStrings = CopiedStrings.Take(max).ToList(); checkedListBox1.Items.Clear(); foreach(string str1 in CopiedStrings) checkedListBox1.Items.Add(GetMenuText(str1)); } } } } |