0

I am trying to programmatically simulate the pressing of the UP arrow key on the keyboard for an end-to-end test. With the below code, the GUI control receives no message (WM_KEYDOWN, WM_GETDLGCODE, WM_KEYUP). I used Spy++ to detect the incoming messages. Do you know why \ can you suggest another solution?

The control inherits from the UserControl class (WinForms). Also there is no cross-thread exception because I used Control.InvokeRequired() and all the relevant stuff.

I did 2 tries. The first one is:

[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);
    }

The second try:

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);
    }
}

This is a follow up question from Simulate the pressing of the UP arrow key on a keyboard

Your help is much appreciated!

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • 2
    [You can't simulate keyboard input with PostMessage](https://devblogs.microsoft.com/oldnewthing/20050530-11/?p=35513). Use [UI Automation](https://learn.microsoft.com/en-us/dotnet/framework/ui-automation/ui-automation-overview), that's what it's for. – IInspectable Mar 23 '22 at 12:51
  • 1
    As mentioned before, if you need help with `SendInput()`, that's ok, I can help you out. But, as @IInspectable suggested, are you sure you cannot perform these *tests* using UIAutomation? What are you testing and what kind of tests are you performing? Something that you cannot achieve with the managed UIAutomation methods? -- BTW, don't mix `SendMessage()` and `SendInput()` like that. – Jimi Mar 23 '22 at 13:33
  • @jimi Yes your help would be welcomed! – Bob the builder Mar 23 '22 at 13:43
  • You may notice a few of questions in my previous comment: you need to answer those questions. – Jimi Mar 23 '22 at 13:45
  • UIAutomation: I don't follow. Are you suggesting any tool? – Bob the builder Mar 23 '22 at 13:45
  • `UIAutomation` is part of the .Net framework. You can find this information in the first comment. You can make an application perform actions using different supported Patterns, or get/set values (when supported by the object you're targeting). You can use it to simulate User interactions with an UI. – Jimi Mar 23 '22 at 13:47
  • @jimi Lets put aside the automation tool for a moment and focus on the SendInput try (the second try in my question). I want my control (call it ControlA) to receive the 3 messages above. ControlA inherits from UserControl class (WinForms). The end goal is to move an object (an image of a bike by instance) in ControlA. On my side, I am replacing the usage of SendMessage by SendInput and give a feedback later. Your help is appreciated! – Bob the builder Mar 23 '22 at 13:50
  • Do you mean *move a PictureBox*? Does it handle `PreviewKeyDown`? Or is the UserControl that handles key presses? Anyway, it doesn't matter much, you just need to select the correct Handle (the Handle of the Control that will receive the Input, to make it the Focused Control - note that a PictureBox cannot be focused). -- You don't need to send `WM_GETDLGCODE`. – Jimi Mar 23 '22 at 13:58
  • With *note that a PictureBox cannot be focused* I man that you need to focus a Control to send input to it. Or, the Control (or one of it Parents in the hierarchy), must handle KeyDown preview event / override the related methods. So, the recipient of the Input you send must be chosen with care, since you may have unwanted side-effects when setting the Focus on a Container that is not selectable (the Focus is then sent to the first focusable child Control - usually the one with the lower TabIndex property value). – Jimi Mar 23 '22 at 14:05
  • Anyway, `SendInput()` should work, as long as you can press a Key and the key is processed by the recipient (either the UserControl or the PictureBox, if it handles `PreviewKeyDown`) -- I'll test it and let you know. -- As a note, it's going to be quite a bunch of code, if I decide to post it. – Jimi Mar 23 '22 at 14:20
  • You should also clarify why you're checking `InvokeRequired` here. – Jimi Mar 23 '22 at 14:37
  • About UIAutomation: Does it work with WinForms? And with DevExpress? – Bob the builder Mar 23 '22 at 15:15
  • UI Automation can be used with any GUI framework that supports automation. This includes Win32, WinForms, WPF, UWP (and others). – Jimi Mar 23 '22 at 15:48
  • *"Does it work with WinForms?"* - WinForms is just a .NET wrapper around the native Windows controls. It's not a technology in itself. The native Windows controls support UI Automation. Whether DevExpress implements the required interfaces is something you'll have to find out. You can use the [Inspect.exe](https://learn.microsoft.com/en-us/windows/win32/winauto/inspect-objects) to to quickly verify this. – IInspectable Mar 23 '22 at 21:06
  • Someone told me DevExpress does not support well UI Automation but I will verify by myself with inspect.exe. Thank you IInspectable. – Bob the builder Mar 25 '22 at 19:49

2 Answers2

2

A few notes about SendInput(), in relation to what you tried to do.

  • This function accepts and array of INPUT structures. You need to pass all Key presses in a single call to this function. This include a Key and Key modifiers (Control, Shift, ALT, Menu et.)
  • The input is sent to the current Input recipient, so you need to make sure the Window that is meant to receive the key presses is Focused (or its Parent Window or the Main Window, i.e., the window that actually processes the key presses)
  • If the target Window belongs to a different Thread, the call to SetFocus() may fail, so we attach to that Thread beforehand.
  • If the parent Main Window is minimized, you need to bring it up beforehand, to be sure it will receive focus and then our input.

Here, I'm using GetCurrentThreadId() and compare with GetWindowThreadProcessId(), to verify whether the caller Thread and the target Thread are different.
If they're not the same, then AttachThreadInput() is called.

If the target Main Window is minimized (see the call to IsIconic()), it's restored, calling SetWindowPlacement() and brought to the foreground using BringWindowToTop().

Then a call to SetFocus() moves the focus to the target handle (assuming it can receive focus, that is - the function should return success anyway).

In the end, you collect all the Keys you need to send in different INPUT structs (each Key Modifiers in its own struct) and make a single call to SendInput().


For example:

Send the Up key to the target handle:

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

Send Control + Shift + Home:

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

This is how it works:

SendInput test

A standard PictureBox cannot be focused, so you'll see a Button flicker when SetFocus() is called

Sample Project for testing (Google Drive)

NativeMethods:

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);
    }
}
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • I asked another question: https://stackoverflow.com/questions/71606962/simulate-the-up-arrow-key-press-on-a-keyboard-for-a-test-executed-by-testhost – Bob the builder Mar 24 '22 at 17:31
  • "*If the target Window belongs to a different Thread, it won't probably receive your Input, so you need to attach to that Thread to send your Keys.*" - I have never heard of that requirement before. It certainly isn't documented, and doesn't make sense anyway, given that [`SendInput()` injects into the same hardware input queue that the physical keyboard does](https://devblogs.microsoft.com/oldnewthing/20140213-00/?p=1773) (and [also this](https://devblogs.microsoft.com/oldnewthing/20101221-00/?p=11953)), so thread input states should not be a factor here... – Remy Lebeau Mar 24 '22 at 18:47
  • ... and no examples of `SendInput()` I have ever seen (or written) ever use `AttachThreadInput()`, and they work fine without it. – Remy Lebeau Mar 24 '22 at 18:50
  • @RemyLebeau If you look at the code, `AttachThreadInput()` is not used for `SendInput()` (which is the last call, hence after `AttachThreadInput(currentThreadID, targetThreadID, false);`. That piece of text is wrong, I was referring to the Focus not Input. Which may or may not be necessary, but probably less prone to fail like this. I'll edit it. -- If you have different / more precise information in relation to focusing a child window in a different Thread, I'm glad to hear it. – Jimi Mar 24 '22 at 19:01
  • The "Extended Keys" is what I was missing, and some keys were not doing what was expected. This alone deserves +1! – Nathan Evans Jun 19 '22 at 10:05
  • @Bobthebuilder is there anyway to make this work with .net4? The generic version of Marshal.SizeOf was not introduced until .NET 4.5.1 – gclark18 Sep 01 '22 at 13:05
  • @gclark18 You can use `Marshal.SizeOf(typeof(WINDOWPLACEMENT))` etc. – Jimi Sep 01 '22 at 13:26
0

Jimi, you were very helpful! From you answer, I fixed my code. I submit another answer to my problem.

    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.
        }
    }