Tuesday, May 05, 2015

Adding Touch Keyboard Support to a WPF Desktop Application

Touch enabled devices are increasingly becoming more common. Microsoft has released its own line of Surface tablets and there are quite a few hybrid or convertible laptops available today (e.g. Lenovo Yoga, Dell Inspiron Series 2-in-1, etc.). Today many laptops even include touch screens. When developing a new Microsoft Windows application that needs to provide a good user experience on a touch screen, using the new Universal Windows App template in Visual Studio 2015 is a good choice, but these applications can only run on Windows 8.1 and Windows 10. If the application also needs to run on Windows 7, a traditional desktop application must be used. That, however, does not mean the application cannot provide a good user experience on touch enabled devices. This article describes how to add touch keyboard support to a traditional WPF desktop application.

Avoid Keyboard Data Entry

Typing on a virtual touch keyboard is not an ideal user experience. Whenever possible it is best to avoid requiring the user to use the keyboard. There are numerous ways to get information from users without using a textbox (e.g. dropdown lists, radio buttons, checkboxes, etc.). Unfortunately sometimes there is no way around requiring keyboard data entry.

Showing and Hiding the Touch Keyboard

The following TouchKeyboardProvider class contains a ShowTouchKeyboard and a HideTouchKeyboard method that can be used to show or hide the touch keyboard:

public class TouchKeyboardProvider : ITouchKeyboardProvider
{
#region Private: Fields

private readonly string _virtualKeyboardPath;
private readonly bool _hasTouchScreen;

#endregion

#region ITouchKeyboardProvider Methods

public TouchKeyboardProvider()
{
_hasTouchScreen = HasTouchInput();

_virtualKeyboardPath = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles),
@"Microsoft Shared\ink\TabTip.exe");
}

public void ShowTouchKeyboard()
{
if (!_hasTouchScreen) return;

try
{
Process.Start(_virtualKeyboardPath);
}
catch (Exception ex)
{
// TODO: Log error.
Debug.WriteLine(ex.ToString());
}
}

public void HideTouchKeyboard()
{
if (!_hasTouchScreen) return;

var nullIntPtr = new IntPtr(0);
const uint wmSyscommand = 0x0112;
var scClose = new IntPtr(0xF060);

var keyboardWnd = FindWindow("IPTip_Main_Window", null);
if (keyboardWnd != nullIntPtr)
{
SendMessage(keyboardWnd, wmSyscommand, scClose, nullIntPtr);
}
}

#endregion

#region Private: Win32 API Methods

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindow(string sClassName, string sAppName);

[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);

#endregion

#region Private: Methods

private static bool HasTouchInput()
{
return Tablet.TabletDevices.Cast<TabletDevice>().Any(
tabletDevice => tabletDevice.Type == TabletDeviceType.Touch);
}

#endregion
}



The ShowTouchKeyboard method shows the touch keyboard by telling Windows to start the TabTip.exe executable. The HideTouchKeyboard method hides the touch keyboard by telling the IPTip_Main_Window window (i.e. the touch keyboard window) to close.

Both of these methods check to see if the device has a touch screen before attempting to show or hide the touch keyboard. Since some users may be on a laptop with a touch screen, but want to use the physical keyboard rather than the touch keyboard, consider having an application setting that prevents the touch keyboard from appearing even if the device supports touch.


Alternate Technique for Showing and Hiding the Touch Keyboard


Another technique for showing and hiding the touch keyboard in a desktop application is described in detail at http://brianlagunas.com/showing-windows-8-touch-keyboard-wpf/, but at the time this article was written there was a bug in .NET Framework 4.5.2 that causes applications that use this technique to stop responding to the user. Microsoft has released hotfix 3026376 (https://support.microsoft.com/en-us/kb/3026376) to address this issue, but the hotfix must be downloaded directly from Microsoft as it is not yet available in Windows Update.


Showing and Hiding the Touch Keyboard on GotFocus Events


Different techniques can be used to call the ShowTouchKeyboard and HideTouchKeyboard methods described above, but the technique I found works the best is to call the methods in the GotFocus event handlers. For framework elements that should have the touch keyboard visible (e.g. TextBox, RichTextBox, etc.), call the ShowTouchKeyboard method in their GotFocus event handlers. For other framework elements that should not have the touch keyboard visible (e.g. ComboBox, RadioButton, CheckBox, etc.), call the HideTouchKeyboard method in their GotFocus event handlers.



private readonly ITouchKeyboardProvider _touchKeyboardProvider = new TouchKeyboardProvider();

private void ShowTouchKeyboard(object sender, RoutedEventArgs e)
{
_touchKeyboardProvider.ShowTouchKeyboard();
}

private void HideTouchKeyboard(object sender, RoutedEventArgs e)
{
_touchKeyboardProvider.HideTouchKeyboard();
}


Controlling Which Touch Keyboard Is Displayed


The touch keyboard layout Windows uses is determined by the framework element’s InputScope property (https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.inputscope.aspx). The complete list of InputScope options is available at https://msdn.microsoft.com/en-us/library/system.windows.input.inputscopenamevalue.aspx. The following are some of the more common touch keyboard layouts on Windows 8.1:


InputScope="Default"


DefaultKeyboard


InputScope="Url"


UrlKeyboard


InputScope="EmailSmtpAddress"


EmailKeyboard


InputScope="Number"


NumericKeyboard



Dealing With Controls Hidden By the Touch Keyboard


Microsoft Windows will automatically resize the desktop when the touch keyboard appears (assuming the touch keyboard in in “docked” mode). This will cause the application to move or resize to accommodate the new desktop size. Often when this resizing occurs, the framework element that has focus will no longer be visible. The following code can be used in the main application window’s SizeChanged event to cause the framework element that has focus to scroll back into view if the window gets smaller (e.g. the touch keyboard appeared).



private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
// When the main window resizes (often caused by the touch keyboard
// being displayed), attempt to bring the element that has focus
// into view.
var elementWithFocus = Keyboard.FocusedElement as FrameworkElement;

if ((elementWithFocus != null) && ((e.PreviousSize.Height > e.NewSize.Height)))
{
elementWithFocus.BringIntoView();
}
}


The BringIntoView method typically causes the element to appear at the bottom of the scrollable area. The BringIntoView method can be replaced with the following BringIntoTopOfView extension method to cause the element to appear at the top of the scrollable area instead:



public static void BringIntoTopOfView(this FrameworkElement frameworkElement)
{
if (frameworkElement == null)
{
throw new ArgumentNullException("frameworkElement");
}

var parentScrollViewer = frameworkElement.FindParent<ScrollViewer>();
if (parentScrollViewer != null)
{
parentScrollViewer.ScrollToBottom();
frameworkElement.BringIntoView();
}
}


The BringIntoTopOfView extension method makes use of the following FindParent extension method, which can be used to find a specific parent of the element by type:



public static T FindParent<T>(this DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
throw new ArgumentNullException("dependencyObject");
}

T parent;

do
{
dependencyObject = VisualTreeHelper.GetParent(dependencyObject);

if (dependencyObject == null) return null;

parent = dependencyObject as T;

} while (parent == null);

return parent;
}


Summary


Although it takes extra work, a Microsoft Windows desktop application can provide a user on a touch device with an experience similar to that of a Windows Store or Windows Universal App. All of the example code shown in this article can be downloaded at http://bit.ly/BWinTKS.

7 comments:

Ahmet Erdogan said...

Thank you so much

Manuel said...

Hi,

at Windows 10, the keyboard hovers over the application. Some Applications, like explorer.exe do a resize when the keyboard is opened. Do you have any idea how to implement this?

Cheers,
Argbeil

Matthew Heironimus said...

If the touch keyboard is in "docked" mode, this should work for any re-sizable application. When it is not in "docked" mode, this becomes quite a bit trickier. It was going to be difficult enough that we decided to just tell people to keep their touch keyboard in docked mode.

That said, I think it would be possible to detect this, even if the touch keyboard was not in docked mode. Here are some links to get you started on your research: http://brianlagunas.com/showing-windows-8-touch-keyboard-wpf/, https://code.msdn.microsoft.com/Enabling-Windows-8-Touch-7fb4e6de

SamRoo Channel said...

Hi, Is it possible to hide some of the keys on the keyboard? for example changing the layout of the keyboard in the bottom right corner?

Matthew Heironimus said...

@SammRoo Channel - Unfortunately no, you cannot customize the keyboard outside of what is documented. If you want a custom keyboard layout, you either have to create your own virtual keyboard in WPF or use an existing library. Some example libraries include http://www.codeproject.com/Articles/145579/A-Software-Virtual-Keyboard-for-Your-WPF-Apps, http://wpfkb.codeplex.com/, http://mindfusion.eu/virtual-keyboard-wpf.html, etc.

Asma Said said...

hello,

does this work on windows 7 ?? the same keyboard ??

Matthew Heironimus said...

@Asma Said - Yes this will work on Windows 7, but it will display the Windows 7 touch keyboard instead of the Windows 8/8.1/10 version. The Windows 7 touch keyboard is not as nice. If you want something that looks identical in all versions of the OS, you will need to build your own custom touch keyboard or use a third-party control.