Introduction
For a machine we were building last year we decided to use a touch
screen only user interface. This machine, with its 40 axes of motion and 500 over I/O points with all of them acting parallel,
needed a very responsive
UI that updated status information such as moving motor positions, changing digital I/O status of processes etc. without much load
on the software. So, we came
up with a comprehensive industrial UI for this machine, using WPF and C#, that has LED controls that reflected associated digital I/O status,
text controls that displayed changing numbers such as motor positions or analog I/O status, buttons that were capable of showing
status of the operations that they initiated etc. As with the
decision to use only touch screen and not keyboard or mouse for user interface came the requirement of an on screen keyboard.
Initially, we thought we would manage with the OS supplied keyboard that comes with Windows. However, we were proven wrong
by the unusability of it to the task simply dues to the lack of control that we could exercise over it using our application as
well as the size of the keys that we quite unfriendly for industrial users that often wear gloves. So, we came up with two
controls that derive themselves from the WPF TextBox control. These derived controls, when touched, would display a popup keypad that
depending on the type of control, would be numeric keypad or alphanumeric keypad. I would like to present these controls here for
the benefit of someone that decides to use touch screen only user interface in their next design.
Background
WPF, by design, allows us to transform the look and feel and to certain extent the behavior of all the UI controls using
Styles and Templating features. Most often we do not need to use inheritance to derive a new control as we did with old MS technologies
such as MFC etc. However, WPF designers have left room for us to derive new UI controls if we really needed to. The two controls that
I am going to be talking about here are derived from the traditional TextBox control. The only difference between these two controls
are in the way they construct their keypads. There are other ways, such as deriving a single control from TextBox with a virtual method
for constructing keypad and have derived these two controls from that control and overidded the virtual method. For clarity and
for simplicity, I have derived two different control that are very much alike, except in the way they construct their keypads. That
would be another discussion, which I am not inclined to get in to here. The derived control overrides the OnMouseDown event, that is
triggered when the control is touched. In OnMouseDown
event handler code, a Popup is created in code and is displayed if one is not
already created for this control. Same event is also used to close a displayed Popup. Functionally, if one of these controls are touched
a keypad is dropped down and once the key entries are made, the control is touched again to hide the keypad.
Using the Code
For this article, the code for TouchNumericBox
control class and TouchAlphaNumericBox
control class are provided, under a name space
SiriusMicrotech.core.UI, in a single file
named AlphaNumericEditBox.cs. The styling information that we used for our machine's UI theme is provided in TouchStyle.xaml. TouchStyle.xaml
also provides styling information for the popup keypad keys. These can be modified, as they are intended, to suit individual preferences.
We used 40 pixels as the key size for the keypad keys and chose the size of the text box in such a way that the keypad, when popped,
will be bigger than the text box by 10 pixels on both sides and nicely centered. To use
these controls in your project, add these two files, AlphaNumericEditBox.cs and TouchStyle.xaml, in to your project. To use these controls
in a window, you have to first refer to the name space in your window's XAML code by placing the name space reference
in the Window properties. To access the styling information in TouchStyle.xaml, you need to place the following Window.Resources
section in your WPF
window XAML representation. Window.Resources
section is usually placed
just after the Window section of the XAML code, just above the main container panel section. In our demo, we have Grid as the main
container panel for our Window.
Following code shows how the name space reference and Window.Resources section looks in our demo code that is attached with this article.
<Window x:Class="AlphanumericEditbox.TouchScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SiriusMicrotech="clr-namespace:SiriusMicrotech.core.UI"
Title="Touch Screen Friendly Edit Box Controls Demonstration"
SizeToContent="WidthAndHeight" >
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="TouchBox\TouchStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
...
Here is the code for OnMouseDown
event handler for the TouchNumericBox
class. This is where we programmatically construct the keypad.
protected override void OnMouseDown(MouseButtonEventArgs e)
{
if (popKpd != null) {
popKpd.IsOpen ^= true; }
else
{
popKpd = new Popup();
popKpd.Placement = PlacementMode.Bottom;
popKpd.PopupAnimation = PopupAnimation.Scroll;
kpd = new Grid();
for (int i = 0; i < 4; i++)
{
kpd.ColumnDefinitions.Add(new ColumnDefinition());
kpd.RowDefinitions.Add(new RowDefinition());
}
int btn = 1;
for (int row = 2; row >= 0; row--)
{
for (int col = 0; col < 3; col++)
populateGridPos(row, col, Convert.ToString(btn++));
}
populateGridPos(3, 0, "0");
populateGridPos(3, 1, ".");
populateGridPos(3, 2, "CLR");
popKpd.Child = kpd;
popKpd.IsOpen = false;
popKpd.PlacementTarget = this;
popKpd.Placement = PlacementMode.Bottom;
popKpd.HorizontalOffset = -10;
popKpd.IsOpen = true;
}
base.OnMouseDown(e);
}
The helper function populateGridPos
is used to create a button in the given cell of the keypad grid. This function creates a button
with a fixed size of 40 pixels and assigns an event handler for the click event. The button click handler is where we update the
text in the text box. The code for populateGridPos
and button_click
are as follows.
private void populateGridPos(int row, int col, string btn)
{
var button = new Button();
button.Style = (Style)FindResource("TouchKey");
button.Content = btn;
button.Width = 40;
button.Click += new RoutedEventHandler(button_Click);
Grid.SetRow(button, row);
Grid.SetColumn(button, col);
this.kpd.Children.Add(button);
}
void button_Click(object sender, RoutedEventArgs e)
{
if (this.IsReadOnly) return;
Button btn = (Button)e.OriginalSource;
string s = btn.Content.ToString();
if (s == "CLR")
{
this.Text = "";
}
else
{
this.Text += s;
}
}
Points of Interest
Even though we derive these controls from native TextBox control in WPF, we need to specify the ScrollViewer
property, which is the
key to display and use the functionality of a text box. Mere inheritance did not bring all the TextBox control functionality here.
This took as a little bit of time to figure out. The other weird thing that took as a while to work around was a problem with
DefaultStyleKeyProperty.OverrideMetadata
call that was throwing an exception when we tried to use more than one instance of these
controls. For now, we have overcome this by surrounding this call with a try-catch block.