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
}
1 Answer 1
Conditional code in Debug
There is an alternative for using this pattern:
#if DEBUG Trace.WriteLine($"Clipboard Failure {ex.Message}"); #endif
Use Debug.WriteLine instead. Much of you code could be simplified replacing Trace.WriteLine
with Debug.WriteLine
so you don't require the preprocessor directive #if DEBUG
.
Debug.WriteLine($"Clipboard Failure {ex.Message}");
-
\$\begingroup\$ I think it also worth mentioning that
Trace.WriteLine
is decorated with[System.Diagnostics.Conditional("TRACE")]
so it depends on what kind of output is required but I admit, wrappingTRACE
byDEBUG
is definitely wrong. \$\endgroup\$t3chb0t– t3chb0t2019年10月03日 17:19:27 +00:00Commented Oct 3, 2019 at 17:19
SendKeys
, but you're using it all over the place. Parts of your question are unclear. Could you try to clarify? \$\endgroup\$SendKeys
dependency. Excellent premise, I'm sure we can work with that :-) \$\endgroup\$message = message.sak;jmas;kjdReplace("{ENTER}x", "\r\n");
, is there a typo in your code? \$\endgroup\$