Universal Windows Platform – Keyboard App

Keyboard App demonstrates how to create an on-screen Keyboard application using Button and StackPanel Controls

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in a Name and select a Location and then select Ok to create the Project
vs2017-new-project-window

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10 Fall Creators Update (10.0; Build 16299) and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-library

Step 7

Once in the Code View for Library.cs the following should be entered:

using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

public enum Modes
{
    Character, Backspace, Tab, Enter, Shift, Space
}

public class Item
{
    public Modes Mode { get; set; }
    public string Content { get; set; }
    public string Normal { get; set; }
    public string Shift { get; set; }
    public string Value { get; set; }
}

public class Library
{
    private readonly string[][][] keys =
    {
        new string[][]
        {
            new string[] { "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=" },
            new string[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]" },
            new string[] { "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "#" },
            new string[] { "\\", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/" }
        },
        new string[][]
        {
            new string[] { "¬", "!", "\"", "£", "$", "%", "^", "&", "*", "(", ")", "_", "+" },
            new string[] { "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}" },
            new string[] { "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "@", "~" },
            new string[] { "|", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?" }
        },
    };

    private enum Chords { normal, shift };
    private enum Rows { top, upper, middle, lower, bottom };

    private Chords _chord = Chords.normal;

    public delegate void PressedEvent(Item item);
    public event PressedEvent Pressed;

    public static IEnumerable<T> GetChildren<T>(DependencyObject depObj) where T : DependencyObject
    {
        if (depObj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                if (child != null && child is T)
                {
                    yield return (T)child;
                }
                foreach (T childOfChild in GetChildren<T>(child))
                {
                    yield return childOfChild;
                }
            }
        }
    }

    private Button Add(Item item, int width, int height)
    {
        Button button = new Button()
        {
            Height = height,
            Width = width,
            FontSize = 10,
            Padding = new Thickness(0),
            Margin = new Thickness(0.5),
            Content = item.Content,
            Tag = item,
        };
        button.Click += (object sender, RoutedEventArgs e) =>
        {
            button = (Button)sender;
            Pressed?.Invoke((Item)button.Tag);
        };
        return button;
    }

    private void Row(StackPanel panel, string[] normal, string[] shift)
    {
        for (int i = 0; i < normal.Length; i++)
        {
            Item item = new Item()
            {
                Mode = Modes.Character,
                Normal = normal[i],
                Shift = shift[i],
                Value = (_chord == Chords.shift) ? shift[i] : normal[i],
                Content = (_chord == Chords.shift) ? shift[i] : normal[i],
            };
            panel.Children.Add(Add(item, 24, 24));
        }
    }

    private void Layout(ref StackPanel panel)
    {
        // Top
        StackPanel top = new StackPanel()
        {
            Orientation = Orientation.Horizontal,
            BorderThickness = new Thickness(1, 0, 1, 0)
        };
        Row(top, keys[(int)Chords.normal][(int)Rows.top],
            keys[(int)Chords.shift][(int)Rows.top]);
        top.Children.Add(Add(new Item()
        {
            Mode = Modes.Backspace,
            Content = "BkSp",
        }, 42, 24));
        panel.Children.Add(top);
        // Upper
        StackPanel upper = new StackPanel()
        {
            Orientation = Orientation.Horizontal,
            BorderThickness = new Thickness(1, 0, 1, 0)
        };
        upper.Children.Add(Add(new Item()
        {
            Mode = Modes.Tab,
            Content = "↹",
        }, 34, 24));
        Row(upper, keys[(int)Chords.normal][(int)Rows.upper],
            keys[(int)Chords.shift][(int)Rows.upper]);
        upper.Children.Add(Add(new Item()
        {
            Mode = Modes.Enter,
            Content = "↵",
        }, 32, 24));
        panel.Children.Add(upper);
        // Middle
        StackPanel middle = new StackPanel()
        {
            Orientation = Orientation.Horizontal,
            BorderThickness = new Thickness(1, 0, 1, 0)
        };
        middle.Children.Add(new Canvas()
        {
            Width = 42,
            Height = 24
        });
        Row(middle, keys[(int)Chords.normal][(int)Rows.middle],
                    keys[(int)Chords.shift][(int)Rows.middle]);
        panel.Children.Add(middle);
        // Lower
        StackPanel lower = new StackPanel()
        {
            Orientation = Orientation.Horizontal,
            BorderThickness = new Thickness(1, 0, 1, 0)
        };
        lower.Children.Add(new Canvas()
        {
            Width = 50,
            Height = 24
        });
        Row(lower, keys[(int)Chords.normal][(int)Rows.lower],
                    keys[(int)Chords.shift][(int)Rows.lower]);
        panel.Children.Add(lower);
        // Bottom
        StackPanel bottom = new StackPanel()
        {
            Orientation = Orientation.Horizontal,
            BorderThickness = new Thickness(1, 0, 1, 0)
        };
        bottom.Children.Add(new Canvas()
        {
            Width = 60,
            Height = 24
        });
        bottom.Children.Add(Add(new Item()
        {
            Mode = Modes.Shift,
            Content = "Shift",
        }, 40, 24));
        bottom.Children.Add(Add(new Item()
        {
            Mode = Modes.Space,
            Content = "Space",
        }, 170, 24));
        bottom.Children.Add(Add(new Item()
        {
            Mode = Modes.Shift,
            Content = "Shift",
        }, 40, 24));
        panel.Children.Add(bottom);
    }

    private void Update(StackPanel input)
    {
        IEnumerable<Button> buttons = GetChildren<Button>(input);
        foreach (Button button in buttons)
        {
            if (button.Tag != null &&
                button.Tag.GetType() == typeof(Item))
            {
                Item item = (Item)button.Tag;
                if (item.Mode == Modes.Character)
                {
                    item.Value = (_chord == Chords.shift) ? item.Shift : item.Normal;
                    item.Content = item.Value;
                    button.Content = item.Content;
                    button.Tag = item;
                }
            }
        }
    }

    public void Init(ref StackPanel panel)
    {
        Layout(ref panel);
    }

    public void Output(TextBox display, StackPanel input, Item item)
    {
        string value = string.Empty;
        switch (item.Mode)
        {
            case Modes.Backspace:
                int start = display.SelectionStart == 0 ?
                    display.Text.Length + 1 : display.SelectionStart;
                display.Select(start - 1, 1);
                display.SelectedText = string.Empty;
                break;
            case Modes.Character:
                value = (_chord == Chords.shift) ? item.Shift : item.Normal;
                break;
            case Modes.Enter:
                value = "\n";
                break;
            case Modes.Shift:
                _chord = (_chord == Chords.shift) ? Chords.normal : Chords.shift;
                Update(input);
                break;
            case Modes.Space:
                value = " ";
                break;
            case Modes.Tab:
                value = "\t";
                break;
        }
        display.Text += value;
    }
}

In the Library.cs there are using statements to include the necessary functionality. There is an enum for the different Modes of the Keyboard, there is an Item Class to represent each of the Keys of the Keyboard and there is a custom event called PressedEvent. The Library Class contains a string[][][] to represent sets of keys and their rows, plus the Chords and Rows. There is a GetChildren Helper Method, the Add Method that will along with Row make up each set of Button Controls that will make up the Layout of the Keyboard. The Layout Method will create the look-and-feel of the Application, Update will toggle the state of the Keys, Init will help initialise the look-and-feel and Output will get any inputs from the Keyboard and produce the desired results.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-library

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this between the Grid and /Grid elements, enter the following XAML:

<Grid>
	<Grid.RowDefinitions>
		<RowDefinition Height="75*"/>
		<RowDefinition Height="25*"/>
	</Grid.RowDefinitions>
	<TextBox Name="Display" Grid.Row="0" AcceptsReturn="True" TextWrapping="Wrap"
		VerticalAlignment="Stretch" PreventKeyboardDisplayOnProgrammaticFocus="True"/>
	<Viewbox Grid.Row="1">
		<StackPanel Name="Input"/>
	</Viewbox>
</Grid>

The first block of XAML the main user interface of the Application, this features a Grid with two rows, the first is a TextBox which will be used for Output and the second row is a StackPanel where the Keyboard will appear in a ViewBox.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();
 
protected override void OnNavigatedTo(NavigationEventArgs e) 
{
	library.Init(ref Input);
	library.Pressed += (Item item) =>
	{
		library.Output(Display, Input, item);
	};
}

Below the MainPage() Method an instance of the Library Class is created and in OnNavigatedTo calls the Init Method in the Library Class and sets up the Pressed Event.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

After the Application has started running you can tap any button on the Keyboard to type the selected character into the Application

ran-keyboard-app

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Creative Commons License

Leave a comment