36

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

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

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

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

更新

我找到了一种解决方法 - 假鼠标点击系统托盘中的触摸键盘图标。这是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, $00010001);
  PostMessage(TrayButtonWindow, WM_LBUTTONUP, 0, $00010001);
end;

更新 2

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

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TabletTip\1.7\EnableDesktopModeAutoInvoke=1
4

11 回答 11

31

好的,当用户按下系统托盘中的那个按钮时,我对资源管理器的操作进行了逆向工程。

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

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

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

#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;
}

这也是 C# 版本:

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

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

于 2016-12-01T22:34:12.813 回答
8

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

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;

    }
}
于 2018-01-31T14:54:41.703 回答
6

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

  1. 键盘可见 - 当“ IPTIP_Main_Window ”存在时,未禁用且可见
  2. 键盘不可见 - 当“IPTIP_Main_Window”存在但禁用时
  3. 键盘不可见 - 当“ IPTIP_Main_Window ”存在但未禁用且不可见时
  4. 键盘不可见 - 当“IPTIP_Main_Window”存在时

1 - 无事可做

2+3 - 通过 COM 激活

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

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

于 2017-04-04T15:14:50.687 回答
6

我发现唯一可行的解​​决方案是发送您在答案 1 中提到的 PostMessage。这是它的 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,
}
于 2016-09-08T07:55:59.913 回答
5

以下代码将始终有效,因为它使用最新的 MS Api
我将其放入 dll 中(Delphi 项目需要),但它是纯 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;
}
于 2018-06-24T09:20:12.983 回答
4

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

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

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

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

  • 您可以使用 Torvin 的代码来显示 TabTip.exe(正如提到的那样,当您执行 COM 操作时 TabTip.exe 应该运行),它在一些运行 1607 的计算机上运行良好(包括我的 HP 触摸板!耶!)但它什么也不做在其他一些具有相同 Windows 构建的组合上。

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

于 2017-03-13T16:11:01.777 回答
3

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

于 2017-05-10T07:57:43.540 回答
3

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

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

  2. 点击设备>打字

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

    紧接着该键盘也开始出现在 Windows 10 中。

于 2017-03-09T13:40:49.103 回答
1

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

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

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

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

using WindowsInput;
using WindowsInput.Native;

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

我在 Windows 10 和 vs 2019 上。

于 2019-11-07T22:15:44.640 回答
0

使用此方法:

  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.)

于 2018-02-06T16:46:37.417 回答
-2

在 Win10 Ver 1803 的桌面模式中,没有可靠的方法来
打开|关闭“触摸键盘”[ ITipInvocation.Toggle() ];
你也不能可靠地发现它是否“向上”(在屏幕上)
[IFrameworkInputPane.Location()];这两个例程都随机失败。

相反,请确保“TabTIP.EXE”和“....InputApp.EXE”
仅在键盘“向上”(在屏幕上)时运行。

要打开和关闭键盘(来自 Jeff-Relf.Me/X.ZIP 中的 X.CPP):

if ( WM == WM_HOTKEY && C == 'K' ) {

  //  A mouse button takes me here.  Jeff-Relf.Me/g600.PNG

  if ( KillProc = 1, Running( L"TabTIP.EXE" ), KillProc = 1, Running( 
      L"WindowsInternal.ComposableShell.Experiences.TextInput.InputApp.EXE"
  ) )  

    //  The keyboard was _On_ ( i.e. its processes were running ), 
    //  so it was "turned _Off_" (killed); and we're done.

    goto Done ;

  //  The keyboard was _Off_ ( i.e. no running processes ). 
  //  Turn it _On_: 

  Launch( L"%CommonProgramFiles%/microsoft shared/ink/TabTIP.EXE" );
  Sleep(99);

  static const GUID CLSID_UIHostNoLaunch = {  0x4CE576FA, 0x83DC,
    0x4f88,  0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, 0xE3, 0x76 };
  static const GUID IID_ITipInvocation = {  0x37c994e7, 0x432b,
    0x4834, 0xa2, 0xf7, 0xdc, 0xe1, 0xf1, 0x3b, 0x83, 0x4b };
  static struct ITipInvocation : IUnknown {  virtual HRESULT 
    STDMETHODCALLTYPE  Toggle( HWND wnd ) = 0 ;  }  * Tog ;

  Tog = 0, CoCreateInstance( CLSID_UIHostNoLaunch, 0, CLSCTX_INPROC_HANDLER
    | CLSCTX_LOCAL_SERVER, IID_ITipInvocation, (void**) & Tog );

  //  Firefox and Chrome need this: 

  Tog ? Tog->Toggle( GetDesktopWindow() ), Tog->Release() : 0 ;    }
- - - - - - - - - - - - -
//  To get the process list, and kill stuff: 
#include <tlhelp32.H>

      int  KillProc ;
int Running( wchar * EXE ) {  int  Found ;  HANDLE  PIDs, aProc ;
  PROCESSENTRY32  aPID = { sizeof aPID };
  PIDs = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
  Process32First( PIDs, &aPID );
  while ( Found = !strCmpI( aPID.szExeFile, EXE ),
    KillProc && Found && ( 
      aProc = OpenProcess( PROCESS_TERMINATE, 0, aPID.th32ProcessID ),
      aProc ? TerminateProcess( aProc, 9 ), CloseHandle( aProc ) : 0 ),
    !Found && Process32Next( PIDs, &aPID ) );

  KillProc = 0, CloseHandle( PIDs );  return  Found ;   }


Launch( wchar * Cmd ) {  wchar _Cmd[333];  static PROCESS_INFORMATION Stat ;
  static STARTUPINFO SU = { sizeof SU };
  SetEnvironmentVariable( L"__compat_layer", L"RunAsInvoker" );
  ExpandEnvironmentStrings( Cmd, _Cmd, 333 ), Cmd = _Cmd ;
  if ( CreateProcess( 0, Cmd, 0,0,1,0,0,0, &SU , &Stat ) )
    CloseHandle( Stat.hProcess ), CloseHandle( Stat.hThread );  }

//  CoInitialize(0);
于 2018-07-17T07:53:22.867 回答