Thursday, August 2, 2007

WPF: Virtual Keyboard




Creating a virtual keyboard in WPF can be quite an obstacle. As far as I can tell, WPF does not directly support simulating keyboard strokes. As a result, you have two options:

  • Import the SendInput function from a C++ DLL (User32.dll i believe it is)
  • Manually write the output in the cases you want to.

The 2nd option isn't too bad to handle. This way also prevents you from having to handle each button separately, but I'm sure you could work around that in a way similar to what I've done here.

Basically, create your keyboard layout within it's own panel or grid, with each key being a button. Once you've done this, you can do a foreach loop on each button, where if it's a letter (usually the Button.Content.Length == 1), send it to one event handler, otherwise, send the button to a special event handler. Also, make sure every button is not focusable (Button.IsFocusable = false).

1foreach (Button b in KeyboardPanel.Children)
2{
3 b.Focusable = false;
4
5 if (b.Content.ToString().Length <= 1)
6 {
7 b.Click += new RoutedEventHandler(keyboardButton_Click);
8 }
9 else
10 {
11 b.Click += new RoutedEventHandler(keyboardButtonSpecial_Click);
12 }
13}


Once you've done this, the implementation is similiar to this:

662
663
664
665 void keyboardButtonSpecial_Click(object sender, RoutedEventArgs e)
666 {
669 switch (((Button)sender).Content.ToString())
670 {
671 case "Space":
672 if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
673 {
674 TextBox box = ((TextBox)System.Windows.Input.Keyboard.FocusedElement);
675 int newIndex = box.CaretIndex + 1;
676 box.Text = box.Text.Insert(box.CaretIndex, " ");
677 box.CaretIndex = newIndex;
678 }
679 break;
680 case "Shift":
681 if (isKeyboardUppercase())
682 setKeyboardUppercase(false);
683 else
684 setKeyboardUppercase(true);
685 break;
686 case "Tab":
687 ((UIElement)System.Windows.Input.Keyboard.FocusedElement).MoveFocus(new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.Next));
688 break;
689 case "Delete":
690 if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
691 {
692 System.Windows.Forms.SendKeys.SendWait("{BACKSPACE}");
693 }
694 break;
695 case "Enter":
696 if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
697 {
698 TextBox box = ((TextBox)System.Windows.Input.Keyboard.FocusedElement);
699 if (box.AcceptsReturn)
700 {
701 int nIndex = box.CaretIndex + 1;
702 box.Text = box.Text.Insert(box.CaretIndex, Environment.NewLine);
703 box.CaretIndex = nIndex;
704 }
705 }
706 break;
707 }
708 }
709
710 bool isKeyboardUppercase()
711 {
712 if (Char.IsUpper((keyboardLetterA.Content.ToString().ToCharArray())[0]))
713 return true;
714 else
715 return false;
716 }
717
718 void setKeyboardUppercase(bool upper)
719 {
720 foreach (Button b in KeyboardPanel.Children)
721 {
722 if (b.Content.ToString().Length <= 1
723 {
724 if (upper)
725 b.Content = b.Content.ToString().ToUpper();
726 else
727 b.Content = b.Content.ToString().ToLower();
728 }
729 }
730 }
731
732 void keyboardButton_Click(object sender, RoutedEventArgs e)
733 {
735 if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
736 {
737 TextBox box = ((TextBox)System.Windows.Input.Keyboard.FocusedElement);
738 int newIndex = box.CaretIndex + 1;
739 box.Text = box.Text.Insert(box.CaretIndex, ((Button)sender).Content.ToString());
740 box.CaretIndex = newIndex;
741
742 if (Char.IsUpper((((Button)sender).Content.ToString().ToCharArray())[0]))
743 {
744 setKeyboardUppercase(false);
745 }
746
747 }
748 }
749 #endregion



In here, you can see I've got several things going on. If you push Shift on my keyboard, it'll change the content of the letters to uppercase or lowercase, based on what they already were. (I test this by looking at the letter A key's content).

I also only type if we're currently focused into a TextBox. For the program I was using this in, I had no need for typing unless we're focused in a TextBox. Since i know I'm in a TextBox, i can look for the Caret and type at that point.

Look at the "DELETE" Key (it's actually Backspace, it was just improperly named). I've left this bad code "as-is" to show the other way you could handle some input. If I'm in WPF, obviously I would like to keep Windows Forms out of my application if possible. After looking at this, the answer seemed somewhat obvious (and similiar to all the other methods of input):

1                case "Delete":
2 if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
3 {
4 TextBox box = ((TextBox)System.Windows.Input.Keyboard.FocusedElement);
5 int newIndex = box.CaretIndex - 1;
6 if (newIndex >= 0)
7 {
8 box.Text = box.Text.Remove(box.CaretIndex - 1, 1);
9 box.CaretIndex = newIndex;
10 }
11 }
12 break;


This method handles deleting characters in logic, and now eliminates the need for System.Windows.Forms.

The applications for a virtual keyboard like this? I'd say for added accessibility, and touch-screen applications where users may not want to move their fingers from the monitor to a keyboard for typing small things.

29 comments:

lingsen said...

Chould you send me your whole project?tks.
my e-mail:xielingsen @ gmail.com

Frédéric P said...

Hello !
Your keyboard control is so cool ! Could you please send me your source project ? Thanks a lot.
My Email : frederic.poindron@gmail.com

Anonymous said...

Hello this is exactly what I need for a project for work for touchscreens. Can you send me the source code?

Thanks,
Wade.Beasley@insightbb.com

Phil said...

hello
it's exactly what i'm searching for a touchscreen project. If possible i would enjoy if you can send me the project:

philippe.staedler@gmail.com

Guard said...

Unfortunately, as this is embedded within an application I'm doing for someone, I'm unable to send out the actual source. Sorry about that guys.

Overall, undertaking this project isn't too complicated. Create the interface using Blend or VS, and use a method for interfacing with the buttons similar to what I've done above.

Anonymous said...

hi,

Great project! Can You Please(Please!!) send it to me?

It will be great!

my mail is john.maymon@gmail.com

Marcin Sulecki said...

Can you publish virtual keyboard as usercontrol?

Anonymous said...

private void keyboardButtonSpecial_Click(object sender, RoutedEventArgs e)
{
switch (((Button)sender).Content.ToString())
{
case "Space":
if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
{
TextBox box = ((TextBox)System.Windows.Input.Keyboard.FocusedElement);
int newIndex = box.CaretIndex + 1;
box.Text = box.Text.Insert(box.CaretIndex, " ");
box.CaretIndex = newIndex;
}
break;
case "Shift":
if (isKeyboardUppercase())
setKeyboardUppercase(false);
else
setKeyboardUppercase(true);
break;
case "Tab":
((UIElement)System.Windows.Input.Keyboard.FocusedElement).MoveFocus(new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.Next));
break;
case "Delete":
if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
{
System.Windows.Forms.SendKeys.SendWait("{BACKSPACE}");
}
break;
case "Enter":
if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
{
TextBox box = ((TextBox)System.Windows.Input.Keyboard.FocusedElement);
if (box.AcceptsReturn)
{
int nIndex = box.CaretIndex + 1;
box.Text = box.Text.Insert(box.CaretIndex, Environment.NewLine);
box.CaretIndex = nIndex;
}
}
break;
}
}
bool isKeyboardUppercase()
{
if (Char.IsUpper((keyboardLetterA.Content.ToString().ToCharArray())[0]))
return true;
else
return false;
}
void setKeyboardUppercase(bool upper)
{
foreach (Button b in KeyboardPanel.Children)
{
if (b.Content.ToString().Length <= 1)
{
if (upper)
b.Content = b.Content.ToString().ToUpper();
else
b.Content = b.Content.ToString().ToLower();
}
}
}

Anonymous said...

void keyboardButton_Click(object sender, RoutedEventArgs e)
{
if (System.Windows.Input.Keyboard.FocusedElement.GetType() == typeof(TextBox))
{
TextBox box = ((TextBox)System.Windows.Input.Keyboard.FocusedElement);
int newIndex = box.CaretIndex + 1;
box.Text = box.Text.Insert(box.CaretIndex, ((Button)sender).Tag.ToString());
box.CaretIndex = newIndex;

}
}

Anonymous said...

It can help someone to copy and paste...

Edu said...

not function for filds PasswordBox. the BackSpace no contains CaretIndex. tks

Guard said...

It looks like you're right. I've never dealt with the PasswordBox in WPF, but it looks like the control does not support any kind of CaretIndex, and the TextBox no longer supports character masking as was the case in .NET 2.0

I was looking at this for a reference.

So in this case the keyboard isn't going to work at all for password fields. For the example I've posted though, I assumed all fields were TextBox's anyways, which went around this issue. For this issue, you'd need to create a custom Textbox control that does indeed mask characters similar to a PasswordBox similar to the way .NET 2 handled these, unless someone else has a better idea?

Taylor said...

We have implemented a keyboard similar to this and have issues with two way binding when we manipulate the textbox from code. Have you had any issues with this?

Guard said...

Are you binding the textbox to the keyboard specifically somehow, or is this an issue with a Textbox completely outside your keyboard?

I'm not sure what exactly is involved in your two-way binding to help out.

Jim said...

Nice. I'm doing something similar, and I've been using the Tag property on the button to identify the special cases, since I have images rather than string content in some (like the back and return buttons.)

If it has a tag, it's a special case; if the tag is null, I get the content as a string.

Jim said...

Hi Again.

I ran into a wall with PasswordBox controls, which don't have a caretindex property, which sent me back looking for a way to simulate keystrokes.

Check out:
characters and keystrokes

katya said...

Hi! It's really what I'm searching so long..Could you please send me your source code? It's so wonderful!!!!

Thanks a lot and best regards from Ukraine!!!
Katya
e-mail: any.retake@gmail.com

Ramu Attelli said...

I am looking for this kind of keybaord for a touchscreen application. Can you please send me the whole project??? Tks my email is ramu.attelli@gmail.com

Anonymous said...

Hi,
Fucusable is "false" but still getting focus if i am writing google search bar or another textbox which textbox is not on Virtual Keyboard application.. All my elements are not focusable. any clue?

sri said...

I am looking for this kind of keybaord for a touchscreen application. Can you please send me the whole project??? Tks my email is srikanth2k8@gmail.com

Anonymous said...

could you please send me the whole project. This something we are trying to implement and this would really make it easy.

tiggerhorton@gmail.com

Thanks

Guard said...

Due to this code being a part of another project I worked on for someone, I can't release the original code or project, so no point in asking. Sorry.

sri said...

hi Gaurd,

i can understand, but i new to wpf, and the dead lin is aproaching. so need to complete in order to continue the job.

Ray Akkanson said...

SendKey command do not work..

Ray Akkanson

geochatz said...

Can you please send me the whole project ?

Thanks.

geochatz[at]yahoo.com

Christian said...

Would you please send me the project?

Send to chr1986(at)hotmail(dot)com

Anonymous said...

Great code!

Would you please send me the project?

ciro.cesar@gmail.com

Anonymous said...

Great code!

Would you please send me the project?

Thanks !

51FALLINLOVE@giga.net.tw

Anonymous said...

Can you please send me the whole project ?

Thanks.

51FALLINLOVE@giga.net.tw