Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

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 https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.inputscope?view=netframework-4.8). The complete list of InputScope options is available at https://msdn.microsoft.com/en-us/library/system.windows.input.inputscopenamevalue.aspx https://docs.microsoft.com/en-us/dotnet/api/system.windows.input.inputscopenamevalue?view=netframework-4.8. The following are some of the more common touch keyboard layouts on Windows 8.1:


InputScope="Default"





InputScope="Url"








InputScope="EmailSmtpAddress"







InputScope="Number"







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 https://static.heironimus.info/blog/codesamples/TouchKeyboardSample.zip.




Tuesday, November 25, 2014

Making DPI-Aware Applications in .NET

Since the 1980s, the Microsoft Windows operating system has used a default dots per inch (DPI) setting of 96 pixels per inch (PPI). Until recently this setting was hardly ever changed. A developer could pretty safely assuming any computer running their software would be using a DPI setting of 96 PPI. However, high definition displays are becoming more common. Laptop computers have been using higher DPI displays for a while now (e.g. Apple’s MacBook Pro with Retina display uses a 220 or 227 PPI display, Acer Aspire S7 uses a 220 PPI display, Google Chromebook Pixel uses a 239 PPI display). In October 2014 Apple released their 5120 x 2880 pixel resolution (218 PPI) monitor. Dell has announced it will begin selling an UltraSharp 27 Ultra HD 5K in December 2014 with a 5128 x 2880 pixel resolution (218 PPI). Software developers can no longer assume the computer running their software will be using 96 PPI.

WinForms Applications

By default WinForms applications are not DPI-aware, they assume a 96 PPI. If they are executed on a system with a higher DPI setting, the operating system will virtualize and scale these applications to account for the higher DPI setting. This can cause the applications to suffer from many visual artifacts, including incorrect scaling of UI elements, clipped text, and blurry images. The following are screenshots of a WinForms application running at 96 PPI and 144 PPI:

WinForms Not DPI-Aware - 96 PPI
Figure 1 - WinForms Application 96 PPI


WinForms Not DPI-Aware - 144 PPI
Figure 2 - WinForms Application 144 PPI

The application running at 144 PPI appears blurry, unlike the version running on a 96 PPI display.

Making WinForms Applications DPI-Aware

A WinForms application can be made DPI-aware by adding the following <dpiAware> element to the application’s manifest file:

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
   <asmv3:application>
      <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
         <dpiAware>true</dpiAware>
      </asmv3:windowsSettings>
   </asmv3:application>
</assembly>

Adding this content to the application’s manifest file resolves the blurry text and controls issues seen in the non-DPI-aware WinForms application.

WinForms DPI-Aware - 144 PPI
Figure 3 - WinForms DPI Aware Application 144 PPI

When developing a WinForms application that will be run on machines with various DPI settings, it is wise to test the application with the standard DPI settings (100% (96 PPI); 125% (120 PPI); 150% (144 PPI), and 200% (192 PPI)).

WPF Applications

By default Windows Presentation Foundation (WPF) applications are system DPI-aware. WPF uses device-independent units to facilitate resolution and device independence. WPF automatically scales each device-independent unit based on current system DPI. This results in applications that look correct regardless of the machine’s DPI setting.

WPF DPI-Aware - 96 PPI
Figure 4 - WPF Application 96 PPI


WPF DPI-Aware - 144 PPI
Figure 5 - WPF Application 144 PPI


Per-Monitor DPI-Aware WPF Application

In Windows 8.1 Microsoft added the ability to have different DPI settings for each monitor, instead of a single system DPI setting. WPF applications are system DPI-aware, but they are not monitor DPI-aware. Microsoft has published a detailed article describing how to create a monitor DPI aware application on their website, called “Developing a Per-Monitor DPI-Aware WPF Application” (http://msdn.microsoft.com/en-us/library/windows/desktop/ee308410.aspx https://docs.microsoft.com/en-us/windows/win32/hidpi/declaring-managed-apps-dpi-aware). This is useful for applications that will be used on systems with multiple monitors that have different DPI settings.

Conclusion

High definition displays are here to stay. As application developers we need to ensure our applications look and work properly on traditional 96 PPI systems as well as the new higher PPI system. The newer WPF UI framework handles differing DPI settings by default, but even WinForms applications can be made to work properly on higher PPI systems.

The source code that was used to generate the screenshots for this article can be downloaded at https://static.heironimus.info/blog/dpi-aware/DpiAware.zip.