こんなアプリをつくります。テキストエディタに文字入力するためにキーを押すとそのキーがアプリにも表示されます。
動画を使えば作成したプログラムがどのように動くのか説明しやすくなります。ところがそのときにどのキーを押したのかもわかるようにしたいです。ところが通常は他のアプリを操作しているときにどのキーが押されたかを知ることはできません。これを知るためにはグローバルフックをする必要があります。
フックをすればあるスレッド上で発生したイベントを監視、捕捉してそれに応じて処理を行なうことができます。
ローカルフックは一つのスレッドに対して監視するもので、自身のアプリケーションで主に使います。グローバルフックはすべてのスレッドに対する監視ができます。
KeyboardHookクラス
KeyboardHookというクラスを作成します。これでキーボードの操作に関してはフックすることができます。
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 |
public partial class Form1 : Form { public Form1() { } KeyboardHook keyboardHook = new KeyboardHook(); protected override void OnLoad(EventArgs e) { keyboardHook.KeyDownEvent += KeyboardHook_KeyDownEvent; keyboardHook.KeyUpEvent += KeyboardHook_KeyUpEvent; keyboardHook.Hook(); } private void KeyboardHook_KeyDownEvent(object sender, KeyEventArg e) { // キーが押されたときにやりたいこと } private void KeyboardHook_KeyUpEvent(object sender, KeyEventArg e) { // キーが離されたときにやりたいこと } protected override void OnFormClosing(FormClosingEventArgs e) { keyboardHook.UnHook(); } } |
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 |
class KeyboardHook { protected const int WH_KEYBOARD_LL = 0x000D; protected const int WM_KEYDOWN = 0x0100; protected const int WM_KEYUP = 0x0101; protected const int WM_SYSKEYDOWN = 0x0104; protected const int WM_SYSKEYUP = 0x0105; [StructLayout(LayoutKind.Sequential)] public class KBDLLHOOKSTRUCT { public uint vkCode; public uint scanCode; public KBDLLHOOKSTRUCTFlags flags; public uint time; public UIntPtr dwExtraInfo; } [Flags] public enum KBDLLHOOKSTRUCTFlags : uint { KEYEVENTF_EXTENDEDKEY = 0x0001, KEYEVENTF_KEYUP = 0x0002, KEYEVENTF_SCANCODE = 0x0008, KEYEVENTF_UNICODE = 0x0004, } [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, KeyboardProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); private delegate IntPtr KeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); private KeyboardProc proc; private IntPtr hookId = IntPtr.Zero; public void Hook() { if(hookId == IntPtr.Zero) { proc = HookProcedure; using(var curProcess = Process.GetCurrentProcess()) { using(ProcessModule curModule = curProcess.MainModule) { hookId = SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); } } } } public void UnHook() { UnhookWindowsHookEx(hookId); hookId = IntPtr.Zero; } public IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam) { if(nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)) { var kb = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)); var vkCode = (int)kb.vkCode; 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; OnKeyUpEvent(vkCode); } return CallNextHookEx(hookId, nCode, wParam, lParam); } public delegate void KeyEventHandler(object sender, KeyEventArg e); public event KeyEventHandler KeyDownEvent; public event KeyEventHandler KeyUpEvent; protected void OnKeyDownEvent(int keyCode) { KeyDownEvent?.Invoke(this, new KeyEventArg(keyCode)); } protected void OnKeyUpEvent(int keyCode) { KeyUpEvent?.Invoke(this, new KeyEventArg(keyCode)); } } |
1 2 3 4 5 6 7 8 9 |
public class KeyEventArg : EventArgs { public int KeyCode { get; } public KeyEventArg(int keyCode) { KeyCode = keyCode; } } |
Form1クラスにおける処理
まずデザイナで以下のようなものをつくります。
キーが押されているときはそのキーを表示し、離されたら表示をやめます。A-ZキーとShiftキー、Ctrlキー、Altキーは別に扱います。それ以外のキーはそのまま表示します(わける理由はShiftキーやCtrlキーを押しながらA-Zキーが押されている場合、それがわかるようにするため)。
KeyCodeからどのキーが押されているか調べるためにKeysConverterクラスを使っています。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); labelKey.Text = ""; labelShift.Text = ""; labelCtrl.Text = ""; labelAlt.Text = ""; } KeyboardHook keyboardHook = new KeyboardHook(); protected override void OnLoad(EventArgs e) { keyboardHook.KeyDownEvent += KeyboardHook_KeyDownEvent; keyboardHook.KeyUpEvent += KeyboardHook_KeyUpEvent; keyboardHook.Hook(); } // A-Zキーが押されているときは非0が入る int AtoZ = 0; private void KeyboardHook_KeyDownEvent(object sender, KeyEventArg e) { KeysConverter kc = new KeysConverter(); if(e.KeyCode >= 65 && e.KeyCode <= 90) { AtoZ = e.KeyCode; labelKey.Text = kc.ConvertToString(e.KeyCode); } else if(e.KeyCode == 160 || e.KeyCode == 161) labelShift.Text = "Shift"; else if(e.KeyCode == 162 || e.KeyCode == 163) labelCtrl.Text = "Ctrl"; else if(e.KeyCode == 164 || e.KeyCode == 165) labelAlt.Text = "Alt"; else labelKey.Text = kc.ConvertToString(e.KeyCode); } private void KeyboardHook_KeyUpEvent(object sender, KeyEventArg e) { KeysConverter kc = new KeysConverter(); if(e.KeyCode >= 65 && e.KeyCode <= 90 && AtoZ == e.KeyCode) { AtoZ = 0; labelKey.Text = ""; } else if(e.KeyCode == 160 || e.KeyCode == 161) labelShift.Text = ""; else if(e.KeyCode == 162 || e.KeyCode == 163) labelCtrl.Text = ""; else if(e.KeyCode == 164 || e.KeyCode == 165) labelAlt.Text = ""; else if(AtoZ == 0) labelKey.Text = ""; else labelKey.Text = kc.ConvertToString(AtoZ); } protected override void OnFormClosing(FormClosingEventArgs e) { keyboardHook.UnHook(); } } |
今この例題をやっていますが、CS0229が頻発しますね。
意味がわからないのが、
「CS0229 ‘KeyboardHook.hookId’ と ‘KeyboardHook.hookId’ 間があいまいです」
というもの(あくまで一例です。
1)これ実際にプロジェクトに組み込むときは、どのソースをどの位置に入れ、Usingはどういう宣言をすべきなのでしょう?
2)一部の関数で変数を略している部分があるようで、それがこういうエラーの原因担ってるように思うのですがどう修正すべきでしょうか?
ちなみに、VS2022のC#を使っております。
質問していただきありがとうございます。
掲載されているコードをコピペして実際に動くかやってみましたが、問題なく動作しているようです。
確認してほしいこととして
Form1クラス
KeyboardHookクラス
KeyEventArgクラスはそれぞれ独立したものになっているか?
Form1クラスのなかで他の2つのクラスを定義していないかを確認してください。
KeyboardHookクラスを定義するときは
using System.Runtime.InteropServices;
using System.Diagnostics;
この2行が必要です。using System;も必要ですが、これはVSを使っていれば自動的に入るはずです。
Form1クラスはデフォルトのまま(using System;とusing System.Windows.Forms;があればよい)でOKです。
やりたいことができました。感謝しています。
非アクティブ時のキー入力いろいろ調べてた結果ここにたどり着いて作りたいものが実現できました!!
ありがとうございます!!
助かりました!!
まだまだ自分の勉強不足が多くあって、
戸惑ったところありましたが
本当にコピペだけできちんと動きました。
ありがとうございます。
Git Hubとかでソース公開していただけると
もう「神」の領域です!