Reputation: 35194
Here is my code that I use to simulate a tab-keypress in a certain process:
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
public Form1()
{
PostMessage(MemoryHandler.GetMainWindowHandle(),
(int)KeyCodes.WMessages.WM_KEYDOWN,
(int)KeyCodes.VKeys.VK_TAB, 0);
InitializeComponent();
}
Is there any way to extend it so that it presses the key for (example) 1 second, instead of just tapping it?
Please note that I'm not interested in a Thread.Sleep()
solution that blocks the UI thread.
Upvotes: 6
Views: 7220
Reputation: 35881
If you want to simulate what Windows does with messages, you likely want to find out how fast the key repeat rate is. That can be found at HKEY_CURRENT_USER\Control Panel\Keyboard\KeyboardSpeed
. there's also the KeyboardDelay
value.
What Windows does is send a WM_KEYDOWN
and a WM_CHAR
when a key is initially pressed. Then, if the key is still pressed after KeyboardDelay
time span, the WM_KEYDOWN
and WM_CHAR
pair are repeated every KeyboardSpeed
until the key is depressed--at which point WM_KEYUP
is sent.
I would suggest using a Timer
to send the messages at a specific frequencies.
for example:
int keyboardDelay, keyboardSpeed;
using (var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Keyboard"))
{
Debug.Assert(key != null);
keyboardDelay = 1;
int.TryParse((String)key.GetValue("KeyboardDelay", "1"), out keyboardDelay);
keyboardSpeed = 31;
int.TryParse((String)key.GetValue("KeyboardSpeed", "31"), out keyboardSpeed);
}
maxRepeatedCharacters = 30; // repeat char 30 times
var startTimer = new System.Windows.Forms.Timer {Interval = keyboardSpeed};
startTimer.Tick += startTimer_Tick;
startTimer.Start();
var repeatTimer = new System.Windows.Forms.Timer();
repeatTimer.Interval += keyboardDelay;
repeatTimer.Tick += repeatTimer_Tick;
//...
private static void repeatTimer_Tick(object sender, EventArgs e)
{
PostMessage(MemoryHandler.GetMainWindowHandle(),
(int)KeyCodes.WMessages.WM_KEYDOWN,
(int)KeyCodes.VKeys.VK_TAB, 0);
PostMessage(MemoryHandler.GetMainWindowHandle(),
(int)KeyCodes.WMessages.WM_CHAR,
(int)KeyCodes.VKeys.VK_TAB, 0);
counter++;
if (counter > maxRepeatedCharacters)
{
Timer timer = sender as Timer;
timer.Stop();
}
}
private static void startTimer_Tick(object sender, EventArgs eventArgs)
{
Timer timer = sender as Timer;
timer.Stop();
PostMessage(MemoryHandler.GetMainWindowHandle(),
(int)KeyCodes.WMessages.WM_KEYDOWN,
(int)KeyCodes.VKeys.VK_TAB, 0);
PostMessage(MemoryHandler.GetMainWindowHandle(),
(int)KeyCodes.WMessages.WM_CHAR,
(int)KeyCodes.VKeys.VK_TAB, 0);
}
Upvotes: 3
Reputation: 4636
I'm not sure exactly what you're trying to achieve, but below is the function I use to simulate text input using SendInput.
If you alter this slightly to make the final call to SendInput from a new thread, and then separate out the down and up events with a timer, does that achieve what you need?
[DllImport("user32.dll", SetLastError = true)]
static extern UInt32 SendInput(UInt32 numberOfInputs, INPUT[] inputs, Int32 sizeOfInputStructure);
public enum InputType : uint
{
MOUSE = 0,
KEYBOARD = 1,
HARDWARE = 2,
}
struct INPUT
{
public UInt32 Type;
public MOUSEKEYBDHARDWAREINPUT Data;
}
struct KEYBDINPUT
{
public UInt16 Vk;
public UInt16 Scan;
public UInt32 Flags;
public UInt32 Time;
public IntPtr ExtraInfo;
}
public enum KeyboardFlag : uint // UInt32
{
EXTENDEDKEY = 0x0001,
KEYUP = 0x0002,
UNICODE = 0x0004,
SCANCODE = 0x0008,
}
public static void SimulateTextEntry(string text)
{
if (text.Length > UInt32.MaxValue / 2) throw new ArgumentException(string.Format("The text parameter is too long. It must be less than {0} characters.", UInt32.MaxValue / 2), "text");
var chars = UTF8Encoding.ASCII.GetBytes(text);
var len = chars.Length;
INPUT[] inputList = new INPUT[len * 2];
for (int x = 0; x < len; x++)
{
UInt16 scanCode = chars[x];
var down = new INPUT();
down.Type = (UInt32)InputType.KEYBOARD;
down.Data.Keyboard = new KEYBDINPUT();
down.Data.Keyboard.Vk = 0;
down.Data.Keyboard.Scan = scanCode;
down.Data.Keyboard.Flags = (UInt32)KeyboardFlag.UNICODE;
down.Data.Keyboard.Time = 0;
down.Data.Keyboard.ExtraInfo = IntPtr.Zero;
var up = new INPUT();
up.Type = (UInt32)InputType.KEYBOARD;
up.Data.Keyboard = new KEYBDINPUT();
up.Data.Keyboard.Vk = 0;
up.Data.Keyboard.Scan = scanCode;
up.Data.Keyboard.Flags = (UInt32)(KeyboardFlag.KEYUP | KeyboardFlag.UNICODE);
up.Data.Keyboard.Time = 0;
up.Data.Keyboard.ExtraInfo = IntPtr.Zero;
// Handle extended keys:
// If the scan code is preceded by a prefix byte that has the value 0xE0 (224),
// we need to include the KEYEVENTF_EXTENDEDKEY flag in the Flags property.
if ((scanCode & 0xFF00) == 0xE000)
{
down.Data.Keyboard.Flags |= (UInt32)KeyboardFlag.EXTENDEDKEY;
up.Data.Keyboard.Flags |= (UInt32)KeyboardFlag.EXTENDEDKEY;
}
inputList[2*x] = down;
inputList[2*x + 1] = up;
}
var numberOfSuccessfulSimulatedInputs = SendInput((UInt32)len*2, inputList, Marshal.SizeOf(typeof(INPUT)));
}
Upvotes: 1
Reputation: 1207
I would do it in a thread, for sleeping and not-blocking the UI thread. Look at this:
System.Threading.Thread KeyThread = new System.Threading.Thread(() => {
//foreach process
// press key now
PostMessage(MemoryHandler.GetMainWindowHandle(),
(int)KeyCodes.WMessages.WM_KEYDOWN,
(int)KeyCodes.VKeys.VK_TAB, 0);
System.Threading.Thread.Sleep(1000); // wait 1 second
//foreach process
// release keys again
PostMessage(MemoryHandler.GetMainWindowHandle(),
(int)KeyCodes.WMessages.WM_KEYUP,
(int)KeyCodes.VKeys.VK_TAB, 0);
});
Ofcourse, you have to start it.
Upvotes: 1
Reputation: 2357
When holding a key down on a physical keyboard, repeated keystrokes are passed to the active window. This is not built into the keyboard, but is a Windows feature.
You can simulate this by doing the following steps in order:
Upvotes: 1
Reputation: 941317
The repeating of a keystroke when you hold it down is a feature that's built into the keyboard controller. A microprocessor that's built into the keyboard itself. The 8042 microcontroller was the traditional choice, the keyboard device driver still carries its name.
So, no, this is not done by Windows and PostMessage() is not going to do it for you. Not exactly a problem, you can simply emulate it with a Timer.
Upvotes: 6
Reputation: 3
Not sure what you're doing with the PostMessage but modifying some code from here: SendKey.Send() Not working
[DllImport("user32.dll", SetLastError = true)]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
public static void PressKey(Keys key, bool up)
{
const int KEYEVENTF_EXTENDEDKEY = 0x1;
const int KEYEVENTF_KEYUP = 0x2;
if (up)
{
keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, (UIntPtr)0);
}
else
{
keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY, (UIntPtr)0);
}
}
void TestProc()
{
PressKey(Keys.Tab, false);
Thread.Sleep(1000);
PressKey(Keys.Tab, true);
}
Maybe this could work for you. Its just a key down and then a key up with a sleep in between. You could even add to this further and pass a time value for how long you want the key to stay down.
Upvotes: 0