[C#]Windowsフォームアプリでフォーム外マウス・キーボードイベントを取得する

ドキュメントの多さを期待して古代のもので作ったらハマったのでメモ

## 環境
- Visual Studio Community 2017 ver.15.7.5
- .NET Framework 4.7.1

## 使うもの

- マウス：[グローバルフック・ザ・マウス](http://hongliang.seesaa.net/article/7651626.html)より，`MouseHook2.cs`をダウンロード
 - `MouseHook.cs`にリネームする
- キーボード：[グローバルフック・ザ・キーボード](http://hongliang.seesaa.net/article/7539988.html)より，`KeyboardHook3.cs`をダウンロード
 - `KeyboardHook.cs`にリネームする

## コードの書き換え
古代のコード故に今のVSだとそのまま動かないらしいので，上記サイトのコメント欄を参考にし，一部を書き換える必要がある。
※解決法としてあるVSのデバッグオプションの項目はVS2017から消えたらしい（？）
各々，2箇所を以下のように変更する。

```cs:MouseHook.csの一部
	[DefaultEvent("MouseHooked")]
	public class MouseHook : System.ComponentModel.Component {
		[DllImport("user32.dll", SetLastError=true)]
		private static extern IntPtr SetWindowsHookEx(int hookType, MouseHookDelegate hookDelegate, IntPtr hInstance, uint threadId);
		[DllImport("user32.dll", SetLastError=true)]
		private static extern int CallNextHookEx(IntPtr hook, int code, MouseMessage message, ref MouseState state);
		[DllImport("user32.dll", SetLastError=true)]
		private static extern bool UnhookWindowsHookEx(IntPtr hook);
        /* 追加部分 */
        [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern IntPtr LoadLibrary(string lpFileName);
        IntPtr module = LoadLibrary("user32.dll");
        /*---------*/
        private const int MouseLowLevelHook = 14;
		private delegate int MouseHookDelegate(int code, MouseMessage message, ref MouseState state);
		private IntPtr hook;
		private GCHandle hookDelegate;

		private static readonly object EventMouseHooked = new object();
		///<summary>マウスが入力されたときに発生する。</summary>
		public event MouseHookedEventHandler MouseHooked  {
			add {base.Events.AddHandler(EventMouseHooked, value);}
			remove {base.Events.RemoveHandler(EventMouseHooked, value);}
		}
		///<summary>
		///インスタンスを作成する。
		///</summary>
		///<exception cref="Win32Exception">フックに失敗しました。原因の詳細はエラーコードを参照してください。</exception>
		public MouseHook() {
			if (Environment.OSVersion.Platform != PlatformID.Win32NT)
				throw new PlatformNotSupportedException("Windows 98/Meではサポートされていません。");
			MouseHookDelegate handler = new MouseHookDelegate(CallNextHook);
			this.hookDelegate = GCHandle.Alloc(handler);
            //IntPtr module = Marshal.GetHINSTANCE(typeof(MouseHook).Assembly.GetModules()[0]); // ここを消すかコメントアウト
            this.hook = SetWindowsHookEx(MouseLowLevelHook, handler, module, 0);
            if (hook == IntPtr.Zero)
				throw new Win32Exception(Marshal.GetLastWin32Error());
		}
```

```cs:KeyboardHook.csの一部
	[DefaultEvent("KeyboardHooked")]
	public class KeyboardHook : Component {
		[DllImport("user32.dll", SetLastError=true)]
		private static extern IntPtr SetWindowsHookEx(int hookType, KeyboardHookDelegate hookDelegate, IntPtr hInstance, uint threadId);
		[DllImport("user32.dll", SetLastError=true)]
		private static extern int CallNextHookEx(IntPtr hook, int code, KeyboardMessage message, ref KeyboardState state);
		[DllImport("user32.dll", SetLastError=true)]
		private static extern bool UnhookWindowsHookEx(IntPtr hook);
        /* 追加部分 */
        [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern IntPtr LoadLibrary(string lpFileName);
        IntPtr module = LoadLibrary("user32.dll");
        /*---------*/
        private delegate int KeyboardHookDelegate(int code, KeyboardMessage message, ref KeyboardState state);
		private const int KeyboardHookType = 13;
		private GCHandle hookDelegate;
		private IntPtr hook;
		private static readonly object EventKeyboardHooked = new object();
		///<summary>キーボードが操作されたときに発生する。</summary>
		public event KeyboardHookedEventHandler KeyboardHooked {
			add {base.Events.AddHandler(EventKeyboardHooked, value);}
			remove {base.Events.RemoveHandler(EventKeyboardHooked, value);}
		}
		///<summary>
		///KeyboardHookedイベントを発生させる。
		///</summary>
		///<param name="e">イベントのデータ。</param>
		protected virtual void OnKeyboardHooked(KeyboardHookedEventArgs e) {
			KeyboardHookedEventHandler handler = base.Events[EventKeyboardHooked] as KeyboardHookedEventHandler;
			if (handler != null)
				handler(this, e);
		}
		///<summary>
		///新しいインスタンスを作成する。
		///</summary>
		public KeyboardHook() {
			if (Environment.OSVersion.Platform != PlatformID.Win32NT)
				throw new PlatformNotSupportedException("Windows 98/Meではサポートされていません。");
			KeyboardHookDelegate callback = new KeyboardHookDelegate(CallNextHook);
			this.hookDelegate = GCHandle.Alloc(callback);
			//IntPtr module = Marshal.GetHINSTANCE(typeof(KeyboardHook).Assembly.GetModules()[0]); // ここを消すかコメントアウト
			this.hook = SetWindowsHookEx(KeyboardHookType, callback, module, 0);
		}
```

## プロジェクトへの追加

1. ファイル→追加→新しいプロジェクト→クラスライブラリ(.NET Framework)を作成
2. ソリューションエクスプローラー上で追加したクラスライブラリを右クリック→追加→既存の項目で`MouseHook.cs`と`KeyboardHook.cs`を追加
3. 同様に右クリック→追加→参照で，`System.Drawing`と`System.Windows.Forms`にチェックを入れてOK
4. ソリューションエクスプローラー上でイベントを使うプロジェクト（元のプロジェクト）を右クリック→追加→参照→プロジェクトで今作成したクロスライブラリを追加
5. ビルドする
6. フォームデザインを開き，ツールボックスをクリック
7. `MouseHook`と`KeyboardHook`が追加されていればOK

## MouseHookとKeyboardHookの利用

1. 追加された`MouseHook`と`KeyboardHook`をダブルクリックし，フォームに追加する。正しくできていれば，フォームのデザイン画面の下に`MouseHook1`と`KeyboardHook1`ができているはず
2. 追加した`MouseHook1`と`KeyboardHook1`をダブルクリックすると，Form1.cs上に各々のイベント検知コードが追加される

※ `using HongliangSoft.Utilities.Gui;`を書いておくと良い

例
`using HongliangSoft.Utilities.Gui;`が書いてある前提

```
private void mouseHook1_MouseHooked(object sender, MouseHookedEventArgs e) {
    if (e.Message == MouseMessage.LDown) {
        /* 左クリックを検知したときの動作 */
    }
}
```

```
private void keyboardHook1_KeyboardHooked(object sender, KeyboardHookedEventArgs e) {
    if(e.UpDown == KeyboardUpDown.Down) {
        if(e.KeyCode == Keys.Space) {
            /* スペースキーが押されたときの動作 */
        }
    }
}
```

## その他
マウスの座標は特別なことをしなくても`Cursor`で取れるっぽい