在 Windows 10 周年纪念版中显示触摸键盘 (TabTip.exe)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38774139/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 19:38:32  来源:igfitidea点击:

Show touch keyboard (TabTip.exe) in Windows 10 Anniversary edition

windowswinapitouchwindows-10windows-10-desktop

提问by EugeneK

In Windows 8 and Windows 10 before Anniversary update it was possible to show touch keyboard by starting

在周年更新之前的 Windows 8 和 Windows 10 中,可以通过启动来显示触摸键盘

C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe

It no longer works in Windows 10 Anniversary update; the TabTip.exeprocess is running, but the keyboard is not shown.

它不再适用于 Windows 10 周年更新;该TabTip.exe进程正在运行,但键盘没有显示。

Is there a way to show it programmatically?

有没有办法以编程方式显示它?

UPDATE

更新

I found a workaround - fake mouse click on touch keyboard icon in system tray. Here is code in Delphi

我找到了一个解决方法 - 假鼠标点击系统托盘中的触摸键盘图标。这是Delphi中的代码

// Find tray icon window
function FindTrayButtonWindow: THandle;
var
  ShellTrayWnd: THandle;
  TrayNotifyWnd: THandle;
begin
  Result := 0;
  ShellTrayWnd := FindWindow('Shell_TrayWnd', nil);
  if ShellTrayWnd > 0 then
  begin
    TrayNotifyWnd := FindWindowEx(ShellTrayWnd, 0, 'TrayNotifyWnd', nil);
    if TrayNotifyWnd > 0 then
    begin
      Result := FindWindowEx(TrayNotifyWnd, 0, 'TIPBand', nil);
    end;
  end;
end;

// Post mouse click messages to it
TrayButtonWindow := FindTrayButtonWindow;
if TrayButtonWindow > 0 then
begin
  PostMessage(TrayButtonWindow, WM_LBUTTONDOWN, MK_LBUTTON, 
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TabletTip.7\EnableDesktopModeAutoInvoke=1
010001); PostMessage(TrayButtonWindow, WM_LBUTTONUP, 0,
#include <initguid.h>
#include <Objbase.h>
#pragma hdrstop

// 4ce576fa-83dc-4F88-951c-9d0782b4e376
DEFINE_GUID(CLSID_UIHostNoLaunch,
    0x4CE576FA, 0x83DC, 0x4f88, 0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, 0xE3, 0x76);

// 37c994e7_432b_4834_a2f7_dce1f13b834b
DEFINE_GUID(IID_ITipInvocation,
    0x37c994e7, 0x432b, 0x4834, 0xa2, 0xf7, 0xdc, 0xe1, 0xf1, 0x3b, 0x83, 0x4b);

struct ITipInvocation : IUnknown
{
    virtual HRESULT STDMETHODCALLTYPE Toggle(HWND wnd) = 0;
};

int WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HRESULT hr;
    hr = CoInitialize(0);

    ITipInvocation* tip;
    hr = CoCreateInstance(CLSID_UIHostNoLaunch, 0, CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER, IID_ITipInvocation, (void**)&tip);
    tip->Toggle(GetDesktopWindow());
    tip->Release();
    return 0;
}
010001); end;

UPDATE 2

更新 2

Another thing I found is that setting this registry key restores old functionality when starting TabTip.exe shows touch keyboard

我发现的另一件事是设置此注册表项会在启动 TabTip.exe 显示触摸键盘时恢复旧功能

class Program
{
    static void Main(string[] args)
    {
        var uiHostNoLaunch = new UIHostNoLaunch();
        var tipInvocation = (ITipInvocation)uiHostNoLaunch;
        tipInvocation.Toggle(GetDesktopWindow());
        Marshal.ReleaseComObject(uiHostNoLaunch);
    }

    [ComImport, Guid("4ce576fa-83dc-4F88-951c-9d0782b4e376")]
    class UIHostNoLaunch
    {
    }

    [ComImport, Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface ITipInvocation
    {
        void Toggle(IntPtr hwnd);
    }

    [DllImport("user32.dll", SetLastError = false)]
    static extern IntPtr GetDesktopWindow();
}

回答by torvin

OK, I reverse engineered what explorer does when the user presses that button in the system tray.

好的,我逆向设计了当用户按下系统托盘中的那个按钮时资源管理器会做什么。

Basically it creates an instance of an undocumented interface ITipInvocationand calls its Toggle(HWND)method, passing desktop window as an argument. As the name suggests, the method either shows or hides the keyboard depending on its current state.

基本上它创建一个未记录接口的实例ITipInvocation并调用它的Toggle(HWND)方法,将桌面窗口作为参数传递。顾名思义,该方法根据当前状态显示或隐藏键盘。

Please notethat explorer creates an instance of ITipInvocationon every button click. So I believe the instance should not be cached. I also noticed that explorer never calls Release()on the obtained instance. I'm not too familiar with COM, but this looks like a bug.

请注意,资源管理器ITipInvocation在每次单击按钮时都会创建一个实例。所以我认为不应该缓存该实例。我还注意到资源管理器从不调用Release()获得的实例。我对 COM 不太熟悉,但这看起来像一个错误。

I tested this in Windows 8.1, Windows 10 & Windows 10 Anniversary Edition and it works perfectly. Here's a minimal example in C that obviously lacks some error checks.

我在 Windows 8.1、Windows 10 和 Windows 10 周年纪念版中对此进行了测试,并且运行良好。这是 C 语言中的一个最小示例,它显然缺少一些错误检查。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr FindWindow(string sClassName, string sAppName);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle); 

[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
    static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);

var trayWnd = FindWindow("Shell_TrayWnd", null);
var nullIntPtr = new IntPtr(0);

if (trayWnd != nullIntPtr)
{
    var trayNotifyWnd = FindWindowEx(trayWnd, nullIntPtr, "TrayNotifyWnd", null);
    if (trayNotifyWnd != nullIntPtr)
    {
        var tIPBandWnd = FindWindowEx(trayNotifyWnd, nullIntPtr, "TIPBand", null);

        if (tIPBandWnd != nullIntPtr)
        {
            PostMessage(tIPBandWnd, (UInt32)WMessages.WM_LBUTTONDOWN, 1, 65537);
            PostMessage(tIPBandWnd, (UInt32)WMessages.WM_LBUTTONUP, 1, 65537);
        }
    }
}


public enum WMessages : int
{
    WM_LBUTTONDOWN = 0x201,
    WM_LBUTTONUP = 0x202,
    WM_KEYDOWN = 0x100,
    WM_KEYUP = 0x101,
    WH_KEYBOARD_LL = 13,
    WH_MOUSE_LL = 14,
}

Here's the C# version as well:

这也是 C# 版本:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;

using Osklib.Interop;
using System.Runtime.InteropServices;
using System.Threading;

namespace OSK
{
    public static class OnScreenKeyboard
    {
        static OnScreenKeyboard()
        {
            var version = Environment.OSVersion.Version;
            switch (version.Major)
            {
                case 6:
                    switch (version.Minor)
                    {
                        case 2:
                            // Windows 10 (ok)
                            break;
                    }
                    break;
                default:
                    break;
            }
        }

        private static void StartTabTip()
        {
            var p = Process.Start(@"C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe");
            int handle = 0;
            while ((handle = NativeMethods.FindWindow("IPTIP_Main_Window", "")) <= 0)
            {
                Thread.Sleep(100);
            }
        }

        public static void ToggleVisibility()
        {
            var type = Type.GetTypeFromCLSID(Guid.Parse("4ce576fa-83dc-4F88-951c-9d0782b4e376"));
            var instance = (ITipInvocation)Activator.CreateInstance(type);
            instance.Toggle(NativeMethods.GetDesktopWindow());
            Marshal.ReleaseComObject(instance);
        }

        public static void Show()
        {
            int handle = NativeMethods.FindWindow("IPTIP_Main_Window", "");
            if (handle <= 0) // nothing found
            {
                StartTabTip();                
                Thread.Sleep(100);                
            }
            // on some devices starting TabTip don't show keyboard, on some does  ˉ\_(ツ)_/ˉ
            if (!IsOpen())
            {
                ToggleVisibility();
            }
        }

        public static void Hide()
        {
            if (IsOpen())
            {
                ToggleVisibility();
            }
        }        


        public static bool Close()
        {
            // find it
            int handle = NativeMethods.FindWindow("IPTIP_Main_Window", "");
            bool active = handle > 0;
            if (active)
            {
                // don't check style - just close
                NativeMethods.SendMessage(handle, NativeMethods.WM_SYSCOMMAND, NativeMethods.SC_CLOSE, 0);
            }
            return active;
        }

        public static bool IsOpen()
        {
            return GetIsOpen1709() ?? GetIsOpenLegacy();
        }


        [DllImport("user32.dll", SetLastError = false)]
        private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr after, string className, string title = null);

        [DllImport("user32.dll", SetLastError = false)]
        private static extern uint GetWindowLong(IntPtr wnd, int index);

        private static bool? GetIsOpen1709()
        {
            // if there is a top-level window - the keyboard is closed
            var wnd = FindWindowEx(IntPtr.Zero, IntPtr.Zero, WindowClass1709, WindowCaption1709);
            if (wnd != IntPtr.Zero)
                return false;

            var parent = IntPtr.Zero;
            for (;;)
            {
                parent = FindWindowEx(IntPtr.Zero, parent, WindowParentClass1709);
                if (parent == IntPtr.Zero)
                    return null; // no more windows, keyboard state is unknown

                // if it's a child of a WindowParentClass1709 window - the keyboard is open
                wnd = FindWindowEx(parent, IntPtr.Zero, WindowClass1709, WindowCaption1709);
                if (wnd != IntPtr.Zero)
                    return true;
            }
        }

        private static bool GetIsOpenLegacy()
        {
            var wnd = FindWindowEx(IntPtr.Zero, IntPtr.Zero, WindowClass);
            if (wnd == IntPtr.Zero)
                return false;

            var style = GetWindowStyle(wnd);
            return style.HasFlag(WindowStyle.Visible)
                && !style.HasFlag(WindowStyle.Disabled);
        }

        private const string WindowClass = "IPTip_Main_Window";
        private const string WindowParentClass1709 = "ApplicationFrameWindow";
        private const string WindowClass1709 = "Windows.UI.Core.CoreWindow";
        private const string WindowCaption1709 = "Microsoft Text Input Application";

        private enum WindowStyle : uint
        {
            Disabled = 0x08000000,
            Visible = 0x10000000,
        }

        private static WindowStyle GetWindowStyle(IntPtr wnd)
        {
            return (WindowStyle)GetWindowLong(wnd, -16);
        }

    }


    [ComImport]
    [Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface ITipInvocation
    {
        void Toggle(IntPtr hwnd);
    }

    internal static class NativeMethods
    {
        [DllImport("user32.dll", EntryPoint = "FindWindow")]
        internal static extern int FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll", EntryPoint = "SendMessage")]
        internal static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);

        [DllImport("user32.dll", EntryPoint = "GetDesktopWindow", SetLastError = false)]
        internal static extern IntPtr GetDesktopWindow();

        [DllImport("user32.dll", EntryPoint = "GetWindowLong")]
        internal static extern int GetWindowLong(int hWnd, int nIndex);

        internal const int GWL_STYLE = -16;
        internal const int GWL_EXSTYLE = -20;        
        internal const int WM_SYSCOMMAND = 0x0112;
        internal const int SC_CLOSE = 0xF060;

        internal const int WS_DISABLED = 0x08000000;

        internal const int WS_VISIBLE = 0x10000000;

    }
}

Update:per @EugeneK comments, I believe that tabtip.exeis the COM server for the COM component in question, so if your code gets REGDB_E_CLASSNOTREG, it should probably run tabtip.exeand try again.

更新:根据@EugeneK 评论,我相信这tabtip.exe是有问题的 COM 组件的 COM 服务器,所以如果您的代码得到REGDB_E_CLASSNOTREG,它可能应该运行并重tabtip.exe试。

回答by mikesl

The only solution I've found to work is by sending PostMessage as you've mentioned in answer 1. Here's the C# version of it in case someone needs it.

我发现唯一可行的解​​决方案是发送您在答案 1 中提到的 PostMessage。这是它的 C# 版本,以防有人需要它。

//*******************************************************************
//
// RETURNS KEYBOARD RECTANGLE OR EMPTY ONE IF KEYBOARD IS NOT VISIBLE
//
//*******************************************************************
RECT __stdcall  GetKeyboardRect()
{
    IFrameworkInputPane *inputPane = NULL;
    RECT prcInputPaneScreenLocation = { 0,0,0,0 };
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);    
    if (SUCCEEDED(hr))
    {
        hr = CoCreateInstance(CLSID_FrameworkInputPane, NULL, CLSCTX_INPROC_SERVER, IID_IFrameworkInputPane, (LPVOID*)&inputPane);
        if (SUCCEEDED(hr))
        {
            hr=inputPane->Location(&prcInputPaneScreenLocation);
            if (!SUCCEEDED(hr))
            {                   
            }
            inputPane->Release();
        }
    }       
    CoUninitialize();   
    return prcInputPaneScreenLocation;
}

回答by Alexei Shcherbakov

I detect 4 situations when trying to open Touch Keyboard on Windows 10 Anniversary Update

我在 Windows 10 周年更新中尝试打开触控键盘时检测到 4 种情况

  1. Keyboard is Visible - when "IPTIP_Main_Window" is present, NOTdisabled and ISvisible
  2. Keyboard is not visible - when "IPTIP_Main_Window" is present but disabled
  3. Keyboard is not visible - when "IPTIP_Main_Window" is present but NOTdisabled and NOTvisible
  4. Keyboard is not visible - when "IPTIP_Main_Window" is NOTpresent
  1. 键盘是可见的-当“IPTIP_Main_Window”存在,不是残疾人和IS可见
  2. 键盘不可见 - 当“IPTIP_Main_Window”存在但被禁用时
  3. 键盘是不可见的-当“IPTIP_Main_Window”存在,但禁止,可见
  4. 键盘不可见 - 当“IPTIP_Main_Window”存在时

1 - nothing to do

1 - 无事可做

2+3 - activating via COM

2+3 - 通过 COM 激活

4 - most interesting scenario. In some devices starting TabTip process opens touch keyboard, on some - not. So we must start TabTip process, wait for appearing window "IPTIP_Main_Window", check it for visibility and activate it via COM if nessesary.

4 - 最有趣的场景。在某些设备上启动 TabTip 进程会打开触摸键盘,而在某些设备上 - 不会。所以我们必须启动 TabTip 进程,等待出现窗口“IPTIP_Main_Window”,检查它的可见性并在必要时通过 COM 激活它。

I make small library for my project, you can use it - osklib

我为我的项目制作了一个小库,你可以使用它 - osklib

回答by lama

I had the same problem too. It took me much time and headache, but Thanks to Alexei and Torvin I finally got it working on Win 10 1709. Visibility check was the difficulty. Maybe The OSKlib Nuget could be updated. Let me sum up the complete sulotion (For sure my code has some unnecessary lines now):

我也有同样的问题。这花了我很多时间和头痛,但多亏了 Alexei 和 Torvin,我终于让它在 Win 10 1709 上工作了。可见性检查是困难的。也许 OSKlib Nuget 可以更新。让我总结一下完整的 solotion(现在我的代码肯定有一些不必要的行):

private void button1_Click(object sender, EventArgs e)
{
    var simu = new InputSimulator();
    simu.Keyboard.ModifiedKeyStroke(new[] { VirtualKeyCode.LWIN, VirtualKeyCode.CONTROL }, VirtualKeyCode.VK_O);
}

回答by Usul

There is still some mystery about how the touch keyboard is set visible by Windows 10 Anniversary Update. I'm actually having the exact same issue and here are the lastest infos i've found :

关于如何通过 Windows 10 周年更新设置可见触摸键盘仍然存在一些谜团。我实际上遇到了完全相同的问题,这是我找到的最新信息:

  • Windows 10 1607 works in two modes : Desktop and Tablet. While in Desktop mode, TabTip.exe can be called but won't show. While in Tablet mode, everything works fine : TabTip.exe shows itself when called. So a 100% working workaround is to set your computer in Tablet Mode but who wants his desktop/laptop to work in tablet mode ? Not me anyway !

  • You can use the "EnableDesktopModeAutoInvoke" Key (HKCU, DWORD set to 1) and on some computers running 1607 it worked great while in Desktop Mode. But for some unknown reasons, it is not working on my HP touchpad.

  • Windows 10 1607 在两种模式下工作:桌面和平板电脑。在桌面模式下,TabTip.exe 可以被调用但不会显示。在平板电脑模式下,一切正常:TabTip.exe 在调用时显示自身。所以 100% 的解决方法是将您的计算机设置为平板电脑模式,但谁希望他的台式机/笔记本电脑在平板电脑模式下工作?反正不是我!

  • 您可以使用“ EnableDesktopModeAutoInvoke”键(HKCU,DWORD 设置为 1),并且在某些运行 1607 的计算机上,它在桌面模式下运行良好。但由于某些未知原因,它在我的 HP 触摸板上不起作用。

Please note that this registry value is the "Show touch keyboard on desktop mode if there is no attached keyboard" option in Windows parameters > touch

请注意,此注册表值是 Windows 参数 > 触摸中的“如果没有附加键盘,则在桌面模式下显示触摸键盘”选项

  • You can use Torvin's code to show TabTip.exe (as mentioned TabTip.exe should be running when you do the COM stuff), it is working fine on some computers running 1607 (including my HP touchpad ! yay !) But it will do nothing on some others comps with the same windows Build.
  • 您可以使用 Torvin 的代码来显示 TabTip.exe(如前所述,当您执行 COM 操作时,TabTip.exe 应该会运行),它在某些运行 1607 的计算机上运行良好(包括我的 HP 触摸板!耶!)但它什么也不做在其他一些具有相同 Windows Build 的组件上。

So far tested on 4 different computers and i'm unable to get something working fine on all...

到目前为止,已经在 4 台不同的计算机上进行了测试,但我无法在所有计算机上都能正常工作......

回答by tombam

Implementing the IValueProvider/ITextProvider in your control is a correct way to achieve this, as described here: https://stackoverflow.com/a/43886052/1184950

在您的控件中实现 IValueProvider/ITextProvider 是实现此目的的正确方法,如下所述:https://stackoverflow.com/a/43886052/1184950

回答by user3480038

The problem seems to be with setting of Windows OS. I have faced same issue with the app I was developing. With Windows 8 and 10 (before update) code that called keyboard worked fine, but after update failed to work. After reading this article, I did following:

问题似乎与 Windows 操作系统的设置有关。我正在开发的应用程序遇到了同样的问题。使用 Windows 8 和 10(更新前)调用键盘的代码工作正常,但更新后无法工作。读完这篇文章后,我做了以下事情:

  1. Pressed Win+I to open the Settings app

  2. Clicked on Devices > Typing

  3. Turned "Automatically show the touch keyboard in windowed apps when there's no keyboard attached to your device" ON.

    Right after that keyboard starting showing up in Windows 10 also.

  1. 按 Win+I 打开设置应用程序

  2. 单击“设备”>“键入”

  3. 打开“当您的设备没有连接键盘时,在窗口应用程序中自动显示触摸键盘”开启。

    在那个键盘也开始出现在 Windows 10 之后。

回答by Sevast

The following code will always work since it uses latest MS Api
I put it into a dll (Needed for a Delphi project) but it is a plain C
Also useful for obtaining the keyboard size and adjusting application layout

以下代码将始终有效,因为它使用最新的 MS Api
我将它放入一个 dll(Delphi 项目需要)但它是一个普通的 C
也可用于获取键盘大小和调整应用程序布局

using WindowsInput;
using WindowsInput.Native;

回答by Norm McGeeney

I tried multiple things that did not work. But I discovered that I can use the Shortcut Keys Windows/Ctrl/O to open the On Screen Key Board.
Also there is a Nuget package: Input Simulator by Michael Noonan.

我尝试了多种无效的方法。但我发现我可以使用快捷键 Windows/Ctrl/O 打开屏幕键盘。
还有一个 Nuget 包:Michael Noonan 的 Input Simulator。

If you install the InputSimulator NuGet package in your Winforms project - then add code like this to an event, like a button:

如果您在 Winforms 项目中安装 InputSimulator NuGet 包 - 然后将这样的代码添加到事件中,例如按钮:

##代码##

You will also need to add these using statements:

您还需要添加这些 using 语句:

##代码##

Run your app and the button will display the keyboard and also hit it again and it will remove it.

运行您的应用程序,按钮将显示键盘并再次点击它,它将删除它。

I am on Windows 10 and vs 2019.

我使用的是 Windows 10 和 2019。

回答by Vinny

Use this method:

使用这个方法:

  1. Create osk.bat file and save it under your program folder ie. C:\My Software\osk.bat

  2. Type in this osk.bat the following cmd:

    "C:\Program Files\Common Files\Microsoft Shared\Ink\Tabtip.exe"

  3. Use Windows Script to run this bat file

    oWSH = CREATEOBJECT("wscript.shell")

    oWSH.Run("osk.bat", 0, .T.)

  1. 创建 osk.bat 文件并将其保存在您的程序文件夹下,即。 C:\My Software\osk.bat

  2. 在此 osk.bat 中输入以下 cmd:

    "C:\Program Files\Common Files\Microsoft Shared\Ink\Tabtip.exe"

  3. 使用 Windows 脚本运行这个 bat 文件

    oWSH = CREATEOBJECT("wscript.shell")

    oWSH.Run("osk.bat", 0, .T.)