简体   繁体   中英

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

Please note that I have spent a lot of time searching through online posts including on SO but have been unsuccessful so far.

The problem is with the touchscreen keyboard that started being opened automatically because of the Windows 10 Touch Keyboard and Handwriting Panel Service whenever somebody clicks on a textbox whereas before with Windows 8.1 the keyboard was opened only due to the C# API calls from within our Structures Asset Management (SAM) app. Thus with Windows 10, the virtual keyboard was being opened twice whenever someone clicks on a textbox-—once because of the SAM C# API call and once due to the Touch Keyboard and Handwriting Panel Service .

Please note that we have attempted to disable the Touch Keyboard and Handwriting Panel Service but that then causes the touchscreen keyboard to not appear at all.

Normally, it would be fine to just let the OS open this touchscreen keyboard using the Touch Keyboard and Handwriting Panel Service but the problem is we need to sometimes show the touchscreen keyboard and other times show only a numeric keypad, so just relying on the Windows service is not an option.

Here are the classes that worked successfully for controlling the keyboard in 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);
    }
}

And:

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();
        }
    }
}

Basically, my question is, how can I make the touchscreen keyboard in Windows 10 behave the same way it did in Windows 8.1? Are there some configuration values I can change on the OS settings, or do I need to change something in the registry? What are the differences between the Touch Panel and Handwriting Service in Windows 8.1 and Windows 10? TIA.

UPDATE:

Please note that I have explored using the "On Screen Keyboard" which I believe is based on COM instead of the newer "Touchscreen Keyboard" but that did not help because ultimately because this COM keyboard requires admin privileges to close or minimize it. This is what I attempted with the "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;
        }
    }
}

UPDATE 2:

It looks like I may need to port my app from WPF to WinRT to get it to work on Windows 10: See https://docs.microsoft.com/en-us/windows/uwp/porting/ where it says

Move from WPF and Silverlight to WinRT

in

Related Topics

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM