Click here to Skip to main content
13,248,107 members (35,431 online)
Click here to Skip to main content
Add your own
alternative version

Stats

6.8K views
431 downloads
8 bookmarked
Posted 26 Feb 2016

WPF Embedded Touch Keyboard

, 26 Feb 2016
Rate this:
Please Sign up or sign in to vote.
There are cases where it might be desirable to support character input without a keyboard attached in a more embedded application where the keyboard is displayed as part of the window.

Introduction

I have been working on a project to build an application on a Microsoft Windows system that is dedicated to the application. The touch screen is rather small and the entire surface is dedicated to the application. Therefore, there is really only need for one window. There is a physical keyboard, but it would be desirable to not require the keyboard, or allow the user to just use the screen to input data, like on a smart phone. To expedite maximizing the effective use of the screen, especially since the screen should be easily read from a distance, using a design that goes to a special screen for editing. This allows the screen real estate to be optimized for reading for a distance, and then using a different screen design optimized to allow editing of values with a visible on-screen keyboard.

The design I was working with used a separate window for the touch screen, and all the solutions I found on the web also used a separate keyboard window. This is really no better than using the default touch screen keyboard provided by Microsoft, which is actually not really that good since it still does not really support special symbols well.

I ended up using one of the solutions I found online, in combination with the one currently in the solution. I still heavily modified the code since it still needed to be put into a control in the same window as the controls for the values being edited. There were a lot of things I did not like about the code, and it seemed to make the coding harder than if I rewrote it. Ended up very much sampling the code-behind for the keyboard (which is derived from the Grid with no associated XAML), but left the code-behind for the keys (derived from the Button) and the keyboard sections (again derived from the Grid) alone. This includes a separate set of classes to differentiate the key functionality depending on key type. This was a set of classes that inherited from several different classes to differentiate the functionality of the normal key, the single use modifier key such as the shift key, and the toggle modifier key such as caps lock key. There were also some other derived classes to handle other specialized functionality.

I wanted to have the styles for the keys to use a defined style, so, to minimize change, had a separate style for the normal key and the key down (for the Shift and Caps Lock keys).

This worked quite well, but really I was not at all happy with the design for the key functionality since it seemed to be overly complex, and did not really take advantage of a good state pattern since there was a lot of state dependent functionality implemented in the keyboard class, and thought that the use of a separate class to differentiate button types and wanted to use the Key Button as the base class, and derive from that class to create the custom functionality. I also wanted to use the ToggleButton for the base class instead of the Button so that only one Style was required.

The Keyboard Class

The heart of the design is the creation of the keys:

public override void BeginInit()
{
 SetValue(FocusManager.IsFocusScopeProperty, true);

 var mainSection = new OnScreenKeyboardSection();
 var mainKeys = new ObservableCollection<OnScreenKey>
 {
  new OnScreenKeyNormal(0, 00, new[] {"`", "~"}, KeyTextUpdateDelegateFunctions.CapsAndSpecial),
      .
      .
      .
  new OnScreenKeySpecial(4, 02,  string.Empty, " "){GridWidth = new GridLength(5, GridUnitType.Star)},
 };

 mainSection.Keys = mainKeys;
 mainSection.SetValue(ColumnProperty, 0);
 _sections.Add(mainSection);
 ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) });
 Children.Add(mainSection);
 _allOnScreenKeys.AddRange(mainSection.Keys);
 Loaded += OnScreenKeyboard_Loaded;
 base.BeginInit();
}

The abstract class for all the keys in the OnScreenKey, from which both the OnScreenKeyNormal and the OnScreemKeySpecial, are derived, and the OnScreenKey is derived from a ToggleButton. As each key is created, it is assigned to the Grid.Column and a Grid.Row. The keyboard (OnScreenKeyboard), which is derived from the Grid, contains one or more of another class derived from the Grid, the OnScreenKeyboardSection. The keys are added to an ObservableCollection because the OnScreenKeyboardSection Keys collection needs the CollectionChanged event to allow monitor changes in the collection if keys are ever changed dynamically. Once all the keys are added to the OnScreenKeyboardSection, the Grid.Column for the section is assigned, a ColumnDefinition in the OnScreenKeyboard is added for the Column, and the OnScreenKeyboardSection is added to the Children of the OnScreenKeyboard. Then, all the keys are added to the collection of all keys so that all keys can be notified when there is a change that will affect the state of the keys, such as when the Shift key is pressed.

After all the OnScreenKeyboardSection instances have been added to the OnScreenKeyboard, there is a subscription made to Loaded event:

private void OnScreenKeyboard_Loaded(object sender, RoutedEventArgs e)
{
 _allOnScreenKeys.ForEach(x =>
 {
  x.Style = ToggleButtonStyle;
  x.OnScreenKeyPressEvent += OnScreenKeyPressEventHandler;
 });
}

The Loaded event handler is responsible for setting the ToggleButton Style to all of the OnScreenKey instances (cannot do it earlier because the Style is not available until the Loaded event). It also subscribes to the OnScreenKeyPressEvent of all the keys to allow the effect of the key press to be executed:

void OnScreenKeyPressEventHandler(object sender, OnScreenKeyPressEventArgs e)
{
 if (e.StateModifier == null)
 {
  e.Execute(_activeControl);
  var singleInstance = _activeKeyModifiers.Where(i => i.SingleInstance).Select(k => k).ToList();
  singleInstance.ForEach(j => _activeKeyModifiers.Remove(j));
 }
 else
 {
  var dups = _activeKeyModifiers.Where(i => i.ModifierType == e.StateModifier.ModifierType)
          .Select(k => k).ToList();
  dups.ForEach(j => _activeKeyModifiers.Remove(j));
  if (e.StateModifier.Clear == false)
   _activeKeyModifiers.Add(e.StateModifier);
 }
 _allOnScreenKeys.ForEach(i => i.Update(_activeKeyModifiers));
}

There are basically two cases that need to be handled on the KeyPress, when a modifier key is pressed and a normal key press.

If it is a normal key press, then need to tell the OnScreenKey that was activated to execute, passing the currently selected control to the OnScreenKey Execute method. Then check to see if any single instance modifiers exist, and clear any out of the modifiers that do.

If a modifier key is pressed, need to check to see if that modifier type is already in the the modifiers, and removing them if they are. Then add the new modifier to the collection of modifiers.

Finally, for both cases, call the Update method on all OnScreenKey instances with the current collection of key modifiers. This allows each key to change its caption in responce to the set of modifiers, and any modifiers OnScreenKey instances whose state is affected by the current set of modifiers to change to the appropriate toggle state.

The class used to pass the argument when a key is pressed is as follows:

internal class OnScreenKeyPressEventArgs
 {
  public delegate void ExecuteKeyPress(FrameworkElement frameworkElement);

  public OnScreenKeyStateModifier StateModifier { get; }

  internal OnScreenKeyPressEventArgs(OnScreenKeyStateModifier stateModifier)
  {
   StateModifier = stateModifier;
  }

  public OnScreenKeyPressEventArgs(ExecuteKeyPress execute)
  {
   Execute = execute;
  }

  public ExecuteKeyPress Execute { get; }
 }

I have found that I quite often regret not creating a specific class for the argument when I create an event since retrofitting is somewhat painful, so generally try to create an associated argument class for the event. Since I was using a class for the arguments anyway, I included the Execute method in the OnScreenKeyPressEventArgs class instead of having to casting the class to use the Execute method. I also used it to pass the key modifier. As can be seen, there was either an Execute method or a StateModifier passed in the event argument.

The Key Class

All of the keys are based on an abstract class called OnScreenKey that is based on the ToggleButton class. The constructor takes arguments to specify the Grid.Row, Grid.Column, an array of values that will be display as the caption for the key, and, optionally, the function to execute when the Execute method is called. The constructor sets these values, including the current string to display on the key cap, and subscribes to the Click event:

protected OnScreenKey(int row, int column, string[] values,
  Func<IEnumerable<OnScreenKeyStateModifier>, int> valueIndexFunction = null,
  ExecuteDelegate executeFunction = null)
{
  _values = values;
  _valueIndexFunction = valueIndexFunction ?? KeyTextUpdateDelegateFunctions.FirstInArray;
  _executeFunction = executeFunction ?? ExecuteDelegateFunctions.DefaultExecuteDelegate;
  Content = Value = values[0];
  GridRow = row;
  GridColumn = column;
  Click += KeyPressEventHandler;
}

The GridRow and GridColumn properties just bind to the Grid.RowProperty and Grid.ColumnProperty using the GetValue and SetValue methods. The KeyPressEventHandler is an abstract method. There is also a GridWidth property that is used by the OnScreenKeyboardSection for the relative key width.  

The public Execute method is called with the currently selected FrameworkElement by the OnScreenKeyboard class after it has received the initiation of the key Click event, and executes the ExecuteDelegateFunction in passed into the constructor, or the default ExecuteFunction if the value is null. This ExecuteFunction usually does the execution of the key press such as simulating a KeyPress on a TextBox.

public delegate void ExecuteDelegate(OnScreenKey button, FrameworkElement frameworkElement);

An example of the ExecuteDelegate, and also the default ExecuteDelegate is the method to handle an ordinary key press of a key that represents a character:

public static ExecuteDelegate DefaultExecuteDelegate = (key, frameworkElement) =>
{
 if (frameworkElement is TextBoxBase)
 {
  var textBoxBase = (TextBox)frameworkElement;
  var start = textBoxBase.Text.Substring(0, textBoxBase.SelectionStart);
  var end = textBoxBase.Text.Substring(textBoxBase.SelectionStart + textBoxBase.SelectionLength);
  textBoxBase.Text = start + key.Value + end;
  textBoxBase.SelectionStart = start.Length + 1;
  textBoxBase.SelectionLength = 0;
 }
 else if (frameworkElement is PasswordBox)
 {
  var passwordBoxBase = (PasswordBox)frameworkElement;
  passwordBoxBase.Password = passwordBoxBase.Password + key.Value;
 }
};

This method has a reference to the OnScreenKey and the FrameworkElement that currently has keyboard focus. The method is intended to support a key that, when pressed, is supposed to represent the press of a single character key such as the "a" key. The method handles two different types of FrameworkElements: the TextBoxBase and the PasswordBox. For the TextBoxBase, the Value, which would be the character that is currently displayed as the ToggleButton content, either inserted in the current SelectionStart location if the SelectionLength is zero, or replaces the currrent selection with the character. For a PasswordBox, only append is supported since the PasswordBox does not have the SelectionStart or SelectionLength properties.

The DefaultExecuteDelegate is used for most keys, but there are a lot of keys that require special one-time functionality. Examples of these keys would be the backspace, tab, delete, return, etc.  I could have created an inherited class for each of these cases but that would mean that there were a lot of derived classes with only minor differences.

Another important method is the Update method. It is called on all keys after a key press, unlike the Execute method which in only called on the instance that initiated the key press event. It updated the modifiers, and then updates any states, such as displaying the right caption for the keys that have been activated such as the Shift and Caps Lock keys, and, for modifier (Shift) and toggle keys (Caps Lock), put the ToggleButton IsChecked property in the right state.

public delegate int CaptionUpdateDelegate(IEnumerable<OnScreenKeyStateModifier> values);

An example of the CaptionUpdateDelegate, and also the default ExecuteDelegate is the method to handle an ordinary key press of a key that represents a character:

public static int CapsAndSpecial(IEnumerable<OnScreenKeyStateModifier> values)
{
 var capValue = values.Any(i => i.ModifierType == OnScreenKeyModifierType.Shift) ? 1 : 0;
 var specialValue =values.Any(i => i.ModifierType == OnScreenKeyModifierType.Special) ? 2 : 0;
 return capValue + specialValue;
}

Basically this function returns a "0" if neither a Shift nor Special ModifierType is included in the modifier values, "1" if just the Shift exists, "2" if just the Special exists, and "3" if both exist. It is possible that there are not three values, and this is handled in Update method of the abstract OnScreenKey:

protected virtual void Update()
{
 Content = Value = GetCurrentValue(_valueIndexFunction(Modifiers));
}

protected string GetCurrentValue(int index)
{
 return _values[Math.Min(index, _values.Length - 1)];
}

The reason for the update function instead of having this included in the inherited class is that the keyboard could include a NumLock functionality, which I have not included in this sample. There could also be other special cases. This is also why I had this as the default. I did not implement it in the abstract class basically to keep this class cleaner. There are two other of these methods that have been implemented: NumberOnly, and FirstInArray. FirstInArray is used if there is only one caption, no matter what the modifiers are, such as for Shift, Space, etc.

Example of an Inherited OnScreenKey Class

The following code is an example of a key class that inherits from the OnScreenKey class.

public class OnScreenKeyNormal : OnScreenKey
{
 private readonly Func<IEnumerable<OnScreenKeyStateModifier>, int> _valueIndexFunction;

 internal OnScreenKeyNormal(int row, int column, string[] values,
     Func<IEnumerable<OnScreenKeyStateModifier>, int> valueIndexFunction)
   : base(row, column, values, valueIndexFunction) { }

 internal override void KeyPressEventHandler(object sender, RoutedEventArgs routedEventArgs)
 {
  IsChecked = false;
  OnClick(new OnScreenKeyPressEventArgs( Execute));
 }
}

As can be seen, about the only thing this class really does is set the IsChecked property to false to keep the ToggleButton from going to this state since it is only applicable for modifier keys and do not want to have the button ever to be in the depressed state.

Modifier Type Key Class

The class used for a single use modifier key such as the Shift key is as follows:

public class OnScreenKeyModifier : OnScreenKey
{
 private readonly OnScreenKeyModifierType _modifierType;
 public OnScreenKeyModifier(int row, int column, string[] values, OnScreenKeyModifierType modifierType) : base(row, column, values)
 {
  _modifierType = modifierType;
 }

 internal override void KeyPressEventHandler(object sender, RoutedEventArgs routedEventArgs)
 {
  OnClick(new OnScreenKeyPressEventArgs(new OnScreenKeyStateModifier(_modifierType, true,
             IsChecked == false)));
 }

 protected override void Update()
 {
  IsChecked = Modifiers.Any(i => i.ModifierType == _modifierType);
 }
}

In this class need to maintain the ModifierType so that the can be communicated to the OnScreenKeyboard class when a Click event occurs, and also to know when to reset. Both the Update and KeyPressEventHandler are overridden to accomplish this. In the KeyPressEventHandler, the OnScreenKeyStateModifier is passed in the OnScreenKeyPressEventArgs. The triggering of the Update cause the IsChecked state to be set if any of the ModifierTypes in the Modifiers is of the type of the saved _modifierType.

The colleciton of OnScreenKeyStateModifier instances contain information on the current state of the modifier keys. This class is also used to communicate to the OnScreenKeyboard class when a modifier key has been activated or deactivated, the distinction being specified by Clear property. The OnScreenKeyStateModifier contains three properties: the type of the modifier (shift, num lock, special), whether is is for one character press or not (SingleInstance p;roperty) and whether this modifier should be cleared (Clear property) from the collection or added to the collection.

public class OnScreenKeyStateModifier
{
 public OnScreenKeyStateModifier(OnScreenKeyModifierType modifierType, bool singleInstance, bool clear)
 {
  Clear = clear;
  ModifierType = modifierType;
  SingleInstance = singleInstance;
 }

 public OnScreenKeyModifierType ModifierType { get; }
 public bool SingleInstance { get; }
 public bool Clear { get; }
}

The big difference between the single instance class above and the toggle class is that the Update method checks if the SingleInstance property of the OnScreenKeyStateModifier before setting the IsChecked value to true. If the SingleInstance is set then the toggle class should not set its IsChecked property to true.

Support of ICommand

There is one hidden tidbit in the OnScreenKey abstract class and that is that it has a property that allows the key to be bound to an ICommand interface:

public string ClickCommand
{
 get { return _clickCommand; }
 set
 {
  _clickCommand = value;
  SetBinding(Button.CommandProperty, new Binding(_clickCommand)
  { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor,
          typeof(OnScreenKeyboard), 1) });
 }
}
private string _clickCommand;

The property has to be set to the name of the DependencyProperty for the command that is specified OnScreenKeyboard code, and the propety has to be bound to a property in the XAML.

Binding has to be used for this since that is the only way to get the CanExecute to affect whether the key is enabled. There may be cases where the functionality is disabled, and it would be a lot more complex to add another property to enable the button.

Of course to make this work the OnScreenKeyboard class has to include a DependencyProperty for this ICommand and the command has to have a Binding assigned in the XAML.

Since the ClickCommand property is in the abstract OnScreenKey class, any key can have a command associated with it.

Using the Code

Below is the sample XAML for using the keyboard:

<keyboard:OnScreenKeyboard Height="300"
                           VerticalAlignment="Bottom"
                           ToggleButtonStyle="{StaticResource DefaultTouchToggleButtonStyle}"
                           ActiveContainer="{Binding ElementName=MainGrid}"
                           CancelCommand="{Binding EditorViewModel.CancelCommand}"
                           SaveCommand="{Binding EditorViewModel.SaveCommand}" />

The ToggleButtonStyle is the Style to be used for the keys, which are implemented using ToggleButtons. The most important binding is the ActiveContainer, which tells the keyboard which container to monitor for the GotKeyboardFocus event. If the UIElement that gets focus is a TextBoxBase or a PasswordBox, then the keyboard can be functional.

 

The Sample

Basic image of keyboard

Basic keyboard with modifier keys selected

MessageBox displayed from ViewModel when Save button pressed

Keyboard with Speical key pressed.

History

  • 02/26/2016: Initial version with minimal implementation detail
  • 03/03/2016: Cleanup
  • 03/04/2016: New code with ICommand keys implemented
  • 03/05/2016: Code updates
  • 03/09/2016: Updates and cleanup
  • 03/11/2016: Minor code cleanup

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Clifford Nelson
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

You may also be interested in...

Comments and Discussions

 
QuestionOnScreenKeyboardControl.zip appears to be missing on our servers Pin
Benjamin Krause11-Mar-16 21:49
memberBenjamin Krause11-Mar-16 21:49 
AnswerRe: OnScreenKeyboardControl.zip appears to be missing on our servers Pin
Clifford Nelson12-Mar-16 5:41
memberClifford Nelson12-Mar-16 5:41 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.171114.1 | Last Updated 26 Feb 2016
Article Copyright 2016 by Clifford Nelson
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid