2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

文字列、または、仮想キーコードを送信して、キー操作をシミュレート(擬似的に操作)する

Posted at

はじめに

(マルチバイト)文字列、または、仮想キーコードを送信して、キー操作をシミュレート(擬似的に操作)するサンプルコード。

任意の他のウィンドウにフォーカスを移せば、アプリケーションを越えて(別プロセスの)ウィンドウへキー入力が可能。自・他(任意)のアプリケーションに対してキーボード入力を送信できます。これにより、自・他(任意)のアプリケーションの自動化やリモート操作等が可能です。

キーワード: キー操作シミュレート, 仮想キーコード送信, Win32 API(user32.dll内) SendInput

処理方法

主要部分は、

  • Win32 API(user32.dll内)のSendInput関数を呼び出して、任意のキー入力を送信
  • Input構造体の中のScanCode、または、VirtualKeyで送信するキー内容を指定:
    • 文字列を送信する場合は、ScanCodeに(マルチバイト)文字コードを代入し、
    • 仮想キーコードを送信する場合は、VirtualKeyに仮想キーコードを代入 (仮想キーコードは特殊キーも含めて細かく指定可能)
  • 主要機能は自作関数InputSender.KeyPressとして実装

実装+テストコード

テストコードのGUIは:
image.png

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        // sec: test code

        // ボタンをクリックしてテスト開始
        private void button1_Click(object sender, EventArgs e) {
            Test_KeyPress_Basic(textBox1);
            Test_KeyPress_ToOtherApp();
        }

        // キー入力の送信テスト
        internal static void Test_KeyPress_Basic(TextBox textBox) {
            // 結果: TextBoxへの入力OK: abcあいう醤油★☺<改行>ちzつ
            // ・ScanCodeの指定時は、全角モード時でも"abc"と入力が入る。
            // ・仮想キー指定時は、全角モード時で、Aと送ると「ち」と入力が入る。Enterを送ると、変換が確定となる。
            // ・全角/半角キーを送ると、全角/半角が切り替わる。

            Console.WriteLine("▼全角/半角モードでコードからキー入力を送る");
            textBox.Focus();
            textBox.ImeMode = ImeMode.Hiragana; // 全角モードに強制

            // ScanCodeの指定時
            InputSender.KeyPress("abc"); // ScanCodeの指定時は、全角モード時でも"abc"と入力が入る。
            InputSender.KeyPress("あいう醤油★☺\n"); // 全角文字を直接送信可

            // 仮想キー指定時
            InputSender.KeyPress(new[] { InputSender.VK_A, InputSender.VK_RETURN }); // 仮想キー指定時は、全角モード時で、Aと送ると「ち」と入力が入る。Enterを送ると、変換が確定となる。
            InputSender.KeyPress(new[] { InputSender.VK_KANA, InputSender.VK_Z, InputSender.VK_RETURN }); // 全角/半角キーを送ると、全角/半角が切り替わる。
            InputSender.KeyPress(new[] { InputSender.VK_KANA, InputSender.VK_Z, InputSender.VK_RETURN });
        }

        // 他アプリへのキー入力の送信テスト
        internal static async void Test_KeyPress_ToOtherApp() {
            // 結果: 他アプリへの入力OK: abc0ちわち
            // ・他のアプリにフォーカスを移せば、アプリを越えて(別プロセスの)ウィンドウへキー入力が可能。

            Console.WriteLine("▼他のアプリ(別プロセス)にキー入力を送る");
            Console.WriteLine("他のアプリにフォーカスを移して、全角モードで待機して下さい。");
            await System.Threading.Tasks.Task.Delay(3000);

            InputSender.KeyPress("abc"); // 他のアプリにフォーカスを移せば、アプリを越えて(別プロセスの)ウィンドウへキー入力が可能。
            InputSender.KeyPress(new[] { InputSender.VK_NUMPAD0, InputSender.VK_A });
            InputSender.KeyPress(new[] { InputSender.VK_0, InputSender.VK_A });
        }
    }

    // sec: キー入力を送信

    // (マルチバイト)文字列、または、仮想キーコードを送信して、キー操作をシミュレート(擬似的に操作)
    public static class InputSender {
        // 他のアプリにフォーカスを移せば、アプリを越えて(別プロセスの)ウィンドウへキー入力可能

        // (マルチバイト)文字列のキー入力を送信 (ScanCodeの指定となる)
        public static void KeyPress(string text) {

            Input[] inputs = new Input[2 * text.Length];
            for (var i_char = 0; i_char < text.Length; i_char++) {

                var val_sc = (short)text[i_char];
                var i_in = 2 * i_char;
                inputs[i_in] = new Input();
                inputs[i_in].Type = INPUT_KEYBOARD;
                inputs[i_in].ui.Keyboard.ScanCode = val_sc;
                inputs[i_in].ui.Keyboard.Flags = KEYEVENTF_UNICODE | KEYEVENTF_KEYDOWN;

                i_in = 2 * i_char + 1;
                inputs[i_in] = new Input();
                inputs[i_in].Type = INPUT_KEYBOARD;
                inputs[i_in].ui.Keyboard.ScanCode = val_sc;
                inputs[i_in].ui.Keyboard.Flags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
            }
            InputSender.SendInput(inputs.Length, inputs, Marshal.SizeOf<Input>());
        }
        public static void KeyPress(char text) => InputSender.KeyPress(text.ToString());

        // 仮想キーコードを送信 (VirtualKeyの指定となる)
        public static void KeyPress(short[] vkeys) {

            var inputs = new Input[2 * vkeys.Length];
            for (var i_vkey = 0; i_vkey < vkeys.Length; i_vkey++) {

                var val_vk = vkeys[i_vkey];
                var i_in = 2 * i_vkey;
                inputs[i_in] = new Input();
                inputs[i_in].Type = INPUT_KEYBOARD;
                inputs[i_in].ui.Keyboard.VirtualKey = val_vk;
                inputs[i_in].ui.Keyboard.Flags = KEYEVENTF_KEYDOWN;

                i_in = 2 * i_vkey + 1;
                inputs[i_in] = new Input();
                inputs[i_in].Type = INPUT_KEYBOARD;
                inputs[i_in].ui.Keyboard.VirtualKey = val_vk;
                inputs[i_in].ui.Keyboard.Flags = KEYEVENTF_KEYUP;
            }
            InputSender.SendInput(inputs.Length, inputs, Marshal.SizeOf<Input>());
        }
        public static void KeyPress(short vkey) => InputSender.KeyPress(new[] { vkey });

        // sec: BeginInvoke

        // [不要?] このWinアプリのスレッド上で呼出しを実施
        //public static void BeginInvoke(this Control control, Action func) => control.BeginInvoke(func);
        // 使用方法: this.BeginInvoke(() => { SendInput.KeyPress("abc"); }); 等

        // sec: 仮想キー値

        // ref: 仮想キーの状態
        // http://wisdom.sakura.ne.jp/system/winapi/win32/win32.html

        // 必要な分を下記に適宜もっと追加
        public const short VK_A = 0x41;
        public const short VK_Z = 0x5A;
        public const short VK_0 = 0x30;
        public const short VK_9 = 0x39;
        public const short VK_RETURN = 0x0D; // Enter
        public const short VK_SHIFT = 0x10; // SHIFTキー
        public const short VK_KANA = 0x15;
        public const short VK_ESCAPE = 0x1B;
        public const short VK_SPACE = 0x20;

        public const short VK_NUMPAD0 = 0x60; // テンキー
        public const short VK_NUMPAD9 = 0x69;
        public const short VK_MULTIPLY = 0x6A; // テンキー *
        public const short VK_ADD = 0x6B; // テンキー +
        public const short VK_SEPARATOR = 0x6C;
        public const short VK_SUBTRACT = 0x6D; // テンキー -
        public const short VK_DECIMAL = 0x6E; // テンキー .
        public const short VK_DIVIDE = 0x6F; // テンキー / 


        // sec: Win API

        // ref: C# - SendInputでマルチバイト文字列を送信する
        // https://qiita.com/kob58im/items/60abf22e6ce9a3a0ed26

        // キー操作、マウス操作をシミュレート(擬似的に操作する)
        [DllImport("user32.dll", SetLastError = true)]
        public extern static void SendInput(int nInputs, Input[] pInputs, int cbsize);

        //[DllImport("user32.dll", SetLastError = true)]
        //public extern static IntPtr GetMessageExtraInfo();

        public const int INPUT_MOUSE = 0;    // マウスイベント
        public const int INPUT_KEYBOARD = 1; // キーボードイベント
        public const int INPUT_HARDWARE = 2; // ハードウェアイベント

        public const int MOUSEEVENTF_MOVE = 0x1;        // マウスを移動する
        public const int MOUSEEVENTF_ABSOLUTE = 0x8000; // 絶対座標指定
        public const int MOUSEEVENTF_LEFTDOWN = 0x2;    // 左 ボタンを押す
        public const int MOUSEEVENTF_LEFTUP = 0x4;      // 左 ボタンを離す
        public const int MOUSEEVENTF_RIGHTDOWN = 0x8;   // 右 ボタンを押す
        public const int MOUSEEVENTF_RIGHTUP = 0x10;    // 右 ボタンを離す
        public const int MOUSEEVENTF_MIDDLEDOWN = 0x20; // 中央ボタンを押す
        public const int MOUSEEVENTF_MIDDLEUP = 0x40;   // 中央ボタンを離す
        public const int MOUSEEVENTF_WHEEL = 0x800;     // ホイールを回転する
        public const int WHEEL_DELTA = 120;             // ホイール回転値

        public const int KEYEVENTF_EXTENDEDKEY = 0x0001; // 拡張コード
        public const int KEYEVENTF_KEYDOWN = 0x0000; // キーを押す
        public const int KEYEVENTF_KEYUP = 0x0002; // キーを離す
        public const int KEYEVENTF_SCANCODE = 0x0008;
        public const int KEYEVENTF_UNICODE = 0x0004;

        // ▼case: 変換する場合
        // 仮想キーコードをスキャンコードに変換
        //[DllImport("user32.dll", EntryPoint = "MapVirtualKeyA")]
        //private extern static int MapVirtualKey(
        //    int wCode, int wMapType);

        // public const int MAPVK_VK_TO_VSC = 0;
        // public const int MAPVK_VSC_TO_VK = 1;
        // ▲case: end

        [StructLayout(LayoutKind.Sequential)]
        public struct MouseInput {
            public int X;
            public int Y;
            public int Data;
            public int Flags;
            public int Time;
            public IntPtr ExtraInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct KeyboardInput {
            public short VirtualKey;
            public short ScanCode;
            public int Flags;
            public int Time;
            public IntPtr ExtraInfo;
        }
        // VirtualKey・ScanCodeは、winuser.hのKEYBDINPUT構造体では、WORD(16ビット符号なし整数、unsigned short型/2バイト)の定義=ushort
        // https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/ns-winuser-keybdinput
        // https://learn.microsoft.com/ja-jp/windows/win32/winprog/windows-data-types#word

        [StructLayout(LayoutKind.Sequential)]
        public struct HardwareInput {
            public int uMsg;
            public short wParamL;
            public short wParamH;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct Input {
            public int Type;
            public InputUnion ui;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct InputUnion {
            [FieldOffset(0)]
            public MouseInput Mouse;
            [FieldOffset(0)]
            public KeyboardInput Keyboard;
            [FieldOffset(0)]
            public HardwareInput Hardware;
        }
    }
}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?