File: MS\Internal\Interop\TipTsfHelper.cs
Web Access
Project: src\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.
// See the LICENSE file in the project root for more information.
 
//
//
 
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 = null;
 
        /// <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;
        }
    }
}