4
\$\begingroup\$

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
 }
dfhwze
14.1k3 gold badges40 silver badges101 bronze badges
asked Sep 19, 2017 at 13:53
\$\endgroup\$
8
  • \$\begingroup\$ You say you can't use SendKeys, but you're using it all over the place. Parts of your question are unclear. Could you try to clarify? \$\endgroup\$ Commented Sep 19, 2017 at 14:17
  • \$\begingroup\$ I can't use SendKeys.Send, SendKeys.SendWait works. I'd prefer not to rely on SendKeys at all due to performance/reliability concerns, but I think it's only option right now to send keyboard input for me. \$\endgroup\$ Commented Sep 19, 2017 at 14:19
  • \$\begingroup\$ Right. So the current code works, but you want to get rid of the entire SendKeys dependency. Excellent premise, I'm sure we can work with that :-) \$\endgroup\$ Commented Sep 19, 2017 at 14:24
  • \$\begingroup\$ Yes, if it's possible. Or at least simplify/improve the performance. Currently I am using the hyprid copy/paste+SendKeys.SendWait to improve performance vs SendKeys.SendWait on its own. Looking at this code I feel there are so many potential unexpected places for this to go wrong ;) \$\endgroup\$ Commented Sep 19, 2017 at 14:28
  • 1
    \$\begingroup\$ I'm not familiar with this construct: message = message.sak;jmas;kjdReplace("{ENTER}x", "\r\n");, is there a typo in your code? \$\endgroup\$ Commented Sep 19, 2017 at 14:39

1 Answer 1

5
\$\begingroup\$

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}");
answered Sep 29, 2019 at 15:15
\$\endgroup\$
1
  • \$\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, wrapping TRACE by DEBUG is definitely wrong. \$\endgroup\$ Commented Oct 3, 2019 at 17:19

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.