在 Windows 的平板模式下才能自动在获取键盘输入焦点时弹出屏幕键盘,但是 Windows 的屏幕键盘做的粗糙,有时候不会自动开启屏幕键盘,此时需要使用代码辅助
如果是非平板模式,以及系统没有检测到触摸,此时不一定能弹出屏幕键盘
在 Win10 版本小于 10.0.14393 时,可以通过启动 TabTip.exe 应用打开屏幕键盘。而在大于等于 10.0.14393 版本需要使用 COM 的方式
先来聊聊如何通过 TabTip.exe 应用打开屏幕键盘
默认的 TabTip.exe 应用将会放在 Program Files
文件夹下,可以通过如下代码拿到 Program Files
文件夹
var commonFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles);
//程序集目标平台为X86时,获取到的是x86的Program Files,但TabTip.exe始终在Program Files目录下
if (commonFilesPath.Contains("Program Files (x86)"))
{
commonFilesPath = commonFilesPath.Replace("Program Files (x86)", "Program Files");
}
此时拿到应用路径可以使用下面代码
var tabTipPath = Path.Combine(commonFilesPath, @"microsoft shared\ink\TabTip.exe");
启动应用,启动之后需要等待一下,下面代码使用 Thread.Sleep(50)
等待,请小伙伴根据需要更改时间或更改为 Task.Delay 等。如果没有后续逻辑依赖键盘,那么可以删除 Thread.Sleep 的代码
var processStartInfo = new ProcessStartInfo
{
FileName = tabTipPath,
UseShellExecute = true,
CreateNoWindow = true
};
Process.Start(processStartInfo);
//第一次系统软键盘启动时候,需要缓冲一下
Thread.Sleep(50);
如果是 10.0.14393 Windows 10周年纪念版 版本,可以使用 com 的方式启动,在启动之前,可以先判断一下版本号
var minWin10Version = new Version(10, 0, 14393, 0);
var isNeedCom = Environment.OSVersion.Version >= minWin10Version;
注意,默认的 .NET 程序是不会让你获取 Environment.OSVersion 到 win10 的版本,详细请看 关于C#中Environment.OSVersion判断操作系统及Win10上的问题 - 夏至千秋 - 博客园
通过 COM 只有 Toggle 方法,也就是如果原本是没有开启的,调用将会开启。否则将会关闭
//使用com组件的方式来打开TabTip.exe
var uiHostNoLaunch = new UIHostNoLaunch();
// ReSharper disable once SuspiciousTypeConversion.Global
var tipInvocation = uiHostNoLaunch as ITipInvocation;
tipInvocation?.Toggle(Win32.User32.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();
判断屏幕键盘是否开启,在 10.0.14393 Windows 10周年纪念版之前可以采用如下方法
private static IntPtr FindTipMainWindow()
{
return Win32.User32.FindWindow(KeyboardWindowClass, null);
}
private const string KeyboardWindowClass = "IPTip_Main_Window";
private static bool GetIsOpenLegacy()
{
var touchHwnd = FindTipMainWindow();
if (touchHwnd == IntPtr.Zero)
{
return false;
}
// 这里需要 unchecked 因为返回的是 int 转换为 WindowStyles 需要忽略负号
var style = (Win32.WindowStyles) Win32.User32.GetWindowLongPtr(touchHwnd,
(int) Win32.GetWindowLongFields.GWL_STYLE);
// 如果满足了下面的条件就可以判断显示键盘
// 由于有的系统在键盘不显示时候只是多返回一个WS_DISABLED这个字段。所以加一个它的判断
return style.HasFlag(Win32.WindowStyles.WS_CLIPSIBLINGS)
&& style.HasFlag(Win32.WindowStyles.WS_VISIBLE)
&& style.HasFlag(Win32.WindowStyles.WS_POPUP)
&& !style.HasFlag(Win32.WindowStyles.WS_DISABLED);
}
如果是 10.0.14393 需要使用下面代码
private const string WindowParentClass = "ApplicationFrameWindow";
private const string WindowClass = "Windows.UI.Core.CoreWindow";
private const string WindowCaption = "Microsoft Text Input Application";
private static bool? GetIsOpenKeyboardWindow()
{
// if there is a top-level window - the keyboard is closed
var wnd = Win32.User32.FindWindowEx(IntPtr.Zero, IntPtr.Zero, WindowClass, WindowCaption);
if (wnd != IntPtr.Zero)
return false;
var parent = Win32.User32.FindWindowEx(IntPtr.Zero, IntPtr.Zero, WindowParentClass, null);
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 = Win32.User32.FindWindowEx(parent, IntPtr.Zero, WindowClass, WindowCaption);
if (wnd != IntPtr.Zero)
return true;
return null;
}
// 这是 Win32.User32 的方法
public const string LibraryName = "user32";
[DllImport(LibraryName, CharSet = Properties.BuildCharSet)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass,
string lpszWindow);
[DllImport(LibraryName, CharSet = Properties.BuildCharSet)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
但是在 10.0.18362(1903) 版本,上面判断方法在一些设备上凉凉。可选采用 https://github.com/microsoft/WindowsAppSDK/discussions/3343 所述方法,通过 IFrameworkInputPane 的 Location 方法,判断宽度高度为 0 断定屏幕键盘没有打开,如官方文档所述
HRESULT Location([out] RECT *prcInputPaneScreenLocation);
Parameters:
[out] prcInputPaneScreenLocation
A pointer to a RECT structure that, when this method returns successfully, receives the location of the input pane, in screen coordinates. If the input pane is not visible, this structure receives an empty rectangle.
关于使用 IFrameworkInputPane 判断屏幕键盘是否打开的详细使用方法请参阅 https://stackoverflow.com/a/55513524
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/WPF-%E5%90%AF%E5%8A%A8%E5%B1%8F%E5%B9%95%E9%94%AE%E7%9B%98.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航
本作品采用
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:
https://blog.lindexi.com
),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请
与我联系
。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利