【问题标题】:How to use SendInput to simulate the UP arrow key press (or other extended keys)如何使用 SendInput 模拟向上箭头键按下(或其他扩展键)
【发布时间】:2022-11-10 13:06:43
【问题描述】:

我正在尝试以编程方式模拟按下键盘上的向上箭头键以进行端到端测试。使用下面的代码,GUI 控件不会收到任何消息(WM_KEYDOWN、WM_GETDLGCODE、WM_KEYUP)。我使用 Spy++ 来检测传入的消息。你知道为什么\你能建议另一种解决方案吗?

该控件继承自 UserControl 类 (WinForms)。也没有跨线程异常,因为我使用了Control.InvokeRequired() 和所有相关的东西。

我做了2次尝试。第一个是:

[System.Runtime.InteropServices.DllImport("User32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);


public static int WM_KEYDOWN = 0x100;
public static int WM_KEYUP = 0x101;
public static int VK_UP = 0x26;

public void SimulateKeystrokeEventMethod()
{
    Thread.Sleep(20000);

    Control control = Control.FromHandle(MyGUICOntrol.Handle);
    if (control.InvokeRequired)
    {
        System.Action safeStimulation = delegate
        {
            SetForegroundWindow(Handle);

            const uint MK_LBUTTON = 0x0001;
            Point ClickPos = new Point(330, 401); //The position on top of the objects
            IntPtr lParam = (IntPtr)((401 << 16) | 330);
            IntPtr result = IntPtr.Zero;

            SendMessage(Handle, WM_LBUTTONDOWN, new IntPtr(MK_LBUTTON), lParam);
            SendMessage(Handle, WM_LBUTTONUP, IntPtr.Zero, lParam);


            for (int i = 0; i < 100; i++)
            {
                lParam = (IntPtr)(0);
                PostMessage(Handle, WM_KEYDOWN, new IntPtr(VK_UP), lParam);
                SendMessage(Handle, WM_GETDLGCODE, 0x00, 0x00);
                PostMessage(Handle, WM_KEYUP, new IntPtr(VK_UP), lParam);
            }
        };
        control.Invoke(safeStimulation);
    }

第二次尝试:

public void SimulateKeystrokeEventMethod()
{
    Thread.Sleep(20000);

    Control control = Control.FromHandle(MyGUICOntrol.Handle);
    if (control.InvokeRequired)
    {
        System.Action safeStimulation = delegate
        {
            SetForegroundWindow(Handle);

            const uint MK_LBUTTON = 0x0001;
            Point ClickPos = new Point(330, 401); //The position on top of the objects
            IntPtr lParam = (IntPtr)((401 << 16) | 330);
            IntPtr result = IntPtr.Zero;

            SendMessage(Handle, WM_LBUTTONDOWN, new IntPtr(MK_LBUTTON), lParam);
            SendMessage(Handle, WM_LBUTTONUP, IntPtr.Zero, lParam);

            ////for (ushort tryScanCode = 0x79; tryScanCode < 0xFF; tryScanCode++)
            {
                ushort tryScanCode = 0x48;
                Input[] inputs1 = new Input[]
                {
                    new Input
                    {
                        type = (int) InputType.Keyboard,
                        u = new InputUnion
                        {
                            ki = new KeyboardInput
                            {
                                wVk = 0,
                                wScan = 0x48,
                                dwFlags = (uint) (KeyEventF.KeyDown | KeyEventF.Scancode),
                                dwExtraInfo = GetMessageExtraInfo()
                            }
                        }
                    }
                };

                Input[] inputs2 = new Input[]
                {
                    new Input
                    {
                        type = (int)InputType.Keyboard,
                        u = new InputUnion
                        {
                            ki = new KeyboardInput
                            {
                                wVk = 0,
                                wScan = 0x48,
                                dwFlags = (uint)(KeyEventF.KeyUp | KeyEventF.Scancode),
                                dwExtraInfo = GetMessageExtraInfo()
                            }
                        }
                    }
                };

                SetForegroundWindow(Handle);

                SendInput((uint)inputs1.Length, inputs1, Marshal.SizeOf(typeof(Input)));
                SendMessage(Handle, WM_GETDLGCODE, 0x00, 0x00);
                SendInput((uint)inputs2.Length, inputs2, Marshal.SizeOf(typeof(Input)));
            }

        };
        control.Invoke(safeStimulation);
    }
}

这是来自的后续问题 Simulate the pressing of the UP arrow key on a keyboard

非常感谢您的帮助!

【问题讨论】:

  • 如前所述,如果您需要SendInput() 的帮助,没关系,我可以帮助您。但是,正如@IInspectable 建议的那样,你确定你不能执行这些测试使用 UI 自动化?你在测试什么,你正在执行什么样的测试?使用托管 UIAutomation 方法无法实现的功能? -- 顺便说一句,不要像这样混合SendMessage()SendInput()
  • @jimi 是的,欢迎您的帮助!
  • 您可能会在我之前的评论中注意到一些问题:您需要回答这些问题。
  • UIAutomation:我不关注。您是否建议使用任何工具?

标签: c# winforms winapi win32gui sendinput


【解决方案1】:

关于SendInput() 的一些说明,与您尝试做的事情有关。

  • 此函数接受INPUT 结构的数组。您需要在一次调用中将所有按键传递给此函数。这包括 Key 和 Key 修饰符(Control、Shift、ALT、Menu 等)
  • 输入被发送到当前输入接收者,因此您需要确保用于接收按键的窗口是聚焦的(或其父窗口或主窗口,即实际处理按键的窗口压力机)
  • 如果目标窗口属于不同的线程,对SetFocus()的调用可能会失败,所以我们预先附加到那个线程。
  • 如果父主窗口被最小化,您需要事先将其打开,以确保它会接收焦点,然后是我们的输入。

这里我用GetCurrentThreadId()GetWindowThreadProcessId()比较,来验证调用者Thread和目标Thread是否不同。
如果它们不相同,则调用AttachThreadInput()

如果目标主窗口被最小化(参见对IsIconic() 的调用),它会被恢复,调用SetWindowPlacement() 并使用BringWindowToTop() 被带到前台。

然后对SetFocus() 的调用将焦点移动到目标句柄(假设它可以接收焦点,即 - 该函数无论如何都应该返回成功)。

最后,您收集所有需要发送到不同INPUT 结构(每个键修饰符在其自己的结构中)的所有密钥,并对SendInput() 进行一次调用。


例如:

发送向上目标句柄的关键:

IntPtr handle = [The selected handle];
bool result = NativeMethods.SendKeyboardInput(handle, Keys.Up, null);

发送控制+转移+

var modifiers = new[] { Keys.ShiftKey, Keys.ControlKey};
bool result = NativeMethods.SendKeyboardInput(handle, Keys.Home, modifiers);

这是它的工作原理:

标准 PictureBox 无法聚焦,因此在调用 SetFocus() 时会看到 Button 闪烁

Sample Project for testing (Google Drive)

本机方法

internal class NativeMethods {

    public static bool SendKeyboardInput(IntPtr hWnd, Keys key, Keys[] modifiers = null, int delay = 0)
    {
        if (hWnd != IntPtr.Zero) {
            uint targetThreadID = GetWindowThreadProcessId(hWnd, IntPtr.Zero);
            uint currentThreadID = GetCurrentThreadId();

            if (targetThreadID != currentThreadID) {
                try {
                    if (!AttachThreadInput(currentThreadID, targetThreadID, true)) return false;
                    var parentWindow = GetAncestor(hWnd, GetAncestorFlags.GA_ROOT);
                    if (IsIconic(parentWindow)) {
                        if (!RestoreWindow(parentWindow)) return false;
                    }

                    if (!BringWindowToTop(parentWindow)) return false;
                    if (SetFocus(hWnd) == IntPtr.Zero) return false;
                }
                finally {
                    AttachThreadInput(currentThreadID, targetThreadID, false);
                }
            }
            else {
                SetFocus(hWnd);
            }
        }

        var flagsKeyDw = IsExtendedKey(key) ? KeyboardInputFlags.ExtendedKey : KeyboardInputFlags.KeyDown;
        var flagsKeyUp = KeyboardInputFlags.KeyUp | (IsExtendedKey(key) ? KeyboardInputFlags.ExtendedKey : 0);

        var inputs = new List<INPUT>();
        var input = new INPUT(SendInputType.InputKeyboard);

        // Key Modifiers Down
        if (!(modifiers is null)) {
            foreach (var modifier in modifiers) {
                input.Union.Keyboard.Flags = KeyboardInputFlags.KeyDown;
                input.Union.Keyboard.VirtKeys = (ushort)modifier;
                inputs.Add(input);
            }
        }

        // Key Down
        input.Union.Keyboard.Flags = flagsKeyDw | KeyboardInputFlags.Unicode;
        input.Union.Keyboard.VirtKeys = (ushort)key;
        inputs.Add(input);

        // Key Up
        input.Union.Keyboard.Flags = flagsKeyUp | KeyboardInputFlags.Unicode;
        input.Union.Keyboard.VirtKeys = (ushort)key;
        inputs.Add(input);

        // Key Modifiers Up
        if (!(modifiers is null)) {
            foreach (var modifier in modifiers) {
                input.Union.Keyboard.Flags = KeyboardInputFlags.KeyUp;
                input.Union.Keyboard.VirtKeys = (ushort)modifier;
                inputs.Add(input);
            }
        }

        uint sent = SendInput((uint)inputs.Count(), inputs.ToArray(), Marshal.SizeOf<INPUT>());
        return sent > 0;
    }

    private static Keys[] extendedKeys = { Keys.Up, Keys.Down, Keys.Left, Keys.Right, Keys.Home, Keys.End, Keys.Prior, Keys.Next, Keys.Insert, Keys.Delete };
    private static bool IsExtendedKey(Keys key) => extendedKeys.Contains(key);

    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-input
    [StructLayout(LayoutKind.Sequential)]
    public struct INPUT {
        public SendInputType InputType;
        public InputUnion Union;

        public INPUT(SendInputType type) {
            InputType = type;
            Union = new InputUnion();
        }
    }

    public enum SendInputType : uint {
        InputMouse = 0,
        InputKeyboard = 1,
        InputHardware = 2
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InputUnion {
        [FieldOffset(0)]
        public MOUSEINPUT Mouse;

        [FieldOffset(0)]
        public KEYBDINPUT Keyboard;

        [FieldOffset(0)]
        public HARDWAREINPUT Hardware;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MOUSEINPUT {
        public int dx;
        public int dy;
        public uint mouseData;
        public MouseEventdwFlags dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput
    [StructLayout(LayoutKind.Sequential)]
    public struct KEYBDINPUT {
        public ushort VirtKeys;
        public ushort wScan;
        public KeyboardInputFlags Flags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

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

    [Flags]
    public enum MouseEventdwFlags : uint {
        MOUSEEVENTF_MOVE = 0x0001,
        MOUSEEVENTF_LEFTDOWN = 0x0002,
        MOUSEEVENTF_LEFTUP = 0x0004,
        MOUSEEVENTF_RIGHTDOWN = 0x0008,
        MOUSEEVENTF_RIGHTUP = 0x0010,
        MOUSEEVENTF_MIDDLEDOWN = 0x0020,
        MOUSEEVENTF_MIDDLEUP = 0x0040,
        MOUSEEVENTF_XDOWN = 0x0080,
        MOUSEEVENTF_XUP = 0x0100,
        MOUSEEVENTF_WHEEL = 0x0800,
        MOUSEEVENTF_VIRTUALDESK = 0x4000,
        MOUSEEVENTF_ABSOLUTE = 0x8000
    }

    [Flags]
    public enum KeyboardInputFlags : uint {
        KeyDown = 0x0,
        ExtendedKey = 0x0001,
        KeyUp = 0x0002,
        Scancode = 0x0008,
        Unicode = 0x0004
    }

    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-windowplacement
    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWPLACEMENT {
        public int length;
        public WplFlags flags;
        public SW_Flags showCmd;
        public POINT ptMinPosition;
        public POINT ptMaxPosition;
        public RECT rcNormalPosition;
    }

    public enum WplFlags : uint {
        WPF_ASYNCWINDOWPLACEMENT = 0x0004,   // If the calling thread and the thread that owns the window are attached to different input queues, the system posts the request to the thread that owns the window. This prevents the calling thread from blocking its execution while other threads process the request.
        WPF_RESTORETOMAXIMIZED = 0x0002,     // The restored window will be maximized, regardless of whether it was maximized before it was minimized. This setting is only valid the next time the window is restored. It does not change the default restoration behavior.
                                             // This flag is only valid when the SW_SHOWMINIMIZED value is specified for the showCmd member.
        WPF_SETMINPOSITION = 0x0001          // The coordinates of the minimized window may be specified. This flag must be specified if the coordinates are set in the ptMinPosition member.
    }

    [Flags]
    public enum SW_Flags : uint {
        SW_HIDE = 0X00,
        SW_SHOWNORMAL = 0x01,
        SW_MAXIMIZE = 0x03,
        SW_SHOWNOACTIVATE = 0x04,
        SW_SHOW = 0x05,
        SW_MINIMIZE = 0x06,
        SW_RESTORE = 0x09,
        SW_SHOWDEFAULT = 0x0A,
        SW_FORCEMINIMIZE = 0x0B
    }

    public enum GetAncestorFlags : uint {
        GA_PARENT = 1,     // Retrieves the parent window.This does not include the owner, as it does with the GetParent function.
        GA_ROOT = 2,       // Retrieves the root window by walking the chain of parent windows.
        GA_ROOTOWNER = 3   // Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
    }

    [StructLayout(LayoutKind.Sequential)]
    public class POINT {
        public int x;
        public int y;

        public POINT(int x, int y) {
            this.x = x;
            this.y = y;
        }
        public Point ToPoint() => new Point(this.x, this.y);
        public PointF ToPointF() => new PointF((float)this.x, (float)this.y);
        public POINT FromPoint(Point p) => new POINT(p.X, p.Y);
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;

        public RECT(int left, int top, int right, int bottom) {
            Left = left; Top = top; Right = right; Bottom = bottom;
        }

        public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
        public Rectangle ToRectangleOffset(POINT p) => Rectangle.FromLTRB(p.x, p.y, Right + p.x, Bottom + p.y);

        public RECT FromRectangle(RectangleF rectangle) => FromRectangle(Rectangle.Round(rectangle));
        public RECT FromRectangle(Rectangle rectangle) => new RECT() {
            Left = rectangle.Left, 
            Top = rectangle.Top,
            Bottom = rectangle.Bottom,
            Right = rectangle.Right
        };
        public RECT FromXYWH(int x, int y, int width, int height) => new RECT(x, y, x + width, y + height);
    }


    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowplacement
    [DllImport("user32.dll", SetLastError = true)]
    internal static extern bool GetWindowPlacement(IntPtr hWnd, [In, Out] ref WINDOWPLACEMENT lpwndpl);

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr voidProcessId);

    // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadid
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern uint GetCurrentThreadId();

    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-attachthreadinput
    [DllImport("user32.dll", SetLastError = true)]
    internal static extern bool AttachThreadInput([In] uint idAttach, [In] uint idAttachTo, [In, MarshalAs(UnmanagedType.Bool)] bool fAttach);

    [ResourceExposure(ResourceScope.None)]
    [DllImport("User32", ExactSpelling = true, CharSet = CharSet.Auto)]
    internal static extern IntPtr GetAncestor(IntPtr hWnd, GetAncestorFlags flags);

    [DllImport("user32.dll")]
    internal static extern bool IsIconic(IntPtr hWnd);

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern bool BringWindowToTop(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    internal static extern IntPtr SetFocus(IntPtr hWnd);

    //https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendinput
    [DllImport("user32.dll", SetLastError = true)]
    internal static extern uint SendInput(uint nInputs, [In, MarshalAs(UnmanagedType.LPArray)] INPUT[] pInputs, int cbSize);

    public static bool RestoreWindow(IntPtr hWnd)
    {
        var wpl = new WINDOWPLACEMENT() {
            length = Marshal.SizeOf<WINDOWPLACEMENT>()
        };
        if (!GetWindowPlacement(hWnd, ref wpl)) return false;

        wpl.flags = WplFlags.WPF_ASYNCWINDOWPLACEMENT;
        wpl.showCmd = SW_Flags.SW_RESTORE;
        return SetWindowPlacement(hWnd, ref wpl);
    }
}

【讨论】:

  • 我又问了一个问题:stackoverflow.com/questions/71606962/…
  • "如果目标窗口属于不同的线程,它可能不会收到您的输入,因此您需要附加到该线程以发送您的密钥。" - 我以前从未听说过这个要求。考虑到SendInput() injects into the same hardware input queue that the physical keyboard does(和also this),它肯定没有记录,也没有任何意义,所以线程输入状态不应该是这里的一个因素。 .
  • ...而且我从未见过(或写过)使用AttachThreadInput()SendInput() 的例子,没有它它们也能正常工作。
  • @RemyLebeau 如果您查看代码,AttachThreadInput() 不用于SendInput()(这是最后一次调用,因此在AttachThreadInput(currentThreadID, targetThreadID, false); 之后。那段文字是错误的,我指的是焦点而不是输入。这可能或者可能没有必要,但可能不太容易像这样失败。我会编辑它。-如果您有不同/更精确的信息来将子窗口聚焦在不同的线程中,我很高兴听到.
  • “扩展键”是我所缺少的,有些键没有达到预期的效果。仅此一项就值得+1!
【解决方案2】:

吉米,你很有帮助!根据您的回答,我修复了我的代码。我对我的问题提交了另一个答案。

    public void SimulateKeystrokeEventMethod()
    {
        Thread.Sleep(10000);
        IntPtr handle = DesignPlanHelper.Handle;
        ushort key = (ushort)KB_UP;
        int repetitions = 30;

        Control control = Control.FromHandle(handle);

        if (control.InvokeRequired)
        {
            System.Action safeStimulation = delegate
            {
                SetFocus(handle);

                // Select an image lying in the control (a descendant of UserControl in WinForms)
                Clicks(handle, 330, 401);

                Input keyDown = new Input
                {
                        type = (int) InputType.Keyboard,
                        u = new InputUnion
                        {
                            ki = new KeyboardInput
                            {
                                wVk = 0, wScan = key, dwFlags = (uint) (KeyEventF.KeyDown | KeyEventF.Scancode), dwExtraInfo = GetMessageExtraInfo()
                            }
                        }
                };

                Input keyUp = new Input
                {
                    type = (int) InputType.Keyboard,
                    u = new InputUnion
                    {
                        ki = new KeyboardInput
                        {
                            wVk = 0, wScan = key, dwFlags = (uint) (KeyEventF.KeyUp | KeyEventF.Scancode), dwExtraInfo = GetMessageExtraInfo()
                        }
                    }
                };

                Input[] inputs = new Input[repetitions * 2];
                for (int input = 0; input < repetitions*2;)
                {
                    inputs[input] = keyDown;
                    input++;
                    inputs[input] = keyUp;
                    input++;
                }

                // You must pass all Key presses in a single call to this function.
                SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(Input)));
            };
            control.Invoke(safeStimulation);
        }
        else
        {
            //copy some code from above.
        }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-07
    • 1970-01-01
    • 2014-07-12
    • 1970-01-01
    • 2013-03-07
    • 1970-01-01
    • 2018-09-16
    • 1970-01-01
    相关资源
    最近更新 更多