Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating On-Screen Keyboard Using Attached Behavior in WPF

0.00/5 (No votes)
19 Dec 2008 1  
An implementation of a numeric on-screen keyboard using attached behaviors in WPF

Introduction

This article describes an implementation of a numeric on-screen keyboard using attached properties and UserControl in WPF. It can be used in WPF applications just like a Popup control. And, you can easily modify this sample into a full-size on-screen keyboard for any language of your choice.

The source code was compiled and tested against Visual Studio 2008 SP1 with .NET 3.0 as target framework, and the demo EXE will work in either Windows XP or Windows Vista with .NET 3.0 installed.

The Design of the Code

Here are the main design goals at a glance:

  • Enable the on-screen keyboard simply by setting the attached property PopupKeyboard.IsEnabled="true" on any FrameworkElement.
  • Position the control by setting the Placement, PlacementTarget, PlacementRectangle, HorizontalOffset, and VerticalOffset properties, just like you position a Popup control.
  • Switch the keyboard states between Normal and Hidden by double-clicking the FrameworkElement where the keyboard is attached.
  • When the mouse cursor leaves the FrameworkElement, the keyboard will remember its last keyboard state so that it can be used later.

Using the Code

To use the control, you will need to include the files PopupKeyboard.xaml and PopupKeyboard.xaml.cs in your project and then add an xmlns to the window.

xmlns:local="clr-namespace:VirtualKeyboard"

After this is set, the following set of attached properties will be available to any FrameworkElement in that Window:

PopupKeyboard.Placement
PopupKeyboard.PlacementTarget
PopupKeyboard.PlacementRectangle
PopupKeyboard.HorizontalOffset
PopupKeyboard.VerticalOffset
PopupKeyboard.CustomPopupPlacementCallback
PopupKeyboard.State
PopupKeyboard.Height
PopupKeyboard.Width
PopupKeyboard.IsEnabled

You can check MSDN documentation Popup Placement Behavior on how to set up attached properties Placement, PlacementTarget, PlacementRectangle, HorizontalOffset, VerticalOffset, and CustomPopupPlacementCallback. The attached properties State, Height, and Width set the initial keyboard state (Normal or Hidden), keyboard height and width respectively. And, the last attached properties IsEnabled sets a value that indicates whether the keyboard is available.

Following is a sample on how to set these attached properties:

<TextBox
    x:Name="txtEmployeeID"
    local:PopupKeyboard.Placement="Bottom"
    local:PopupKeyboard.PlacementTarget="{Binding ElementName=txtEmployeeID}"
    local:PopupKeyboard.HorizontalOffset="20" 
    local:PopupKeyboard.Height="220"
    local:PopupKeyboard.Width="200"
    local:PopupKeyboard.IsEnabled="true"/>

Also, you can check the demo source code for more information on how to use the numeric on-screen keyboard.

How It Works

The numeric on-screen keyboard is composed mainly of two classes: PopupKeyboardUserControl and PopupKeyboard. The internal class PopupKeyboardUserControl derives from UserControl that includes XAML and all the logic for the on-screen keyboard. Class PopupKeyboard is a static class that has a reference to an object of PopupKeyboardUserControl and defines all the attached properties needed to display and position the on-screen keyboard.

Next, I will briefly describe how these two classes are actually coded:

1. Keyboard Events Simulation

In order to simulate keyboard events, I use the keybd_event function to synthesize a keystroke, and here is the syntax for the Windows API function:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern void keybd_event
	(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);

For every key Button defined in file PopupKeyboard.xaml, its Click event is linked to event handler cmdNumericButton_Click, which includes the logic to simulate keyboard events, for example, when you click Button "btn010300", the following lines of code will be executed, and it simulates a user pressing the keyboard button "Number 1".

...
// Number 1
case "btn010300":
    keybd_event(VK_1, 0, 0, (UIntPtr)0);
    keybd_event(VK_1, 0, KEYEVENTF_KEYUP, (UIntPtr)0);
    // event already handled
    e.Handled = true;
    break;
...

By modifying the XAML file PopupKeyboard.xaml and changing event handler cmdNumericButton_Click, you can implement any type of on-screen keyboard of your choice, while the rest of the source codes do not need any change.

2. The Keyboard is a Popup Control

Class PopupKeyboardUserControl defines a property called IsOpen that controls whether the keyboard is open or not. When setting IsOpen = true, function HookupParentPopup() is called, which essentially creates a Popup control and attaches the keyboard to it. You can check the relevant source code below:

/// <summary>
/// IsOpen
/// </summary>
public static readonly DependencyProperty IsOpenProperty =
    Popup.IsOpenProperty.AddOwner(
    typeof(PopupKeyboardUserControl),
    new FrameworkPropertyMetadata(
        false,
        FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        new PropertyChangedCallback(IsOpenChanged)));

public bool IsOpen
{
    get { return (bool)GetValue(IsOpenProperty); }
    set { SetValue(IsOpenProperty, value); }
}

/// <summary>
/// PropertyChangedCallback method for IsOpen Property
/// </summary>
/// <param name=""element""></param>
/// <param name=""e""></param>
private static void IsOpenChanged(DependencyObject element, 
				DependencyPropertyChangedEventArgs e)
{
    PopupKeyboardUserControl ctrl = (PopupKeyboardUserControl)element;

    if ((bool)e.NewValue)
    {
        if (ctrl._parentPopup == null)
        {
            ctrl.HookupParentPopup();
        }
    }
}

/// <summary>
/// Create the Popup and attach the CustomControl to it.
/// </summary>
private void HookupParentPopup()
{
    _parentPopup = new Popup();

    _parentPopup.AllowsTransparency = true;
    _parentPopup.PopupAnimation = PopupAnimation.Scroll;

    // Set Height and Width
    this.Height = this.NormalHeight;
    this.Width = this.NormalWidth;

    Popup.CreateRootPopup(_parentPopup, this);
}

3. Defining Attached Properties

Static class PopupKeyboard includes all the necessary attached properties to enable and position the numeric on-screen keyboard. As an example, I list the definition for attached property IsEnabled as follows:

/// <summary>
/// IsEnabled
/// </summary>
public static readonly DependencyProperty IsEnabledProperty =
    DependencyProperty.RegisterAttached("IsEnabled",
    typeof(bool),
    typeof(PopupKeyboard),
    new FrameworkPropertyMetadata(false,
        new PropertyChangedCallback(PopupKeyboard.OnIsEnabledChanged)));

[AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
public static bool GetIsEnabled(DependencyObject element)
{
    if (element == null)
        throw new ArgumentNullException("element");

    return (bool)element.GetValue(IsEnabledProperty);
}

public static void SetIsEnabled(DependencyObject element, bool value)
{
    if (element == null)
        throw new ArgumentNullException("element");

    element.SetValue(IsEnabledProperty, value);
}

/// <summary>
/// PropertyChangedCallback method for IsEnabled Attached Property
/// </summary>
/// <param name=""element""></param>
/// <param name=""e""></param>
private static void OnIsEnabledChanged
	(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
    FrameworkElement frameworkElement = element as FrameworkElement;

    // Attach & detach handlers for events GotKeyboardFocus, 
    // LostKeyboardFocus, MouseDown, and SizeChanged
    if (frameworkElement != null)
    {
        if (((bool)e.NewValue == true) && ((bool)e.OldValue == false))
        {
            frameworkElement.AddHandler(FrameworkElement.GotKeyboardFocusEvent, 
		new KeyboardFocusChangedEventHandler
		(frameworkElement_GotKeyboardFocus), true);
            frameworkElement.AddHandler(FrameworkElement.LostKeyboardFocusEvent, 
		new KeyboardFocusChangedEventHandler
		(frameworkElement_LostKeyboardFocus), true);
            frameworkElement.AddHandler(FrameworkElement.MouseDownEvent, 
		new MouseButtonEventHandler(frameworkElement_MouseDown), true);
            frameworkElement.AddHandler(FrameworkElement.SizeChangedEvent, 
		new SizeChangedEventHandler(frameworkElement_SizeChanged), true);
        }
        else if (((bool)e.NewValue == false) && ((bool)e.OldValue == true))
        {
            frameworkElement.RemoveHandler(FrameworkElement.GotKeyboardFocusEvent, 
		new KeyboardFocusChangedEventHandler
		(frameworkElement_GotKeyboardFocus));
            frameworkElement.RemoveHandler(FrameworkElement.LostKeyboardFocusEvent, 
		new KeyboardFocusChangedEventHandler
		(frameworkElement_LostKeyboardFocus));
            frameworkElement.RemoveHandler(FrameworkElement.MouseUpEvent, 
		new MouseButtonEventHandler(frameworkElement_MouseDown));
            frameworkElement.RemoveHandler(FrameworkElement.SizeChangedEvent, 
		new SizeChangedEventHandler(frameworkElement_SizeChanged));
        }
    }

    Window currentWindow = Window.GetWindow(element);

    // Attach or detach handler for event LocationChanged
    if (currentWindow != null)
    {
        if (((bool)e.NewValue == true) && ((bool)e.OldValue == false))
        {
            currentWindow.LocationChanged += currentWindow_LocationChanged;
        }
        else if (((bool)e.NewValue == false) && ((bool)e.OldValue == true))
        {
            currentWindow.LocationChanged -= currentWindow_LocationChanged;
        }
    }
}

When setting attached property PopupKeyboard.IsEnabled="true" for a control, event handlers for events GotKeyboardFocus, LostKeyboardFocus, MouseUp, and SizeChanged are added to that control, and event handler for event LocationChanged is added to the current Window. These event handlers contain the logic to enable, hide, and reposition the numeric on-screen keyboard by passing the settings from the attached properties to the relevant property settings inside the PopupKeyboardUserControl object. Essentially, they tie together the attached properties with the internal class PopupKeyboardUserControl.

4. Passing Values in GotKeyboardFocus and LostKeyboardFocus Event Handlers

When event GetKeyboardFocus is fired, a new instance of PopupKeyboardUserControl is created, and the settings from attached properties are passed in as you can see below:

/// <summary>
/// Event handler for GotKeyboardFocus
/// </summary>
/// <param name=""sender""></param>
/// <param name=""e""></param>
private static void frameworkElement_GotKeyboardFocus
	(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
    FrameworkElement frameworkElement = sender as FrameworkElement;

    if (frameworkElement != null)
    {
        if (PopupKeyboard._popupKeyboardUserControl == null)
        {
            _popupKeyboardUserControl = new PopupKeyboardUserControl();

            // Set all the necessary properties
            _popupKeyboardUserControl.Placement = 
			PopupKeyboard.GetPlacement(frameworkElement);
            _popupKeyboardUserControl.PlacementTarget = 
			PopupKeyboard.GetPlacementTarget(frameworkElement);
            _popupKeyboardUserControl.PlacementRectangle = 
			PopupKeyboard.GetPlacementRectangle(frameworkElement);
            _popupKeyboardUserControl.HorizontalOffset = 
			PopupKeyboard.GetHorizontalOffset(frameworkElement);
            _popupKeyboardUserControl.VerticalOffset = 
			PopupKeyboard.GetVerticalOffset(frameworkElement);
            _popupKeyboardUserControl.StaysOpen = true;
            _popupKeyboardUserControl.CustomPopupPlacementCallback = 
		PopupKeyboard.GetCustomPopupPlacementCallback(frameworkElement);
            _popupKeyboardUserControl.State = PopupKeyboard.GetState(frameworkElement);
            _popupKeyboardUserControl.NormalHeight = 
			PopupKeyboard.GetHeight(frameworkElement);
            _popupKeyboardUserControl.NormalWidth = 
			PopupKeyboard.GetWidth(frameworkElement);

            if (PopupKeyboard.GetState(frameworkElement) == KeyboardState.Normal)
                PopupKeyboard._popupKeyboardUserControl.IsOpen = true;
        }
    }
}

Similarly, when event LostKeyboardFocus is fired, the latest setting for property State (either Normal or Hidden) is saved, and keyboard is closed by setting IsOpen = false:

/// <summary>
/// Event handler for LostKeyboardFocus
/// </summary>
/// <param name=""sender""></param>
/// <param name=""e""></param>
private static void frameworkElement_LostKeyboardFocus
	(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
    FrameworkElement frameworkElement = sender as FrameworkElement;

    if (frameworkElement != null)
    {
        if (PopupKeyboard._popupKeyboardUserControl != null)
        {
            // Retrieves the setting for the State property
            PopupKeyboard.SetState(frameworkElement, _popupKeyboardUserControl.State);

            PopupKeyboard._popupKeyboardUserControl.IsOpen = false;
            PopupKeyboard._popupKeyboardUserControl = null;
        }
    }
}

Feedback

That's it. I hope you'll find this article helpful and instructive, and please vote if you like it.

History

  • Dec. 20, 2008 - Initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here