ジョークアプリ ウィンドウタイトルを書き換える
MouseMoveイベントを利用すればフォーム上をマウスが移動したときにそれを知ることができます。ところが他のアプリのウィンドウ上を移動した場合はそれを知ることはできません、
しかしマウスフックをすれば他のウィンドウでマウスが操作されたことを知ることができます。マウスがフォーム上から外れた場所で移動してもそれを知ることができるのです。
ではマウスフックをするクラスを作成してみることにしましょう。マウスが移動したりクリックされたときにイベントを発生させることができます。
SetWindowsHookEx関数を呼ぶときに第二引数をフィールド変数に保存しています。これをしないとガベージコレクタによって回収されてしまい動かなくなってしまいます。同じような記述を他のサイトで見たときは信じられませんでした。
ガベージコレクタはそんな間抜けなことはしないはずだ。
ところが実験してみると(実際にSetWindowsHookEx関数を実行したあとでSystem.GC.Collect()を呼んでみるとよい)、見事に詰みます。
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 |
using System.Runtime.InteropServices; using System.Diagnostics; using System.Drawing; class MouseHook { protected const int WH_MOUSE_LL = 14; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, MouseHookProc 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); // これをしないとGCにより回収されてしまってCallbackOnCollectedDelegate例外で詰む private delegate IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam); MouseHookProc mouseHookProc = null; private IntPtr hookId = IntPtr.Zero; [StructLayout(LayoutKind.Sequential)] private struct POINT { public int x; public int y; } [StructLayout(LayoutKind.Sequential)] private struct MSLLHOOKSTRUCT { public POINT pt; public uint mouseData; public uint flags; public uint time; public System.IntPtr dwExtraInfo; } public void Hook() { if(hookId == IntPtr.Zero) { using(var curProcess = Process.GetCurrentProcess()) { using(ProcessModule curModule = curProcess.MainModule) { // フィールド変数にHookProcedureを保存する // そうしないとGCにより回収されてしまってCallbackOnCollectedDelegate例外で詰む mouseHookProc = HookProcedure; hookId = SetWindowsHookEx(WH_MOUSE_LL, mouseHookProc, GetModuleHandle(curModule.ModuleName), 0); } } } } public void UnHook() { UnhookWindowsHookEx(hookId); hookId = IntPtr.Zero; } public IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam) { int WM_MOUSEMOVE = 0x0200; int WM_LBUTTONDOWN = 0x0201; int WM_LBUTTONUP = 0x0202; int WM_RBUTTONDOWN = 0x0204; int WM_RBUTTONUP = 0x0205; if(nCode >= 0 && wParam == (IntPtr)WM_MOUSEMOVE) { MSLLHOOKSTRUCT ms = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); MouseMoveEvent?.Invoke(this, new MouseEventArg(ms.pt.x, ms.pt.y)); } if(nCode >= 0 && wParam == (IntPtr)WM_LBUTTONDOWN) { MSLLHOOKSTRUCT ms = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); LButtonDownEvent?.Invoke(this, new MouseEventArg(ms.pt.x, ms.pt.y)); } if(nCode >= 0 && wParam == (IntPtr)WM_LBUTTONUP) { MSLLHOOKSTRUCT ms = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); LButtonUpEvent?.Invoke(this, new MouseEventArg(ms.pt.x, ms.pt.y)); } if(nCode >= 0 && wParam == (IntPtr)WM_RBUTTONDOWN) { MSLLHOOKSTRUCT ms = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); RButtonDownEvent?.Invoke(this, new MouseEventArg(ms.pt.x, ms.pt.y)); } if(nCode >= 0 && wParam == (IntPtr)WM_RBUTTONUP) { MSLLHOOKSTRUCT ms = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); RButtonUpEvent?.Invoke(this, new MouseEventArg(ms.pt.x, ms.pt.y)); } return CallNextHookEx(hookId, nCode, wParam, lParam); } public delegate void MouseEventHandler(object sender, MouseEventArg e); public event MouseEventHandler MouseMoveEvent; public event MouseEventHandler LButtonDownEvent; public event MouseEventHandler LButtonUpEvent; public event MouseEventHandler RButtonDownEvent; public event MouseEventHandler RButtonUpEvent; } public class MouseEventArg : EventArgs { public MouseEventArg(int x, int y) { Point = new Point(x, y); } public Point Point { get; private set; } } |
これを使えばマウスが移動するとタイトルバーにマウスの座標を表示させることができます。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); hook.MouseMoveEvent += Hook_MouseMoveEvent; } MouseHook hook = new MouseHook(); private void button1_Click(object sender, EventArgs e) { hook.Hook(); } private void button2_Click(object sender, EventArgs e) { hook.UnHook(); } private void Hook_MouseMoveEvent(object sender, MouseEventArg e) { Text = String.Format("X={0}, Y={1}", e.Point.X, e.Point.Y); } } |
また他のウィンドウのタイトルを書き換えるプログラムもつくれます(実用性ゼロだけど)。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); hook.LButtonDownEvent += Hook_LButtonDownEvent; } MouseHook hook = new MouseHook(); private void button1_Click(object sender, EventArgs e) { hook.Hook(); } private void button2_Click(object sender, EventArgs e) { hook.UnHook(); foreach(OldTitle oldTitle in OldTitles) { if(IsWindow(oldTitle.hWnd) != 0) { SetWindowText(oldTitle.hWnd, oldTitle.Title); } } OldTitles.Clear(); } // ウィンドウハンドルからタイトルを書き換える [DllImport("User32.Dll", EntryPoint = "SetWindowText")] public static extern void SetWindowText(IntPtr hwnd, String text); // ウィンドウハンドルからタイトルを取得する [DllImport("User32.Dll", EntryPoint = "GetWindowText")] public static extern int GetWindowText(IntPtr hwnd, StringBuilder buf, int nMaxCount); // 座標からウィンドウハンドルを求める [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr WindowFromPoint(POINT point); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags); const uint GA_PARENT = 1; const uint GA_ROOT = 2; const uint GA_ROOTOWNER = 3; // そのウィンドウハンドルをもつウィンドウは存在するか? 存在するなら0以外の値を返す [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern int IsWindow(IntPtr hWnd); [StructLayout(LayoutKind.Sequential)] public struct POINT { public int x; public int y; } // タイトルを書き換えたウィンドウハンドルともとのタイトルのリスト public class OldTitle { public OldTitle(IntPtr hwnd, string title) { hWnd = hwnd; Title = title; } public IntPtr hWnd { get; private set; } public string Title { get; private set; } } List<OldTitle> OldTitles = new List<OldTitle>(); // クリックしたらその座標からどのウィンドウがクリックされたかを調べて // そのウィンドウのタイトルを書き換える。 // また戻せるようにもとのタイトルを記憶する。 private void Hook_LButtonDownEvent(object sender, MouseEventArg e) { POINT pt = new POINT(); pt.x = e.Point.X; pt.y = e.Point.Y; IntPtr hWnd = WindowFromPoint(pt); IntPtr hRootWnd = GetAncestor(hWnd, GA_ROOT); if(hRootWnd != null && hRootWnd != this.Handle && IsWindow(hRootWnd) != 0) { StringBuilder buf = new StringBuilder(1024); GetWindowText(hRootWnd, buf, buf.Capacity); if(!OldTitles.Any(x => x.hWnd == hRootWnd)) { OldTitles.Add(new OldTitle(hRootWnd, buf.ToString())); SetWindowText(hRootWnd, "乗っ取り成功"); } } } // 終了するときに元に戻す protected override void OnClosing(CancelEventArgs e) { foreach(OldTitle oldTitle in OldTitles) { if(IsWindow(oldTitle.hWnd) != 0) { SetWindowText(oldTitle.hWnd, oldTitle.Title); } } hook.UnHook(); base.OnClosing(e); } } |
こんにちは。調べると毎回サイトが出てきて、
参考にさせて頂いております。とても助かります。
1つ質問なのですが、「乗っ取り成功」と出るのは
クリック前でしょうか?それともクリック後なのでしょうか?
アプリを起動した状態で別ウィンドウ等をクリックすると、
スクショした後にクリック動作をさせたいと思っています。
「乗っ取り成功」と出るのはマウスの左ボタンが押下されたときです。
ちょっと紛らわしい点として、「クリックイベントは左ボタンが押下されて離されないと発生しない」があります。
質問に対する答えは「マウスの左ボタンが押下後」です。
>スクショした後にクリック動作をさせたいと思っています。
ということなので
でよいのではないかと思います。