如何让WPF输入控件在触摸屏上获得焦点时显示虚拟键盘

32
对于我们的WPF应用程序,在触摸屏幕上运行时(Surface Pro等),TextBox/PasswordBox控件在获取焦点时无法显示虚拟键盘。

有没有好的方法在WPF中实现这个功能?


更新:

最终我们想要实现的是:

如果用户在PC上运行应用程序,我们不关心这个功能,也就是说无论用户是否有物理键盘,我们像在PC上运行普通的WPF应用程序一样。

如果用户在Surface Pro上运行,当他点击TextBox时,内置的虚拟键盘会出现,并且应该更加用户友好,例如键盘永远不会覆盖输入元素。


更新2:

所以,WPF不能轻松设置某些属性来实现此功能吗?在我看来,这个功能应该是WPF内置的,我不明白为什么我找不到实现这个功能的简单方法。


你已经有虚拟键盘了吗?如果有,那么是哪一个?如果没有,我理解,你需要将焦点放在“TextBlock”上,显示虚拟键盘。 - Anatoliy Nikolaev
没有物理键盘的用户如何输入文本到您的应用程序中? - Eric Brown
2
@AnatoliyNikolaev 在这个阶段,我们没有自定义虚拟键盘,我们认为如果WPF提供了这个功能,我们可以节省很多工作。 - user1031200
1
@EricBrown 你好,我们最终想要实现的是这样的:如果用户在PC上运行应用程序,我们不关心这个功能,也就是说无论用户是否有物理键盘,我们都像普通的WPF应用程序一样运行。如果用户在Surface Pro上运行,当他点击TextBox时,内置的虚拟键盘可以显示出来,并且应该是用户友好的,例如键盘永远不会覆盖输入元素。 - user1031200
让我更明确一下。你如何区分 PC 和 Surface Pro? - Eric Brown
显示剩余6条评论
8个回答

31

试试这个:

首先检查是否有物理键盘存在:

KeyboardCapabilities keyboardCapabilities = new Windows.Devices.Input.KeyboardCapabilities();
return  keyboardCapabilities.KeyboardPresent != 0 ? true : false;
如果您找不到物理键盘,请使用Windows内置的虚拟键盘:
Process.Start(Environment.GetFolderPath(Environment.SpecialFolder.System) + Path.DirectorySeparatorChar + "osk.exe");

从这里得到了帮助: 链接1 链接2


1
那么WPF不能通过设置某些属性或非常少的其他设置来轻松实现这一点吗?实际上,我们还发布了另一个与osk.exe相关的问题(https://dev59.com/knfZa4cB1Zd3GeqPRGr9),总的来说,我们认为这不是一个好的解决方案,因为我们必须控制很多东西来满足用户友好的设计。 - user1031200
这确实是获得所需行为的唯一方法。如果您希望当前输入元素处于可视状态,则可以调整CurrentWindow的大小,以便不覆盖屏幕键盘。(实际上,创建具有所描述功能的自定义文本框非常容易且运行顺畅。在打开键盘之前,请确保检查操作系统是否为W8。) - Florian
10
这个命名空间仅存在于WinRT框架中。在普通的.NET WPF中有相应的等价物吗? - Mathias Becher
你如何设置键盘的位置? - lahjaton_j
有趣,什么是路径变量? - digitai
1
我认为“TabTip.exe”通常比“osk.exe”更好。 - 00jt

7
我已经发布了一份示例,演示如何在WPF应用程序中触发触摸键盘,当用户点击到一个文本框时,它在这里: http://code.msdn.microsoft.com/Enabling-Windows-8-Touch-7fb4e6de 我已经花费了数月时间来开发这个示例,我很高兴能够最终为我们的社区做出贡献。如果有任何问题、建议、问题等,请在示例Q&A窗格中告诉我。

3
工作得相当不错,但它会禁用像ScrollViewer这样的滚动控件。最终我使用EventManager.RegisterClassHandler()在GotFocus上启动了tabtip.exe。 - LMeyer

5
我创建了一个库,用于自动化WPF应用程序中与TabTip集成有关的所有事项。
您可以在nuget上获取它,之后您所需做的就是在应用程序启动逻辑中进行简单的配置:
TabTipAutomation.BindTo<TextBox>();

你可以将TabTip自动化逻辑绑定到任何UIElement上。当指定类型的任何元素获得焦点时,虚拟键盘将打开,并且当元素失去焦点时,它将关闭。不仅如此,TabTipAutomation还会将UIElement(或Window)移动到视图中,以便TabTip不会阻挡焦点元素。
有关更多信息,请参考项目网站

使用命名空间 using System.Windows.Controls; - Olorunfemi Davis
1
我必须添加以下内容才能使其正常工作:System.Reactive.Linq、System.Reactive.Core、System.Reactive.Interfaces和System.Reactive.Windows.Threading。 - Olorunfemi Davis

3
这个解决方案非常简单: http://code.msdn.microsoft.com/windowsapps/Enabling-Windows-8-Touch-7fb4e6de 上面的链接中详细介绍了步骤,这里给出简要版本:
  • Add a UIAutomationClient reference
  • Use IFrameworkInputPane from managed code (DLL at link or convert inputpanelconfiguration.idl to DLL, see steps below)
  • Create new class InkInputHelper to disable inking support (code below)
  • Call InkInputHelper.DisableWPFTabletSupport(); from MainWindow constructor or similar
  • Add using System.Windows.Interop;
  • Add to MainWindow_Loaded or similar:

        System.Windows.Automation.AutomationElement asForm =
        System.Windows.Automation.AutomationElement.FromHandle(new WindowInteropHelper(this).Handle);
        InputPanelConfigurationLib.InputPanelConfiguration inputPanelConfig = new InputPanelConfigurationLib.InputPanelConfiguration();
        inputPanelConfig.EnableFocusTracking();
    
将inputpanelconfiguration.idl转换为DLL
在Windows 8.1上: c:\Program Files (x86)\Windows Kits\8.1\Include\um\inputpanelconfiguration.idl
要从IDL构建DLL,请按照以下步骤进行:
- 启动命令提示符 - 使用MIDL编译器工具构建类型库TLB文件 - 示例:midl /tbld {filename} - 运行以下命令,使用TLBIMP工具将上述生成的类型库(TLB文件)转换为.NET可以使用的DLL - 示例:TLBIMP.exe InputpanelConfiguration.tlb /publickey:{pathToKey} /delaysign
InkInputHelper类:
using System;
using System.Reflection;
using System.Windows.Input;

namespace ModernWPF.Win8TouchKeyboard.Desktop
{
public static class InkInputHelper
{
    public static void DisableWPFTabletSupport()
    {
        // Get a collection of the tablet devices for this window.  
        TabletDeviceCollection devices = System.Windows.Input.Tablet.TabletDevices;

        if (devices.Count > 0)
        {
            // Get the Type of InputManager.
            Type inputManagerType = typeof(System.Windows.Input.InputManager);

            // Call the StylusLogic method on the InputManager.Current instance.
            object stylusLogic = inputManagerType.InvokeMember("StylusLogic",
                        BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
                        null, InputManager.Current, null);

            if (stylusLogic != null)
            {
                //  Get the type of the stylusLogic returned from the call to StylusLogic.
                Type stylusLogicType = stylusLogic.GetType();

                // Loop until there are no more devices to remove.
                while (devices.Count > 0)
                {
                    // Remove the first tablet device in the devices collection.
                    stylusLogicType.InvokeMember("OnTabletRemoved",
                            BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
                            null, stylusLogic, new object[] { (uint)0 });
                }
            }
        }
    }
}
}

这应该可以工作。在链接中有更好的信息和可下载的示例。我只是为了档案目的复制粘贴了基础知识。


2

1
@00jt 很有趣。你是否拥有最新的Windows 10版本? (1809) - itsho
1
@00jt 10.0.17134是版本1803(2018年4月更新)。我猜你启用了平板电脑模式。如果仍然无法工作,请尝试原始链接中的“Windows 10设置方法”部分。 - itsho
1
@00jt 我已在 Windows 10 1709 上进行了测试,并使用 4.6.2 进行了编译。它仅在您使用触摸屏时才能正常工作,如果您使用鼠标单击字段,则无法正常工作。我还在 PasswordBox 上进行了测试-请参见 gist 我刚刚创建的。 - itsho
1
@00jt 真的很奇怪 :-( 我在其他几台电脑和平板电脑上进行了测试,对于 TextBoxPasswordBox 都表现正常。出于好奇:你试过我提供的上面的示例吗? - itsho
2
@00jt 我真的不知道该说什么,但也许你可以在GitHub上利用微软的示例之一。 - itsho
显示剩余5条评论

1

你有TechEd会议的链接吗?不幸的是,EnableFocusTracking仅支持Win8或更高版本.. - Wouter

0
 public static class InkInputHelper
    {
        public static void DisableWPFTabletSupport()
        {
            // Get a collection of the tablet devices for this window.  
            TabletDeviceCollection devices = System.Windows.Input.Tablet.TabletDevices;

            if (devices.Count > 0)
            {
                // Get the Type of InputManager.
                Type inputManagerType = typeof(System.Windows.Input.InputManager);

                // Call the StylusLogic method on the InputManager.Current instance.
                object stylusLogic = inputManagerType.InvokeMember("StylusLogic",
                            BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
                            null, InputManager.Current, null);

                if (stylusLogic != null)
                {
                    //  Get the type of the stylusLogic returned from the call to StylusLogic.
                    Type stylusLogicType = stylusLogic.GetType();

                    // Loop until there are no more devices to remove.
                    while (devices.Count > 0)
                    {
                        // Remove the first tablet device in the devices collection.
                        stylusLogicType.InvokeMember("OnTabletRemoved",
                                BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
                                null, stylusLogic, new object[] { (uint)0 });
                    }
                }
            }
        }
    }

使用该类来确定是否存在物理键盘,或者有其他更适合您需求的方式。

我使用这个类在任何我想要的地方打开和关闭键盘。

 class KeyboardManager
    {

        public static void LaunchOnScreenKeyboard()
        {
            var processes = Process.GetProcessesByName("osk").ToArray();
            if (processes.Any())
                return;
            string keyboardManagerPath = "KeyboardExecuter.exe";
           Process.Start(keyboardManagerPath);
        }

        public static void KillOnScreenKeyboard()
        {
            var processes = Process.GetProcessesByName("osk").ToArray();
            foreach (var proc in processes)
            {
                proc.Kill();
            }
        }
        public static void killTabTip()
        {
            var processes = Process.GetProcessesByName("TabTip").ToArray();
            foreach (var proc in processes)
            {
                proc.Kill();
            }
        }

        public static void LaunchTabTip()
        {
            Process.Start("TabTip.exe");
        }
    }

请记住以下内容: 我添加了osk.exe和tabtip.exe的副本。 将其添加到我的程序中解决了在32/64位上无法使用tabtip或osk的问题。
osk是键盘,tabtip是其停靠版本。 keyboardexecuter是我制作的一个程序,用作备用方法。
注意*我目前无法在触摸屏设备上测试此功能。您必须自行尝试。
为了使所有内容正常工作,我在我的mainwindow中使用了以下代码:
public int selectedTableNum;
        public MainWindow()
        {
            InitializeComponent();

            Loaded += MainWindow_Loaded;

            // Disables inking in the WPF application and enables us to track touch events to properly trigger the touch keyboard
            InkInputHelper.DisableWPFTabletSupport();
            //remove navigationbar
            Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
            {
                var navWindow = Window.GetWindow(this) as NavigationWindow;
                if (navWindow != null) navWindow.ShowsNavigationUI = false;
            }));


            KeyboardManager.LaunchTabTip();

        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //Windows 8 API to enable touch keyboard to monitor for focus tracking in this WPF application
            InputPanelConfiguration cp = new InputPanelConfiguration();
            IInputPanelConfiguration icp = cp as IInputPanelConfiguration;
            if (icp != null)
                icp.EnableFocusTracking();
            mainFrame.Content = new LoginPage();
        }
        //public bool HasTouchInput()
        //{
        //    foreach (TabletDevice tabletDevice in Tablet.TabletDevices)
        //    {
        //        //Only detect if it is a touch Screen not how many touches (i.e. Single touch or Multi-touch)
        //        if (tabletDevice.Type == TabletDeviceType.Touch)
        //            return true;
        //    }

        //    return false;
        //}

我包含了注释,因为如果有错误的话,它可能对某些人有用。

输入面板配置:

[Guid("41C81592-514C-48BD-A22E-E6AF638521A6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInputPanelConfiguration
{
    /// <summary>
    /// Enables a client process to opt-in to the focus tracking mechanism for Windows Store apps that controls the invoking and dismissing semantics of the touch keyboard.
    /// </summary>
    /// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.</returns>
    int EnableFocusTracking();
}

[ComImport, Guid("2853ADD3-F096-4C63-A78F-7FA3EA837FB7")]
class InputPanelConfiguration
{
}

我希望这能帮助到未来访问此问题的访客。


返回"S_OK"不再使用,如果需要的话,我会尝试找到它。 - ThrowingDwarf

-2
最简单的方法是使用TextBox,并使用类似Wingdings字体的方式,如果不需要SecureString输出。

1
这个(解决方法)如何回答问题并不清楚。请详细说明,并最好提供一个例子。 - Charlie Joynt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接