I have a handheld Windows Embedded Compact Edition bluetooth device submimtting data to PC application that needs to type the information into a Windows 8.1 computer. The longest strings are about 128 characters long. In an ideal world I would get the device to just simulate a Bluetooth keyboard, but working out how to do that is beyond me for now.
As much as I consider it a crime to use SendKeys, I have had to use SendKeys.SendWait
to transmit the data to keyboard. I was unable to use SendKeys.Send
as this program runs in system notification area, and I get error message:
SendKeys cannot run inside this application because the application is not handling Windows messages.
Either change the application to handle messages, or use the SendKeys.SendWait method.
Unfortunately for long strings SendKeys.SendWait
is quite slow, you can see the characters being typed out gradually.
It is a requirement to simulate the keyboard input faster.
It is OK if we destroy user clipboard in this scenario; as these are special purpose machines not used for any other task. In my testing I found clipboard much faster. Unfortunately sometimes the data sends special keyboard controls (i.e. Ctrl/Alt/F2 etc) which do not work via Clipboard.
So what I currently do is send bulk of data via clipboard, and separately send any special characters via SendKeys
, typically there is only 4 of these special characters at most per data sent.
In my testing this is improving speeds from ~200 ms to type the text, to ~50ms, a noticeable improvement.
Unfortunately the code got way more complex and ugly.
While this works, I am concerned I may not have thought of all the scenarios that will break it.
Some points:
The following must be sent by SendKeys
:
- Special keypress is +, ^, % followed by either one character or {string of characters}
- Special keypress can also be defined as {}
Anything else is being sent via Clipboard.
- There is never any escaped { or } being sent
Code example:
private static void QuickSend(string message)
{
// these "SPECIAL" characters can be sent via clipboard, so replace them with clipboard version
message = message.Replace("{ENTER}", "\r\n");
message = message.Replace("{TAB}", "\t");
// stores messages in order to be sent to keyboard
List<string> messages = new List<string>();
// these characters are special keypresses that can't be sent via clipboard
char[] specialChars = { '+', '^',
'%',
'~', '{' };
for (int i = 0; i < message.Length; i++)
{
int nextSpecialCharIndex = message.IndexOfAny(specialChars, i);
// no more special characters, use remaining string as is
if (nextSpecialCharIndex == -1)
{
messages.Add(message.Substring(i));
i = message.Length;
}
else
{
// if the current character is not the special character
// add text up until this special character occurs
if (nextSpecialCharIndex != i)
{
messages.Add(message.Substring(i, nextSpecialCharIndex - i));
}
// check we are not end of string
if (nextSpecialCharIndex < message.Length - 1)
{
// if special character is brace, send text from current brace to end brace
if (message.Substring(nextSpecialCharIndex, 1) == "{")
{
int endBraceIndex = message.IndexOf('}', i);
if (endBraceIndex == -1)
{
// no end brace - just send remaining text
messages.Add(message.Substring(nextSpecialCharIndex));
i = message.Length;
}
else
{
messages.Add(message.Substring(nextSpecialCharIndex, endBraceIndex - nextSpecialCharIndex + 1));
i = endBraceIndex;
}
}
else
{
// if string following special character is a brace, include that text as welll
if (message.Substring(nextSpecialCharIndex + 1, 1) == "{")
{
int endBraceIndex = message.IndexOf('}', i);
if (endBraceIndex == -1)
{
messages.Add(message.Substring(nextSpecialCharIndex));
i = message.Length;
}
else
{
messages.Add(message.Substring(nextSpecialCharIndex, endBraceIndex - nextSpecialCharIndex + 1));
i = endBraceIndex;
}
}
else
{
// Just submimt special character + next character
messages.Add(message.Substring(nextSpecialCharIndex, 2));
i = nextSpecialCharIndex + 1;
}
}
}
}
}
#if DEBUG
StringBuilder checkResult = new StringBuilder();
#endif
// need STA thread for Clipboard
Thread thread = new Thread(() => {
for (int i = 0; i < messages.Count; i++)
{
#if DEBUG
Trace.WriteLine($"{i}: '{messages[i]}'");
checkResult.Append(messages[i]);
#endif
// check if message has special character
if (messages[i].IndexOfAny(specialChars) == -1)
{
// no...send via "FAST" clipboard method
try
{
Clipboard.SetText(messages[i]);
SendKeys.SendWait("^V");
}
catch (Exception ex)
{
#if DEBUG
Trace.WriteLine("Clipboard Failure {ex.Message}");
#endif
// clipboard failure, revert to SendKeys...
SendKeys.SendWait(messages[i]);
}
}
else
{
// yes...send via SENDKEYS
SendKeys.SendWait(messages[i]);
}
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
#if DEBUG
Trace.WriteLine($"Original: {message}");
Trace.WriteLine($"CHECK : {checkResult.ToString()}");
Debug.Assert(checkResult.ToString() == message, "Messages do not match!");
#endif
}
SendKeys
, but you're using it all over the place. Parts of your question are unclear. Could you try to clarify? \$\endgroup\$ – Mast Sep 19 '17 at 14:17SendKeys
dependency. Excellent premise, I'm sure we can work with that :-) \$\endgroup\$ – Mast Sep 19 '17 at 14:24message = message.sak;jmas;kjdReplace("{ENTER}x", "\r\n");
, is there a typo in your code? \$\endgroup\$ – Mast Sep 19 '17 at 14:39