可以使用c:\program files\common files\microsoft shared\ink\tabtip.exe
(命令打开触摸键盘来源)。
它在 Windows 10 中运行良好,但我无法在 Windows 11 下使用它(屏幕键盘不显示)。但我没有收到任何错误消息(tabtip.exe 作为进程运行),所以我假设发生了其他事情。有人可以验证这种行为吗?
PS 最终目标是为触摸键盘创建键盘快捷键
添加01:我发现了这个问题:当触摸键盘关闭时(例如通过 X),tabtip.exe 任务不会关闭,再次启动 tabtip.exe 不会有任何反应,因为任务已经在运行。提前结束任务可能是一个解决方案,但它需要提升的 shell,因此键盘快捷键有点过时了。任何关于如何解决这个问题的想法都值得赞赏!
答案1
警告:此答案中描述的技术依赖于 Windows Shell 的未记录的实现细节,例如未记录的 COM 方法和未记录的进程名称。它可能会随时因任何更新而中断。Microsoft 不鼓励使用未记录的行为,但据我所知,目前不支持以编程方式打开触摸键盘。
在 Stack Overflow 上的两个答案中,@托文和@Andrea S.描述如何以编程方式打开触摸键盘和如何确定输入窗格(触摸键盘或手写面板)当前是否打开。
我们可以使用他们的方法来创建一个切换触摸键盘的 PowerShell 脚本:
# A script to toggle the Touch Keyboard of Windows 11,
# compatible with both Windows PowerShell and PowerShell 7.
# Based on code by @torvin (https://stackoverflow.com/users/332528/torvin): https://stackoverflow.com/a/40921638
# Based on code by @Andrea S. (https://stackoverflow.com/users/5887913/andrea-s): https://stackoverflow.com/a/55513524
# Warning: Relies on undocumented behaviour of the Windows Shell
# and may break with any update.
# Last tested on Windows 11 Home 22000.978.
Add-Type -ReferencedAssemblies $(if ($PSVersionTable.PSEdition -eq "Desktop") {"System.Drawing.dll"} else {$null}) -Language CSharp -TypeDefinition @'
using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
public class TouchKeyboardController
{
public static void ToggleTouchKeyboard()
{
try
{
UIHostNoLaunch uiHostNoLaunch = new UIHostNoLaunch();
((ITipInvocation)uiHostNoLaunch).Toggle(GetDesktopWindow());
Marshal.ReleaseComObject(uiHostNoLaunch);
}
catch (COMException exc)
{
if (exc.HResult == unchecked((int)0x80040154)) // REGDB_E_CLASSNOTREG
{
ProcessStartInfo processStartInfo = new ProcessStartInfo("TabTip.exe")
{
UseShellExecute = true
};
using (Process process = Process.Start(processStartInfo))
{
}
}
else
{
throw;
}
}
}
[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();
public static bool IsInputPaneOpen()
{
FrameworkInputPane frameworkInputPane = new FrameworkInputPane();
Rectangle rect;
((IFrameworkInputPane)frameworkInputPane).Location(out rect);
Marshal.ReleaseComObject(frameworkInputPane);
return !rect.IsEmpty;
}
[ComImport, Guid("d5120aa3-46ba-44c5-822d-ca8092c1fc72")]
public class FrameworkInputPane
{
}
[ComImport, Guid("5752238b-24f0-495a-82f1-2fd593056796")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFrameworkInputPane
{
int Advise([MarshalAs(UnmanagedType.IUnknown)] object pWindow, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
int AdviseWithHWND(IntPtr hwnd, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
int Unadvise(int pdwCookie);
int Location(out Rectangle prcInputPaneScreenLocation);
}
}
'@
# Toggle the Touch Keyboard regardless of whether it is currently shown or not.
[TouchKeyboardController]::ToggleTouchKeyboard()
# Alternatively, if you only want to show the Touch Keyboard
# and not hide it if it is already active:
# if (-not [TouchKeyboardController]::IsInputPaneOpen()) {
# [TouchKeyboardController]::ToggleTouchKeyboard()
# }
该脚本的工作方式如下:如果 TabTip.exe 未运行,我们通过 启动它ShellExecute
,这样触摸键盘就会出现。如果 TabTip.exe 已在运行,我们调用ITipInvocation.Toggle()
来切换触摸键盘的显示状态。(TabTip.exe 似乎是 UIHostNoLaunch 的某种 COM 服务器。)
如果您希望脚本只打开触摸键盘而不切换它(即,当它已经显示时不隐藏它),您首先必须检查触摸键盘是否已处于活动状态。幸运的是,有一个支持此功能的 API:。IFrameworkInputPane.Location()
如果已经有一个活动的输入窗格,我们可以使用它来中止(请参阅脚本中的注释)。
要创建用于切换触摸键盘的键盘快捷键,您可以将上述脚本保存为ToggleTouchKeyboard.ps1
,然后为该脚本创建桌面快捷方式,最后为桌面快捷方式分配键盘快捷键(通过右键单击桌面快捷方式>属性)。
如果您无法在系统上使用 PowerShell/.NET(因为它已被管理员出于安全原因阻止),那么您必须在安装了 Visual Studio 的计算机上编译一个直接调用 Windows API 的小型 C++ 程序:
#include <iostream>
#include <initguid.h>
#include <Objbase.h>
#include <Shobjidl.h>
// 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;
};
using namespace std;
/// <summary>
/// Determines whether an input pane (Touch Keyboard or handwriting Panel) is currently open.
/// </summary>
/// <returns>
/// If the function succeeded and an input pane is currently open, S_OK is retured.
/// If the function succeeded and no input pane is currently open, S_FALSE is returnd.
///
/// Otherwise, another error code is returned.
/// </returns>
HRESULT IsInputPaneOpen()
{
RECT rect;
ZeroMemory(&rect, sizeof(rect));
IFrameworkInputPane* frameworkInputPane{ nullptr };
HRESULT hr{ CoCreateInstance(CLSID_FrameworkInputPane, NULL, CLSCTX_INPROC_SERVER, IID_IFrameworkInputPane, (void**)&frameworkInputPane)};
if (SUCCEEDED(hr))
{
hr = frameworkInputPane->Location(&rect);
if (SUCCEEDED(hr))
{
hr = IsRectEmpty(&rect) ? S_FALSE : S_OK;
}
frameworkInputPane->Release();
}
return hr;
}
int main()
{
HRESULT hr { CoInitialize(NULL) };
if (FAILED(hr))
{
wcerr << L"Failed to initialize COM." << endl;
return 1;
}
// Toggle the Touch Keyboard regardless of whether it is currently shown or not.
// To only show the Touch Keyboard and not hide it if it is already active,
// abort here if IsInputPaneOpen() == S_OK.
ITipInvocation* tip{ nullptr };
hr = CoCreateInstance(CLSID_UIHostNoLaunch, NULL, CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER, IID_ITipInvocation, (void**)&tip);
if (hr == REGDB_E_CLASSNOTREG)
{
INT_PTR result = (INT_PTR)ShellExecuteW(NULL, NULL, L"TabTip.exe", NULL, NULL, SW_SHOWNORMAL);
if (result > 32)
{
wcout << L"Started TabTip.exe to open Touch Keyboard." << endl;
}
else
{
wcerr << L"Failed to start TabTip.exe. Error: " << result << endl;
}
}
else if (SUCCEEDED(hr))
{
HWND desktopWindow = GetDesktopWindow();
hr = tip->Toggle(desktopWindow);
if (SUCCEEDED(hr))
{
wcout << L"Toggled the touch keyboard via ITipInvocation.Toggle()." << endl;
}
else
{
wcerr << L"Failed to toggle the Touch Keyboard via ITipInvocation.Toggle()." << endl;
}
tip->Release();
}
CoUninitialize();
}