如何让 Windows 10 平板电脑中的 "touchscreen keyboard" 停止在我们的 WPF 应用程序中打开两次?

How can I get the "touchscreen keyboard" in a Windows 10 tablet to stop opening twice in our WPF app?

请注意,我花了很多时间搜索包括 SO 在内的在线帖子,但到目前为止都没有成功。

问题是触摸屏键盘开始自动打开,因为 Windows 10 Touch Keyboard and Handwriting Panel Service 每当有人点击文本框时,而在 Windows 8.1 之前,键盘会打开仅由于来自我们的结构资产管理 (SAM) 应用程序的 C# API 调用。因此,在 Windows 10 中,只要有人单击文本框,虚拟键盘就会打开两次——一次是因为 SAM C# API 调用,一次是由于 Touch Keyboard and Handwriting Panel Service.

请注意,我们已尝试禁用 Touch Keyboard and Handwriting Panel Service,但这会导致触摸屏键盘根本不显示。

通常,让 OS 使用 Touch Keyboard and Handwriting Panel Service 打开此触摸屏键盘会很好,但问题是我们有时需要显示触摸屏键盘,而其他时候只显示数字键盘,因此仅依赖 Windows 服务不是一种选择。

以下是 类 在 Windows 8.1 中成功控制键盘的方法:

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Input;

namespace Cpr.Apps.Sam.Controls
{
    /// <summary>
    /// Shows or hides the touch keyboard on tablets.
    /// </summary>
    public class TouchKeyboard
    {
        /// <summary>
        /// The touch keyboard app's file path.
        /// </summary>
        private static string _touchKeyboardAppFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), @"Microsoft Shared\ink\TabTip.exe");

        /// <summary>
        /// Set to true if the app is currently running on a touch device and false otherwise.
        /// </summary>
        private static bool _isTouchScreen = Tablet.TabletDevices.Cast<TabletDevice>().Any(tabletDevice => tabletDevice.Type == TabletDeviceType.Touch);

        /// <summary>
        /// The keyboard visible flag.
        /// </summary>
        /// <remarks>
        /// This flag only keeps track of the keyboard's visibility if it was set using this class.
        /// </remarks>
        private static bool _isKeyboardVisible;

        /// <summary>
        /// The delay after which the keyboard will be hidden, in seconds.
        /// </summary>
        /// <remarks>
        /// The keyboard is not hidden immediately when the associated input field loses the keyboard focus, so that it will
        /// not flicker if another input field with this behavior obtains the keyboard focus immediately afterwards.
        /// </remarks>
        private const double KEYBOARD_HIDE_DELAY = 0.25;

        /// <summary>
        /// The number of milliseconds per second. Used for time conversions.
        /// </summary>
        private const long MILLISECONDS_PER_SECOND = 1000;

        /// <summary>
        /// True if the current device has a touch screen and false otherwise.
        /// </summary>
        public static bool IsTouchScreen
        {
            get { return _isTouchScreen; }
        }

        /// <summary>
        /// Shows the touch keyboard if the app is running on a touch device.
        /// </summary>
        /// <remarks>
        /// This method does nothing if the app is not currently running on a touch device.
        /// </remarks>
        public static void Show()
        {
            // check if the app is running on a touch device
            if (_isTouchScreen && _touchKeyboardAppFilePath != null)
            {
                try
                {
                    // launch the touch keyboard app
                    Process.Start(_touchKeyboardAppFilePath);

                    // set the keyboard visible flag
                    _isKeyboardVisible = true;
                }
                catch (Exception)
                {
                    // do nothing
                }
            }
        }

        /// <summary>
        /// Hides the touch keyboard if the app is running on a touch device.
        /// </summary>
        /// <remarks>
        /// This method does nothing if the app is not currently running on a touch device.
        /// </remarks>
        public static void Hide()
        {
            // check if the app is running on a touch device
            if (_isTouchScreen)
            {
                // reset the keyboard visible flag
                _isKeyboardVisible = false;

                // hide the keyboard after a delay so that if another input field with this behavior obtains the focus immediately,
                // the keyboard will not flicker
                Timer timer = null;
                timer = new Timer((obj) =>
                {
                    // check if the keyboard should still be hidden
                    if (!_isKeyboardVisible)
                    {
                        // check if the keyboard is visible
                        var touchKeyboardWindowHandle = FindWindow("IPTip_Main_Window", null);
                        if (touchKeyboardWindowHandle != _nullPointer)
                        {
                            // hide the keyboard
                            SendMessage(touchKeyboardWindowHandle, WM_SYSCOMMAND, SC_CLOSE, _nullPointer);
                        }
                    }

                    // release the timer
                    timer.Dispose();
                }, null, (long)(KEYBOARD_HIDE_DELAY * MILLISECONDS_PER_SECOND), Timeout.Infinite);
            }
        }

        // Win32 null pointer parameter
        private static IntPtr _nullPointer = new IntPtr(0);

        // Win32 command from the Window menu
        private const uint WM_SYSCOMMAND = 0x0112;

        // Win32 command to close a window
        private static IntPtr SC_CLOSE = new IntPtr(0xF060);

        // Win32 API to get a window reference
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern IntPtr FindWindow(string sClassName, string sAppName);

        // Win32 API to send a message to a window
        [DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
    }
}

并且:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using Cpr.Apps.Sam.Controls;

namespace Cpr.Apps.Sam.Styles.Behaviors
{
    /// <summary>
    /// Behavior that shows the touch keyboard (on tablets only) when the associated control gets the keyboard focus.
    /// </summary>
    public class ControlShowTouchKeyboardOnFocusBehavior : Behavior<Control>
    {
        protected override void OnAttached()
        {
            base.OnAttached();

            // add the event handlers
            WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "GotKeyboardFocus", OnGotKeyboardFocus);
            WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "LostKeyboardFocus", OnLostKeyboardFocus);
        }

        /// <summary>
        /// Called when the associated control receives the keyboard focus.
        /// </summary>
        /// <param name="sender">The object triggering this event.</param>
        /// <param name="e">The event parameters.</param>
        private void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            // show the touch keyboard
            TouchKeyboard.Show();
            var textBox = sender as TextBox;
            if (textBox != null)
                textBox.SelectionStart = Math.Max(0, textBox.Text.Length);  //Move the caret to the end of the text in the text box.
        }

        /// <summary>
        /// Called when the associated control loses the keyboard focus.
        /// </summary>
        /// <param name="sender">The object triggering this event.</param>
        /// <param name="e">The event parameters.</param>
        private void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            // hide the touch keyboard
            TouchKeyboard.Hide();
        }
    }
}

基本上,我的问题是,如何让 Windows 10 中的触摸屏键盘与 Windows 8.1 中的一样?我可以在 OS 设置上更改一些配置值,还是需要更改注册表中的某些内容? Windows 8.1 和 Windows 10 中的 Touch Panel and Handwriting Service 有什么区别? TIA。

更新:

请注意,我已经探索过使用 "On Screen Keyboard",我认为它是基于 COM 而不是较新的 "Touchscreen Keyboard" 但这没有帮助,因为最终因为这个 COM 键盘需要管理员权限才能关闭或最小化它。这就是我尝试使用 "On Screen Keyboard":

using System;
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using Cpr.Apps.Sam.Controls;

namespace Cpr.Apps.Sam.Styles.Behaviors
{
    /// <summary>
    /// Behavior that shows the touch keyboard (on tablets only) when the associated control gets the keyboard focus.
    /// </summary>
    public class ControlShowTouchKeyboardOnFocusBehavior : Behavior<Control>
    {
        [DllImport("User32")]
        private static extern int ShowWindow(int hwnd, int nCmdShow);

        private const int SW_HIDE = 0;
        private const int SW_RESTORE = 9;
        private int hWnd;
        private readonly string _USB = "USB";
        private readonly string _keyboard = @"osk.exe";
        private Process _keyboardProcess = null;
        private ProcessStartInfo _startInfo = null;

        protected override void OnAttached()
        {
            base.OnAttached();

            // add the event handlers
            WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "GotKeyboardFocus", OnGotKeyboardFocus);
            WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "LostKeyboardFocus", OnLostKeyboardFocus);
        }

        private bool GetKeyboardPresent()
        {
            bool flag = false;
            foreach (ManagementBaseObject managementBaseObject in new ManagementObjectSearcher("Select * from Win32_Keyboard").Get())
            {
                foreach (PropertyData property in managementBaseObject.Properties)
                {
                    if (Convert.ToString(property.Value).Contains(this._USB))
                    {
                        flag = true;
                        break;
                    }
                }
            }

            return flag;
        }

        /// <summary>
        /// Called when the associated control receives the keyboard focus.
        /// </summary>
        /// <param name="sender">The object triggering this event.</param>
        /// <param name="e">The event parameters.</param>
        private void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            // show the touch keyboard
            // TouchKeyboard call here not needed in Windows 10 because of “Touch Keyboard and Handwriting Panel Service” causes virtual keyboard to show up twice.  Use on screen keyboard instead. 
            //TouchKeyboard.Show();
            if (!this.GetKeyboardPresent())
            {
                //_keyboardProcess.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
                Process[] pocesses = Process.GetProcessesByName(_keyboard);

                if (pocesses.Any())
                {
                    foreach (var proc in pocesses)
                    {
                        hWnd = (int) proc.MainWindowHandle;
                        ShowWindow(hWnd, SW_RESTORE);
                    }
                }
                else
                {
                    _startInfo = new ProcessStartInfo(_keyboard);
                    _keyboardProcess = new Process
                    {
                        EnableRaisingEvents = true,
                        StartInfo = _startInfo
                    };
                    _keyboardProcess.Exited += new EventHandler(ProcessExited);
                    //Don't need this because it is for parent process: AppDomain.CurrentDomain.ProcessExit += (a, b) => _keyboardProcess.Kill();
                    _keyboardProcess.Start();
                }

            }

            var textBox = sender as TextBox;
            if (textBox != null)
                textBox.SelectionStart = Math.Max(0, textBox.Text.Length);  //Move the caret to the end of the text in the text box.
        }

        /// <summary>
        /// Called when the associated control loses the keyboard focus.
        /// </summary>
        /// <param name="sender">The object triggering this event.</param>
        /// <param name="e">The event parameters.</param>
        private void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            // hide the touch keyboard
            // TouchKeyboard call here not needed in Windows 10 because of “Touch Keyboard and Handwriting Panel Service” causes virtual keyboard to show up twice.  Use on screen keyboard instead. 
            //TouchKeyboard.Hide();
            if (!GetKeyboardPresent() && _keyboardProcess != null)
            {
                //Keyboard doesn't minimize if I call Kill() or SW_HIDE, and instead this simply causes the textbox to lose focus so commented out this code
                //Process[] pocesses = Process.GetProcessesByName("osk");

                //for (int i = 0; i < pocesses.Count(); i++)
                //{
                //    var proc = pocesses[i];
                //    proc.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
                    //hWnd = (int)proc.MainWindowHandle;
                    //ShowWindow(hWnd, SW_HIDE);
                //}

                //Task.Delay(500);
            }
        }

        private void ProcessExited(object sender, System.EventArgs e)
        {
            Debug.WriteLine("Exited _keyboardProcess");
            _keyboardProcess = null;
        }
    }
}

更新 2:

看来我可能需要将我的应用程序从 WPF 移植到 WinRT 才能在 Windows 10 上运行:请参阅 https://docs.microsoft.com/en-us/windows/uwp/porting/

Move from WPF and Silverlight to WinRT

Related Topics

最终使用 C# 内置的自定义 WPF 软件键盘,而不是 Windows“触摸键盘和手写面板服务”触摸屏键盘和屏幕键盘。