| File: MS\Internal\Interop\TipTsfHelper.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // using System.Windows; using System.Windows.Automation.Peers; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Threading; using MS.Internal.WindowsRuntime.Windows.UI.ViewManagement; namespace MS.Internal.Interop { /// <summary> /// DevDiv:1193138 /// /// This class provides functionality to show/hide the touch keyboard. /// This is necessary in Win8+ as touch keyboard invocation was changed /// to use WM_POINTER message tracking. Since Wisp does not allow these /// to propogate to WPF, we have to forcibly show the keyboard. /// /// As of creation of this class, we never call Hide, only Show. This is /// because the touch keyboard is smart about when to show or hide. As long /// as we attempt to show it on each focus, it will determine whether this is /// a valid scenario or whether it should close. /// /// If we are run under a platform that does not support the proper calls into /// the InputPane WinRT API we will catch an exception on the first call to /// TryShow/Hide and then cache the fact that we no longer need to make these calls. /// </summary> internal static class TipTsfHelper { /// <summary> /// Cache if the API call is supported on the current platform. /// </summary> private static bool s_PlatformSupported = true; /// <summary> /// Cache any in progress operation in case we get multiple calls. /// </summary> [ThreadStatic] private static DispatcherOperation s_KbOperation; /// <summary> /// If DispatcherProcessing is disabled, this will BeginInvoke the appropriate KB operation /// for later processing. It also will cancel any pending operations so only one op can be in /// flight at a time. /// </summary> /// <param name="kbCall">The kb function to call.</param> /// <param name="focusedObject">The object being focused</param> /// <returns>True if an operation has been scheduled, false otherwise</returns> private static bool CheckAndDispatchKbOperation(Action<DependencyObject> kbCall, DependencyObject focusedObject) { // Abort the current operation if we reach another call and it is // still pending. if (s_KbOperation?.Status == DispatcherOperationStatus.Pending) { s_KbOperation.Abort(); } s_KbOperation = null; // Don't call any KB operations under disabled processing as a COM wait could // cause re-entrancy and an InvalidOperationException. if (Dispatcher.CurrentDispatcher._disableProcessingCount > 0) { // Retry when processing resumes s_KbOperation = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() => { // Since this action is happening sometime later, the focus // may have already changed. if (Keyboard.FocusedElement == focusedObject) { kbCall(focusedObject); } })); return true; } return false; } /// <summary> /// Attempts to show the touch keyboard. /// We rely on the Windows API to determine if showing the keyboard aligns with the /// current state (no physical KB, touch enabled, focused edit control). /// </summary> internal static void Show(DependencyObject focusedObject) { // We need to only show if applicable to this focused object // so guard the calls to TryShow here. // If the touch stack is disabled or the WM_POINTER touch stack // is enabled, we get touch KB support for free. So don't // attempt any calls into InputPane for these scenarios. // Don't show if implicit invocation is turned off. if (s_PlatformSupported && !CoreAppContextSwitches.DisableImplicitTouchKeyboardInvocation && StylusLogic.IsStylusAndTouchSupportEnabled && !StylusLogic.IsPointerStackEnabled && !CheckAndDispatchKbOperation(Show, focusedObject) && ShouldShow(focusedObject)) { InputPane ip; try { using (ip = InputPane.GetForWindow(GetHwndSource(focusedObject))) { ip?.TryShow(); } } catch (PlatformNotSupportedException) { s_PlatformSupported = false; } } } /// <summary> /// Attempts to hide the touch keyboard. /// </summary> internal static void Hide(DependencyObject focusedObject) { // If the touch stack is disabled or the WM_POINTER touch stack // is enabled, we get touch KB support for free. So don't // attempt any calls into InputPane for these scenarios. if (s_PlatformSupported && StylusLogic.IsStylusAndTouchSupportEnabled && !StylusLogic.IsPointerStackEnabled && !CheckAndDispatchKbOperation(Hide, focusedObject)) { InputPane ip; try { using (ip = InputPane.GetForWindow(GetHwndSource(focusedObject))) { ip?.TryHide(); } } catch (PlatformNotSupportedException) { s_PlatformSupported = false; } } } /// <summary> /// The KB should only show when we have an object that implements the /// UIAutomation Text pattern. Therefore, we should test for this /// pattern on any focused object that we get. /// </summary> /// <param name="focusedObject">The object being focused</param> /// <returns>True if the touch KB should show, false otherwise.</returns> private static bool ShouldShow(DependencyObject focusedObject) { UIElement uiElement; UIElement3D uiElement3D; ContentElement contentElement; AutomationPeer peer = null; if ((uiElement = focusedObject as UIElement) != null) { peer = uiElement.GetAutomationPeer(); } else if ((uiElement3D = focusedObject as UIElement3D) != null) { peer = uiElement3D.GetAutomationPeer(); } else if ((contentElement = focusedObject as ContentElement) != null) { peer = contentElement.GetAutomationPeer(); } return peer?.GetPattern(PatternInterface.Text) != null; } /// <summary> /// Returns an HwndSource from the Visual /// </summary> /// <param name="focusedVisual">The visual to get the HwndSource for</param> /// <returns>The HwndSource associated with the Visual</returns> private static HwndSource GetHwndSource(DependencyObject focusedObject) { return PresentationSource.CriticalFromVisual(focusedObject) as HwndSource; } } }