With the release of Windows 8, the era of touch devices is in full swing. Manufacturers are scrambling to create mobile devices that can run Microsoft Windows with the touch of a finger, without the need for a keyboard and mouse. Even Microsoft has released their very own Surface brand to satisfy this need to run Windows in a mobile world. This is all fine and dandy if you are building Windows Store applications that run on any version of Windows 8, and are built with touch as a first class citizen. Windows Store apps integrate perfectly with the device, such as automatically showing the Windows 8 touch keyboard when giving focus to an input element in the application. Windows Store apps are smart enough to know when I am not using a mouse and keyboard, and I give focus to a TextBox by touching it, it will show the touch keyboard to allow me to input data. On the other hand, if I am using a mouse and keyboard, and I give focus to the TextBox by clicking it with the mouse, the touch keyboard does not show, but rather I use the keyboard to enter my data. This is a nice feature built into the platform.
Although Microsoft has been shoving Windows Store apps down everyone’s throat, Windows Store apps are not a desktop solution, and in it’s current form, cannot replace certain desktop applications. This means, that if you are a desktop developer and want your WPF applications to work in this new mobile world, you need to make your WPF apps more touch accessible. Specifically, when dealing with running your WPF application on a Windows 8 Pro tablet, without the need for a mouse and keyboard.
Unfortunately, this is not as easy as we would like. As you have probably discovered, there is no API in WPF to access the Windows 8 touch keyboard. This means, that if a user loads your awesome WPF application onto their Surface Pro 2, and touches a input control, they have no way of entering data into that control. Well, they can, but it won’t be obvious to them. In order to enter any type of data into your WPF input controls, they would have to manually open the touch keyboard by tapping on the keyboard icon located in the taskbar.
Sure this works, but it is not the ideal experience. Wouldn’t it be nice to have your WPF application behave just like a Windows Store app, and automatically show the touch keyboard when an input control gains focus by touch? Well, you can, and I am going to show you how. Just so you know, it doesn’t require calling Process.Start(“C:\…\TabTip.exe”).
The first thing we have to do is disable inking in the WPF application. We do this because by default WPF touch support comes through the tablet platform’s real-time stylus input events (OnStylusDown, OnStylusUp, OnStylusMove). Instead, we need to use the multi-touch input as Win32 WM_TOUCH windows messages. This means we need to disable WPF listening to stylus input, and remove any tablet support added by the WPF Window. This code is freely available and documented in the MSDN topic “Disable the RealTimeStylus for WPF Applications”.
{
// Get a collection of the tablet devices for this window.
TabletDeviceCollection devices = System.Windows.Input.Tablet.TabletDevices;
if (devices.Count > 0)
{
// Get the Type of InputManager.
Type inputManagerType = typeof(System.Windows.Input.InputManager);
// Call the StylusLogic method on the InputManager.Current instance.
object stylusLogic = inputManagerType.InvokeMember("StylusLogic",
BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
null, InputManager.Current, null);
if (stylusLogic != null)
{
// Get the type of the stylusLogic returned from the call to StylusLogic.
Type stylusLogicType = stylusLogic.GetType();
// Loop until there are no more devices to remove.
while (devices.Count > 0)
{
// Remove the first tablet device in the devices collection.
stylusLogicType.InvokeMember("OnTabletRemoved",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
null, stylusLogic, newobject[] { (uint)0 });
}
}
}
}
}
The next thing we need to do is to get our WPF desktop application to opt-in to the focus tracking mechanism used in Windows Store applications. We do this by implementing the IInputPanelComfiguration interface. This will let our WPF app leverage the invoking and dismissing semantics of the touch keyboard and handwriting input panel. Unfortunately, this is not a managed interface, and there is no DLL you can reference to use it. There are two options for implementing this.
One, we can create an assembly from the IDL file located at c:\Program Files (x86)\Windows Kits\8.1\Include\um\inputpanelconfiguration.idl:
- Start a command prompt
- Use the MIDL compiler tool to build a Type Library TLB file (learn more about MIDL Compiler see here on MSDN)
- Example: midl /tbld {filename}
- Use the TLBIMP tool to convert the above generated Type Library (TLB file) into a DLL that .NET can use by running the following command (learn more about Tlbimp.exe here on MSDN)
- Example: TLBIMP.exe InputpanelConfiguration.tlb /publickey:{pathToKey} /delaysign
This will create an unsigned DLL that you can now reference in your Visual Studio Solution.
Two, we can simply use a ComImport. This is my preferred approach.
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
publicinterfaceIInputPanelConfiguration
{
///
/// Enables a client process to opt-in to the focus tracking mechanism for Windows Store apps that controls the invoking and dismissing semantics of the touch keyboard.
///
///If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
int EnableFocusTracking();
}
[ComImport, Guid("2853ADD3-F096-4C63-A78F-7FA3EA837FB7")]
classInputPanelConfiguration
{
}
Now that we have taken care of the hard part, time to put the pieces together. We first turn off inking by calling InkInputHelp.DisabeWPFTabletSupport, and then we call IInputPanelConfiguration.EnableFocusTracking.
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
// Disables inking in the WPF application and enables us to track touch events to properly trigger the touch keyboard
InkInputHelper.DisableWPFTabletSupport();
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//Windows 8 API to enable touch keyboard to monitor for focus tracking in this WPF application
InputPanelConfiguration cp = newInputPanelConfiguration();
IInputPanelConfiguration icp = cp asIInputPanelConfiguration;
if (icp != null)
icp.EnableFocusTracking();
}
}
Now I can add a TextBox to my MainWindow.xaml, run the application, and watch the magic happen.
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox AcceptsReturn="True" />
Grid>
Window>
When I touch the TextBox to give it focus, the touch keyboard will show allowing me to use it to enter data into the control. If I click the TextBox with my mouse, the touch keyboard will not show, instead I have to use the keyboard to enter data into the control. As you can probably guess, this is not an official support solution by Microsoft, so you can expect some behavioral differences compared to Windows Store apps. For example, when you give touch focus to a control to show the touch keyboard, and then touch another area of your application, the keyboard may not hide as expected. Basically, some controls will dismiss the touch keyboard (for example a Button), while others won’t.
I have been working with my friend Dmitry Lyalin to solve this issue. He was vital to solving this, and he posted his own version of the solution to the Microsoft Developer Network. You will notice some differences in his implementation than mine, but the end result is the same. I would also like to note that Dmitry made a call to System.Windows.Automation.AutomationElement.FromHandle, but I do not see the need for the call, and do not notice any differences in behavior by not calling it. If I discover otherwise, I will update this post.
Don’t forget to download the source code.
Feel free contact me on my blog, connect with me on Twitter (@brianlagunas), or leave a comment below for any questions or comments you may have.