Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / WPF

A Software Virtual Keyboard for Your WPF Apps

Rate me:
Please Sign up or sign in to vote.
4.96/5 (92 votes)
25 Jul 2014Ms-PL19 min read 314K   22.8K   175   129
Walks through the creation of an on-screen virtual keyboard for entering non-ASCII chars

 

Virtual-WPF/KBImage.png

Introduction

Sometimes having a software-based ‘virtual keyboard’ for your desktop applications can be a real help. You can provide a way for your users to enter characters that they don’t have on their physical keyboard. Or, provide a more user-friendly way than other applications provide. You can also circumvent a stealthy keylogger by entering passwords this way. And for writing letters or posts to readers in other languages, it can be a huge timesaver.

Thus, the JHVirtualKeyboard project. You can download the binary files here, and get the full source-code here. I built it using Visual Studio 2010: if you really need a VS 2008 solution then email me.

Background

Virtual-WPF/DemoApp.png

This was a fun project done to do in Windows Presentation Foundation (WPF), and I thought it’d provide a useful article to youz because of the range of design techniques it uses. WPF yielded a few side-bennies like the fact that you can resize this Window and all the keys resize themselves appropriately. That was a freebee, by virtue of the Grid control.  Of course, I consider WPF (and SilverLight) generally fun; it’s the premier GUI-design platform out there and I have great respect for it’s creators. That said, this project does represent a significant amount of work. Let me rephrase that: a lot amount of work, to get it right. And then to boil it down to it’s essentials to the smaller application you see before you. I’m sharing this with you, to save you time on your projects and to illustrate techniques. Please be gracious in your feedback and contribute your own improvements so we can evolve this with improvements. If you send me your suggestions, I’ll edit them into the code upon the next revision and cite you as the contributor. Multiple minds are always superior to just one.

We’ll walk through it’s design, have a little fun, and provide a link so you can download the entire demo application and source-code so you can put it to immediate use.

The JHVirtualKeyboard in Operation

It’s an auxillary WPF window which, if you integrate it into your application, allows you to provide a way for your user to invoke it and click on the key-buttons to insert characters into your application text fields.

Virtual-WPF/DemoAppWithHands_919x449.jpg

I’ve provided it with several language modes as a starter. My immediate need was for a memorization flash-card program to learn various language terms as in Arabic or Russian, so that’s what I created first. It also has French, German, and Spanish, which only actually add a few locale-specific characters. Perhaps you can tackle implementing a Mandarin keyboard and send to me to be included – I’d love to see! lol

You select the keyboard-layout via the ComboBox at the lower-right corner. Changing the language causes the keyboard to automatically update itself.

I found most of the keyboard-layout information on wikipedia and the like. I have to warn you though: that’s a good way to get utterly distracted and winding up delving into the historical underpinnings of ligatures and linguistics. But anyway, I advocate for simplification via standardization. Hence, I try to select the one layout that is most appropriate as a standard for a given language/culture, but not go so far as to bastardize the standard US keyboard form itself. I can see that for some languages, that is not going to be an easy balance to choose. That arrangement should be chosen (by my reasoning) to most resemble what a native speaker would most likely be used to, within the confines of the standard keyboard layout. We are not selecting a Culture/Language for the GUI, after all. That’s a different topic altogether.

Anyway..

As a further aid in typing, along the bottom edge, are thirteen extra typographic symbols you can type. I’ve needed these a lot.

In the demo-app pictured above you can see that there are two text fields. One is a TextBox, the other a RichTextBox. The virtual keyboard (VK) works the same with either one. Whichever text field has the current focus, that’s the one who receives whatever is typed into the VK.

The keyboard has provisions for saving it’s state, in that the next time you invoke it it’ll preset to the last language you used, and it’ll also remember whether you had it up whence you last exited your application, so it can auto-launch itself next time.

As a further convenience, just for kicks n giggles – when you move your application around on the screen, this keyboard Window can move in sync with it.

The key ToolTips are a convenience to the user, to help identify what the key is. I was tempted to insert the entire history of each glyph in there. However, this application/tool is for helping the person of the current Culture, type characters that happen to come from a foreign alphabet (relative to that user). Thus the ToolTips are in English (by default) to identify what those glyphs are. For this application, those ToolTips can be important.

When you click on the CAPS LOCK key, all the letters shift to uppercase. It stays in this state until you click CAPS LOCK again.

When you click on the SHIFT LOCK key, you have access to the shifted-state key symbols that you see on the keycaps, in addition to capital letters. This is the same as a physical keyboard does. In this case, after a ten-second timeout it drops back down to the unshifted state.

The Control and Alt keys do nothing. I just left them there so it looks more like a standard (US English) keyboard. But you can put those to use for something like the dead-letter keys that certain keyboards use languages. Or just remove them.

The Platform

This is created using Visual Studio 2010, on Windows 7 x64, and targets the .NET Framework 4. I did not test it on earlier versions.  I’m a believer in letting the vendors evolve our tools and taking advantage of the latest versions. If you’re one of those still stuck on version 1.0, because you’re so ultra-conservative – all I can say is: go away!  I have a separate library that this and all my other projects use, but I pared it down for this project download to just the bare minimum that it uses and included it with your source download.

I set the configuration to target "Any CPU". And that took a bit of tinkering. I’m unsure why. It kept flipping itself back to x64. When VS 2010 on Windows x64 suddenly seems to not be recognizing certain of your projects, check the build configuration. If one is x86 and the other x64, they will definitely clip your swagger! It can take several attempts before the setting sticks.

I’m sure you want to know how to put it to use within your own program. Then we’ll chat about a few interesting aspects of it’s design, and then finally walk through the code itself.

Using it for your own application

The easiest way to tell how to use the VK in your own design, is to show you a simple demo-app that does just that. Let’s mosey through it in three steps.

Using it within your own Application is easy. The demo app that comes with your download is a pared-down WPF application that has a simple TextBox to receive the characters, and a Button for launching the virtual keyboard. And.. I added a 2nd text control, a RichTextBox, to illustrate using the VK for multiple input fields.

Here’s the XAML for the demo-app:

   <DockPanel LastChildFill="True">
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
            DockPanel.Dock="Bottom" Margin="0,8,10,8">
            <Button Name="btnVK" Margin="5,0" Width="100" Click="btnVK_Click">
                Virtual Keyboard</Button>
            <Button Name="btnClose" Margin="5,0" Width="100" IsCancel="True"
                Click="btnClose_Click">Exit</Button>
        </StackPanel>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Label Name="lblTextBox" Margin="25,0,0,0" Padding="5,5,5,0">TextBox:</Label>
            <TextBox Name="txtBox" Margin="20,0,20,10" Grid.Row="1"
                GotFocus="txtBox_GotFocus" />
            <Label Name="lblRichTextBox" Margin="25,0,0,0" Padding="5,5,5,0"
                Grid.Row="2">RichTextBox:</Label>
            <RichTextBox Name="txtrichBox" Margin="20,0,20,5" Grid.Row="3"
                GotFocus="txtrichBox_GotFocus" />
        </Grid>
    </DockPanel>
</Window>

Mainly there’s a TextBox and a RichTextBox. The DockPanel on line 1 is there simply as a convenience for sticking the buttons along the bottom. I’ve been in the habit of throwing the DockPanel in there everytime I create a new Window. Note that when you set a Label over another field, if you want it to get quite close you have to set it’s bottom-padding to zero as well as it’s bottom-margin. I leave the other sides at the default padding value of 5. In some cases I’ve had to use a negative bottom-margin to get it to nudge as near as I wanted. Tried that same method with a girl at the theatre, but that didn’t have positive results.

The two buttons have click-handlers which serve to launch the virtual keyboard, and to close the application.

I use a certain coding formatting standard in all our projects, which I’ve detailed here.

Let’s check out the code-behind for this MainWindow:

C#
 1. using System;
 2. using System.Windows;
 3. using System.Windows.Controls;
 4. using JHVirtualKeyboard;
 5.
 6. namespace JHVirtualKeyboardDemoApp
 7. {
 8.     public partial class MainWindow : Window, IVirtualKeyboardInjectable
 9.     {
10.         public MainWindow()
11.         {
12.             InitializeComponent();
13.             // Remember which text field has the current focus.
14.             _txtLastToHaveFocus = txtBox;
15.             // Set up our event handlers.
16.             this.ContentRendered += OnContentRendered;
17.             this.LocationChanged += OnLocationChanged;
18.             this.Closing += new System.ComponentModel.CancelEventHandler(OnClosing);
19.         }

You add a using pragma for the JHVirtualKeyboard namespace, and implement the IVirtualKeyboardInjectable interface as shown above. That interface is your contract with the virtual keyboard (VK) so they can interoperate.

Notice what we’re doing on line 14: we have an instance variable, _txtLastToHaveFocus, that serves to point to whichever text field has focus. We use this to tell the VK where to put it’s characters.

Notice also that we have three event handlers defined here. Let’s check these out.

The ContentRendered event handler:

 1.         void OnContentRendered(object sender, EventArgs e)
 2.         {
 3.             // If the Virtual Keyboard was up (being shown) when this
 4.             // application was last closed, show it now.
 5.             if (Properties.Settings.Default.IsVKUp)
 6.             {
 7.                 ShowTheVirtualKeyboard();
 8.             }
 9.             // Put the initial focus on our first text field.
10.             txtBox.Focus();
11.         }

We have 3 things going on here.

  1. As you can see on line 5, it remembers whether the VK was up when we last ran this application. To achieve this, you simply add a boolean flag to your settings; in this case we named it IsVKUp.
  2. Invoke the VK using method ShowTheVirtualKeyboard. This is placed here, within the ContentRendered handler, because at this point your Window is already rendered and the VK can know where to position itself.
  3. On line 10 we set focus to the TextBox. Thus, you’ll still see the nice blinking caret in your text field, just as you would without the VK.

Here’s the ShowTheVirtualKeyboard method:

C#
1. public void ShowTheVirtualKeyboard()
2. {
3.     // (Optional) Enable it to remember which language it was set to last time,
       // so that it can preset itself to that this time also.
4.     VirtualKeyboard.SaveStateToMySettings(Properties.Settings.Default);
5.     // Show the keyboard.
6.     VirtualKeyboard.ShowOrAttachTo(this, ref _virtualKeyboard);
7. }

This is where we fire up the VK. You’re doing 2 things here.

  1. By calling SaveStateToMySettings, you tell it how to save it’s state to your application settings. This is optional.
  2. You call ShowOrAttachTo to launch the VK. You pass it the this pointer, so it knows who it’s owner Window is. You give it a reference to your local _virtualKeyboard instance variable, so you have a reference to it.

You need to implement the IVirtualKeyboardInjectable interface. Here’s how the demo app does this:

C#
public System.Windows.Controls.Control ControlToInjectInto
{
  get { return _txtLastToHaveFocus; }
}

By implementing ControlToInjectInto, you tell the VK where to send the characters that your user types into it.

If you have just one text field, you would simply return that (the instance-variable for the field). Here, we have two text fields. So we instead track which one has focus using this instance-variable, and return that in this property getter.

Let’s check out something else cool..

C#
1. void OnLocationChanged(object sender, EventArgs e)
2. {
3.     // Do this if, when your user moves the application window around the screen,
4.     // you want the Virtual Keyboard to move along with it.
5.     if (_virtualKeyboard != null)
6.     {
7.         _virtualKeyboard.MoveAlongWith();
8.     }
9. }

If you handle the LocationChanged event of your Window thus, then the VK will move around along with your Window, like it’s stuck to it. You can still position the VK elsewhere. Normally, I include a checkbox in my app’s Options dialog to give the user the ability to turn this on or off. The MoveAlongWith is a Window extension method from the WPFExtensions.cs file.

Oh yeah.. the click-handler that launches it. Nothing remarkable here..

C#
1. #region btnVK_Click
2. /// <summary>
3. /// Handle the event that happens when the user clicks on the
   /// Show Virtual Keyboard button.
4. /// </summary>
5. private void btnVK_Click(object sender, RoutedEventArgs e)
6. {
7.     ShowTheVirtualKeyboard();
8. }
9. #endregion

And here is the Closing event handler:

C#
1.         void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
2.         {
3.             bool isVirtualKeyboardUp = _virtualKeyboard != null &&
                   VirtualKeyboard.IsUp;
4.             Properties.Settings.Default.IsVKUp = isVirtualKeyboardUp;
5.             Properties.Settings.Default.Save();
6.         }

All this does, is save the Up/NotUp state of your VK to your application’s settings.

Tracking which Field has Focus

This demo-app has two text fields so that you can see how to use your VK to serve input into more than one control.

What you need to accomplish this, is pretty simple. We added that instance variable, _txtLastToHaveFocus. This variable has to always be pointing to whichever field is currently holding the focus within your Window. How do we do this?

Simple. We hook up handlers to the GotFocus events of all the text fields, as here..

C#
private void txtBox_GotFocus(object sender, RoutedEventArgs e)
{
    // Remember that the plain TextBox was the last to receive focus.
    _txtLastToHaveFocus = txtBox;
}

private void txtrichBox_GotFocus(object sender, RoutedEventArgs e)
{
    // Remember that the RichTextBox was the last to receive focus.
    _txtLastToHaveFocus = txtrichBox;
}

As a result, that variable is always pointing to the correct field.

Settings

I mentioned you need to add something to your application’s settings:

Virtual-WPF/Settings.png

Here you can see I put two settings. The bool setting, IsVKUp, can be whatever you want to name it. The one that specifies the language, though, must be named "VKLayout" or else VK won’t see it.

That’s it as far as how to use this within your own app.

Let’s talk about the design a bit.

The Design of the Virtual Keyboard

I’m quite fond of object-oriented design, especially where it saves time and yields a more elegant creation.

Here, we have keyboard layouts. These can be pretty detailed bits of information. Fifty-plus keys that change according to the language/alphabet, tooltips, and double that for the shifted/unshifted states, and then you have to treat the capital letters versus small letters, differently than the distinction between shifted/unshifted symbols. And there’re potentially hundreds of possible keyboard layouts.

On the other hand, some of these have a lot in common. And, considering the central role of the common US-std English-language keyboard, I based the root class of the hierarchy of keyboard layouts upon the English US keyboard to supply the default properties.

Thus, we have a conceptual scheme that begs of an object-oriented class hierarchy. At the top, the root class is the US keyboard layout. Everything else either inherits their stuff from that, or overrides it. Below that, at the second level, are the main variations. Russian (perhaps that should’ve really been called Cyrillic), Spanish, etc. And below that, could be further variations of those.

The economy of data arises when you consider that, e.g. the Spanish keyboard changes only a few things, but wants to leave everything else in place just as with the English US keyboard. So if we can let the Spanish keyboard inherit all the English keyboard features, it only has to override just those keys (and their tooltips, etc.) that it wants to change. The ideal OOP application.

To forge this scheme into our WPF XAML-based application that has fifty data-bound buttons, check it ..

Virtual-WPF/ClassDiagram.png

If you look at the KeyAssignmentSet class in Visual Studio (VS), you’ll see it has a big honkn glob of instance variables and properties. One for every key-button of the keyboard. I named them "VK_A", "VK_OEM1", etc. to match the Win32 definitions.

The class KeyAssignmentSet represents the US English keyboard layout.

The subclass SpanishKeyAssignmentSet, overrides only those properties it wants to change. In this, just seven does the trick. That’s a lot easier than providing for all 50-plus key definitions. Suweet.

Now, to make this work with our XAML design, merits a bit of intricacy. WPF likes to have stable view-models, to be a view of. The VK itself has a main view-model class: KeyboardViewModel. All the key-buttons have view-model objects that are members of KeyboardViewModel. Thus, to apply the appropriate KeyAssignmentSet to that view-model, we have an array of the individual key view-models, and we iterate across those assigning the information from the KeyAssignmentSet to them.

For this check out the method AssignKeys, in VirtualKeyboard.xaml.cs

In response, the WPF view updates itself automatically to reflect the new layout.

In this code I use the term "CodePoint" to refer to a spot within the Unicode standard that’s represented by a specific number, and depending upon which font you happen to be using – maps to a resulting glyph. Unicode is a wonderful resource; a massive amount of work was invested to wrest order and manageability out of the World’s chaos of languages and alphabets.   Here, I use the term "shifted CodePoint" to refer to the character on the upper portion of the key button, such as the Asterisk that is often over top of the digit-8 key.

Here’s the layout with the key-names shown:

Virtual-WPF/VKwKeynames100.png

A few observations from the XAML of the VK..

1.     xmlns:jhlib="clr-namespace:JHLib;assembly=JHLib"
2.     xmlns:vk="clr-namespace:JHVirtualKeyboard"
3.     mc:Ignorable="d"
4.     jhlib:WindowSettings.SaveSize="True"

I bring in the namespace for the external utility library and assign it to the XML namespace prefix "jhlib". Then I do the same for the JHVirtualKeyboard assembly and assign that to "vk". Note how the syntax varies: one is an external assembly, and the other is a local assembly hence doesn’t require the ";assembly=" part. This is one of those things that if you don’t get it right, you’ll spin wheels trying to figure out why your XAML isn’t recognizing your junk in your trunk.

I set the WindowStyle property to ToolWindow, and the FontFamily to Arial Unicode MS since that seems to have pretty decent coverage of the non-Latin codepoints.

Let’s look at one of the key-button definitions:

<Button Name="btnSectionMark"
        Command="vk:VirtualKeyboard.KeyPressedCommand"
        CommandParameter="§"
        ToolTip="Section sign, or signum sectionis"
        Margin="6,0,2,1"
        Width="25" Height="24"</Button>

This one injects the section-mark (also called the "signum sectionis" if you want to sound like a jerk). All the key-buttons use this same KeyPressedCommand, with the command parameter carrying the character to inject. Most of the formatting is set in a Style, but this one overrides the Margin, Width and Height to fine-tune the appearance.

Right now, looking at this XAML in LiveWriter, the button Content shows up with the syntax for the hexadecimal 00A7 value, with the ampersand, pound, x prefix and the semicolon suffix. But at the same time, the published version I’m looking at online in FireFox, shows the actual signum sectionis instead. Hmm..

I use a style for the key-buttons so that I don’t have to type in a ton of markup..

<Style TargetType="{x:Type Button}">
    <Setter Property="Width" Value="19"/>
    <Setter Property="Height" Value="23"/>
    <Setter Property="FontSize" Value="18"/>
    <Setter Property="Padding" Value="0"/>
    <Setter Property="FontFamily" Value="Bitstream Cyberbase, Roman" />
    <Setter Property="Margin" Value="1,0,2,1"/>
    <Setter Property="Effect">
        <Setter.Value>
            <DropShadowEffect Direction="315" Opacity="0.7"/>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsPressed" Value="True">
            <Setter Property="Foreground" Value="{StaticResource brushBlue}"/>
            <!– Shift the button downward and to the right slightly,
                to give the affect of being pushed inward. –>
            <Setter Property="Margin" Value="2,1,0,0"/>
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect Direction="135" Opacity="0.5" ShadowDepth="2"/>
                </Setter.Value>
            </Setter>
        </Trigger>
    </Style.Triggers>
</Style>

This Style targets all Buttons, thus it becomes the default for all Buttons within that container.

I had to tinker with and fine-tune the sizes, padding and margins to get it to look it’s best.

Note the FontFamily: "Bitstream Cyberbase, Roman". I purchased that due to it’s codepoint-coverage.

The most interesting thing (well, to me anyway) in this Style is making the key-buttons visually float off the surface a bit, and then seem to push downward (and to the right slightly) when you click on them, to yield that cute 3D affect. As you can see from reading the XAML, the trigger fires when the IsPressed property becomes true. It reacts by setting the margin property to shift it’s position down and right, and changes the DropShadowEffect to get that 3D affect.

Here’re how the main key-buttons are defined..

<Button Name="btnVK_OEM_3" Grid.Column="0" Content="{Binding Path=VK_OEM_3.Text}" 
    ToolTip="{Binding Path=VK_OEM_3.ToolTip}"/>

<Button Name="btnVK_1" Grid.Column="1" Content="{Binding Path=VK_1.Text}" 
    ToolTip="{Binding Path=VK_1.ToolTip}"></Button>

<Button Name="btnVK_2" Grid.Column="2" Content="{Binding Path=VK_2.Text}" 
    ToolTip="{Binding Path=VK_2.ToolTip}"></Button>

<Button Name="btnVK_3" Grid.Column="3" Content="{Binding Path=VK_3.Text}" 
    ToolTip="{Binding Path=VK_3.ToolTip}"></Button>

<Button Name="btnVK_4" Grid.Column="4" Content="{Binding Path=VK_4.Text}" 
    ToolTip="{Binding Path=VK_4.ToolTip}"></Button>

The DataContext of this Window is the KeyboardViewModel. As you can see, the Content of these Buttons is set to the Text property of the VK_1, VK_2, etc. members of KeyboardViewModel. Each of those keybuttons implement INotifyPropertyChanged to keep the GUI apprised of their values when they change. The base class BaseViewModel implements that for us.

The ToolTip property is also data-bound to the view-model. Each of those KeyViewModel objects (ie, VK_1, etc) also has a ToolTip property.

A slight complication for me was that that Text property, and the ToolTip, needed to yield different values depending upon whether the shift key was in effect.

C#
 1. public string ToolTip
 2. {
 3.     get
 4.     {
 5.         if ((s_domain.IsShiftLock || (_isLetter && s_domain.IsCapsLock)) &&
                !String.IsNullOrEmpty(_shiftedToolTip))
 6.         {
 7.             return _shiftedToolTip;
 8.         }
 9.         else
10.         {
11.             return _toolTip;
12.         }
13.     }
14. }

Here, s_domain is a static variable that refers to our KeyboardViewModel. This selects between the unshifted, and the shifted, ToolTip value.

The Text property acts similarly, except that it selects from the key’s _text instance variable if that was explicitly set, otherwise it returns the unshifted or shifted codepoint that was assigned to that key.

Showing the VK

This merited a bit of tinkering. Probably there’s a better way to do this, but it’s what I was able to get to work. If you know of a better way, please share.

C#
 1. public static void ShowOrAttachTo(IVirtualKeyboardInjectable targetWindow,
        ref VirtualKeyboard myPointerToIt)
 2. {
 3.     try
 4.     {
 5.         s_desiredTargetWindow = targetWindow;
 6.         // Evidently, for modal Windows I can't share user-focus with another
            // Window unless I first close and then recreate it.
 7.         // A shame. Seems like a waste of time. But I don't know of a
            // work-around to it (yet).
 8.         if (IsUp)
 9.         {
10.             Console.WriteLine("VirtualKeyboard: re-attaching to a different Window.");
11.             VirtualKeyboard.The.Closed += new EventHandler(OnTheKeyboardClosed);
12.             VirtualKeyboard.The.Close();
13.             myPointerToIt = null;
14.         }
15.         else
16.         {
17.             myPointerToIt = ShowIt(targetWindow);
18.         }
19.     }
20.     catch (Exception x)
21.     {
22.         Console.WriteLine("Exception in VirtualKeyboard.ShowOrAttachTo: " + x.Message);
23.         // Below, is what I normally use as my standard for raising
            // objections within library routines (using my own std MessageBox substitute).
24.         //IInterlocution inter = Application.Current as IInterlocution;
25.         //if (inter != null)
26.         //{
27.         //    inter.NotifyUserOfError("Well, now this is embarrassing.",
            //    "in VirtualKeyboard.ShowOrAttachTo.", x);
28.         //}
29.     }
30. }

Yeah, it’s not the trivial call to ShowDialog that we’d expect, is it?

The complication is: if it was already showing, but owned by some other window, and this window (the one you’re actually trying to get to use the VK now) was launched modally, it can’t just "take ownership" of the VK Window. The only thing I could get to work, was to shut the VK down and then re-launch it.

Thus, here we tell the VK to close itself, and hook into the Closed event so that another method gets called after the VK closes. That other method, in turn, re-launches the VK.

A bit of usability testing revealed that your users, in attempting to enter their stuff using the mouse, preferred a shift-key that would reset itself. So, clicking on either of the shift keys pushes the VK into the shifted state, and then clicking on any character pops it back into the un-shifted state. But if the user delays, it resets itself after ten seconds. Here’s what does that..

C#
 1. public void PutIntoShiftState()
 2. {
 3.     // Toggle the shift-lock state.
 4.     _domain.IsShiftLock = !_domain.IsShiftLock;
 5.     // If we're turning Shiftlock on, give that a 10-second timeout before
        // it resets by itself.
 6.     if (_domain.IsShiftLock)
 7.     {
 8.         ClearTimer();
 9.         _resetTimer = new DispatcherTimer(TimeSpan.FromSeconds(10),
10.                                           DispatcherPriority.ApplicationIdle,
11.                                           new EventHandler(OnResetTimerTick),
12.                                           this.Dispatcher);
13.     }
14.     SetFocusOnTarget();
15. }

Note that everytime your user clicks on anything within the VK, the VK’s Window gets focus. Which is not what we want. Thus we follow that with a call to SetFocusOnTarget, which tosses focus back to your text field.

Doing the Actual Key Character Injection

Here is the method that actually inserts the character into your target text-field..

 1. protected void Inject(string sWhat)
 2. {
 3.     if (TargetWindow != null)
 4.     {
 5.         ((Window)TargetWindow).Focus();
 6.         TextBox txtTarget = TargetWindow.ControlToInjectInto as TextBox;
 7.         if (txtTarget != null)
 8.         {
 9.             txtTarget.InsertText(sWhat);
10.         }
11.         else
12.         {
13.             RichTextBox richTextBox =
                    TargetWindow.ControlToInjectInto as RichTextBox;
14.             if (richTextBox != null)
15.             {
16.                 richTextBox.InsertText(sWhat);
17.             }
18.             else // let's hope it's an IInjectableControl
19.             {
20.                 IInjectableControl targetControl =
                        TargetWindow.ControlToInjectInto as IInjectableControl;
21.                 if (targetControl != null)
22.                 {
23.                     targetControl.InsertText(sWhat);
24.                 }
25.             }
26.             //else
// if you have other text-entry controls such as a rich-text box, include them here.

This part can merit a bit of thought. I have provided for two possible controls: a TextBox, and a RichTextBox.

I call your method ControlToInjectInto, to get a reference to what to put the character into. I try to find what it is by casting it to first one type and then another.

For either of these, I defined an extension method InsertText, to do the actual text insertion. Which was surprisingly non-trivial.

In an effort to accommodate you custom-text-box creators, I also defined an interface IInjectableControl. If you have a text field that is neither a TextBox nor a RichTextBox, if you can make your control implement this interface, it’ll still work. Otherwise, you’re going to have to modify this code to make it work for you. Well, that’s the great thing about getting a project complete with source-code. You’ll need to code for your control here, and also in the method DoBackSpace – which btw uses the built-in editing command EditingCommands.Backspace to actually do the BACKSPACE. It was actually simpler to just manipulate the text directly. But I wanted to play with the commanding approach. So I add a command-binding to this control at this point, use that to execute the Backspace operation, and leave that command-binding in place until the keyboard closes at which time we clear it.

The Extension Methods

Here’s the InsertText extension method for TextBox, which you’ll find in JLib.WPFExtensions.cs

C#
 1. public static void InsertText(this System.Windows.Controls.TextBox textbox,
     string sTextToInsert)
 2. {
 3.     int iCaretIndex = textbox.CaretIndex;
 4.     int iOriginalSelectionLength = textbox.SelectionLength;
 5.     string sOriginalContent = textbox.Text;
 6.     textbox.SelectedText = sTextToInsert;
 7.     if (iOriginalSelectionLength > 0)
 8.     {
 9.         textbox.SelectionLength = 0;
10.     }
11.     textbox.CaretIndex = iCaretIndex + 1;
12. }

Yeah, looks a bit verbose.

Here’s the InsertText extension method for RichTextBox:

C#
 1. public static void InsertText(this System.Windows.Controls.RichTextBox richTextBox,
    string sTextToInsert)
 2. {
 3.     if (!String.IsNullOrEmpty(sTextToInsert))
 4.     {
 5.         richTextBox.BeginChange();
 6.         if (richTextBox.Selection.Text != string.Empty)
 7.         {
 8.             richTextBox.Selection.Text = string.Empty;
 9.         }
10.         TextPointer tp = richTextBox.CaretPosition.GetPositionAtOffset(0,
                LogicalDirection.Forward);
11.         richTextBox.CaretPosition.InsertTextInRun(sTextToInsert);
12.         richTextBox.CaretPosition = tp;
13.         richTextBox.EndChange();
14.         Keyboard.Focus(richTextBox);
15.     }

This took a bit of tinkering, just to get to insert a simple character. It’s not as simple as simply appending to the text: if the caret is not at the end, you have to insert at the caret and slide everything to the right (speaking in terms of array-indices of course).

The project-code includes a library that I commonly use for WPF desktop apps, JhLib. Their locations on my system were thusly:
C:\DesktopAppsVS2010\JhVirtualKeyboard
C:\DesktopAppsVS2010\Libs\JhLib

This knowledge should help you to properly manage the project layout on your disk. 

Conclusion 

So, there you have it – a working screen-based virtual keyboard created using C# and WPF. I hope this is useful for you, and that you’ll give me your thoughts and suggestions. I find WPF fun to work with: it feels so natural now that I dislike using anything else for a desktop GUI application. But there’s always new bits to learn.

sincerely thine,

James Witt Hurst

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer (Senior)
United States United States
Professional, contract software developer. I help with projects, give training seminars and do on-site project consultations. BTW I'm available for short & long-term contracts.

San Jose, CA

Comments and Discussions

 
QuestionMy vote 5 Pin
Nitin S27-Nov-12 17:36
professionalNitin S27-Nov-12 17:36 
AnswerRe: My vote 5 Pin
JamesHurst22-Jan-13 8:14
JamesHurst22-Jan-13 8:14 
GeneralMy vote of 5 Pin
Eirik H2-Sep-12 22:07
Eirik H2-Sep-12 22:07 
GeneralRe: My vote of 5 Pin
JamesHurst28-Oct-13 15:38
JamesHurst28-Oct-13 15:38 
QuestionHow to change the default startup location (and subsequent restart locations)? Pin
T Ford7-Aug-12 6:13
T Ford7-Aug-12 6:13 
GeneralRe: How to change the default startup location (and subsequent restart locations)? Pin
T Ford7-Aug-12 22:33
T Ford7-Aug-12 22:33 
GeneralRe: How to change the default startup location (and subsequent restart locations)? Pin
JamesHurst9-Aug-12 9:31
JamesHurst9-Aug-12 9:31 
QuestionUserControl Pin
Member 869768711-Apr-12 2:33
Member 869768711-Apr-12 2:33 
Hi, very nice & thanks

I'tried to transform your code to a UserControl

I deleted the methods or events :
OnLayoutUpdated
OnLocationChanged
Closing
Closed

I moved the code of the OnContentRendered.
I dynamically instanciate the virtualkeyboard usercontrol in a grid

I don't understand : the keys aren't accessible - do you have any suggestion ?
AnswerRe: UserControl Pin
Eirik H2-Sep-12 23:52
Eirik H2-Sep-12 23:52 
AnswerRe: UserControl Pin
JamesHurst10-Sep-12 8:56
JamesHurst10-Sep-12 8:56 
GeneralRe: UserControl Pin
earthit10-Apr-13 23:08
earthit10-Apr-13 23:08 
GeneralRe: UserControl Pin
JamesHurst11-Apr-13 1:06
JamesHurst11-Apr-13 1:06 
Generalis it possible to change the font of the keys? not the functional shift space backspace etc the other ones? Pin
nipsonanomimata11-Feb-12 16:01
nipsonanomimata11-Feb-12 16:01 
GeneralRe: is it possible to change the font of the keys? not the functional shift space backspace etc the other ones? Pin
JamesHurst13-Feb-12 15:00
JamesHurst13-Feb-12 15:00 
GeneralRe: is it possible to change the font of the keys? not the functional shift space backspace etc the other ones? Pin
JamesHurst13-Feb-12 15:26
JamesHurst13-Feb-12 15:26 
QuestionVirtual Keyboard Pin
Iris Li14-Jan-12 14:55
Iris Li14-Jan-12 14:55 
AnswerRe: Virtual Keyboard Pin
JamesHurst13-Feb-12 15:30
JamesHurst13-Feb-12 15:30 
QuestionVS 2008 Pin
ghwilson20026-Nov-11 21:11
ghwilson20026-Nov-11 21:11 
AnswerRe: VS 2008 Pin
JamesHurst10-Sep-12 9:01
JamesHurst10-Sep-12 9:01 
GeneralMy vote of 5 Pin
Filip D'haene31-Oct-11 10:05
Filip D'haene31-Oct-11 10:05 
GeneralRe: My vote of 5 Pin
JamesHurst13-Feb-12 15:31
JamesHurst13-Feb-12 15:31 
QuestionVirtualKeyboard Pin
Sesharaman9-Sep-11 7:46
Sesharaman9-Sep-11 7:46 
AnswerRe: VirtualKeyboard Pin
JamesHurst9-Sep-11 10:23
JamesHurst9-Sep-11 10:23 
GeneralRe: VirtualKeyboard [modified] Pin
Sesharaman9-Sep-11 18:48
Sesharaman9-Sep-11 18:48 
GeneralRe: VirtualKeyboard Pin
Richard MacCutchan10-Sep-11 1:14
mveRichard MacCutchan10-Sep-11 1:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.