【问题标题】:Determine if Windows 10 Touch Keyboard is Visible or Hidden确定 Windows 10 触控键盘是可见还是隐藏
【发布时间】:2018-04-21 14:00:09
【问题描述】:

我正在尝试确定 Windows 10 虚拟触摸键盘是否可见,以了解是否从我的应用程序中打开它。在最新的 Windows 10 更新 15063 或之前的更新之前,以下代码一直运行良好。似乎微软可能对窗口样式进行了一些更改,但我无法弄清楚。

    public static bool IsKeyboardVisible()
    {
        IntPtr keyboardHandle = GetKeyboardWindowHandle();
        // Specifies we wish to retrieve window styles.
        int GWL_STYLE = -16;

        //The window is disabled. See http://msdn.microsoft.com/en-gb/library/windows/desktop/ms632600(v=vs.85).aspx.
        UInt32 WS_VISIBLE =               0x10000000;
        UInt32 WS_DISABLED =              0x08000000;
        UInt32 WS_POPUP =                 0x80000000;


        bool visible = false;
        bool disabled = false;

        if (keyboardHandle != IntPtr.Zero)
        {
            UInt32 style = GetWindowLong(keyboardHandle, GWL_STYLE);
            visible = ((style & WS_VISIBLE) == WS_VISIBLE);
            disabled = ((style & WS_DISABLED) == WS_DISABLED); // ref https://stackoverflow.com/questions/11065026/get-window-state-of-another-process
            log.InfoFormat("style:{0:X4} visible:{1} disabled:{2}", style, visible, disabled);
        }

        return visible && !disabled ;
    }

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

【问题讨论】:

  • 已经打开了,打开会不会疼?
  • 也许GetKeyboardWindowHandle 失败了。我们不知道它是什么。此外,您对WS_VISIBLE 的定义并不完全符合应有的定义(参见window styles)。
  • 是的,肯定是你的WS_VISIBLE 错了。你的实际上是 WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS,但在 15063 中,键盘窗口不再有 WS_POPUP。无论如何,您应该只测试 WS_VISIBLE0x10000000
  • 好电话。我将代码更改为上面的代码,但 Win 10 正在返回 0x84000000,即使在我打开键盘并在屏幕上看到键盘后也不可见。
  • @JonathanPotter 没有可靠的方法来“打开”它。只有切换有效。见stackoverflow.com/a/40921638/332528

标签: c# windows winapi touch virtual-keyboard


【解决方案1】:

我对@9​​87654321@ 进行了一些研究。看起来秋季创作者更新(版本 1709)中的新键盘由另一个窗口托管。这个窗口有Windows.UI.Core.CoreWindow 类和Microsoft Text Input Application 作为它的标题。

以下代码适用于所有 Windows 10 版本,包括新的 1803 和旧 Windows 版本(我相信从 Windows 8 开始)。

static class TouchKeyboard
{
    public static bool GetIsOpen()
    {
        return GetIsOpen1709() ?? GetIsOpenLegacy();
    }

    private static bool? GetIsOpen1709()
    {
        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
            var 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);
    }

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

更新:我更新了答案和代码以兼容 Redstone 4 (v1803)。

【讨论】:

  • 很好,很好。这是唯一适用于 Windows 10 1709 的解决方案。谢谢。
  • 这似乎不再起作用 - 对我来说,即使触摸键盘在屏幕上不可见,它也总是返回 true。这是在 Windows 10 19042.1110
【解决方案2】:

我正在使用这个解决方案,它在 Windows 1607、1709 和 1803 上运行(检查代码下面的 Main 方法):

using System;
using System.Drawing;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {

        [ComImport, Guid("D5120AA3-46BA-44C5-822D-CA8092C1FC72")]
        public class FrameworkInputPane
        {
        }

        [ComImport, System.Security.SuppressUnmanagedCodeSecurity,
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
        Guid("5752238B-24F0-495A-82F1-2FD593056796")]
        public interface IFrameworkInputPane
        {
            [PreserveSig]
            int Advise(
                [MarshalAs(UnmanagedType.IUnknown)] object pWindow,
                [MarshalAs(UnmanagedType.IUnknown)] object pHandler,
                out int pdwCookie
                );

            [PreserveSig]
            int AdviseWithHWND(
                IntPtr hwnd,
                [MarshalAs(UnmanagedType.IUnknown)] object pHandler,
                out int pdwCookie
                );

            [PreserveSig]
            int Unadvise(
                int pdwCookie
                );

            [PreserveSig]
            int Location(
                out Rectangle prcInputPaneScreenLocation
                );
        }


        static void Main(string[] args)
        {
            var inputPane = (IFrameworkInputPane)new FrameworkInputPane();
            inputPane.Location(out var rect);
            Console.WriteLine((rect.Width == 0 && rect.Height == 0) ? "Keyboard not visible" : "Keyboard visible");
        }
    }
}

它使用 IFrameworkInputPane 接口 (https://docs.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nn-shobjidl_core-iframeworkinputpane)

【讨论】:

  • 此方法自 2021 年 7 月 24 日起适用于 Windows 10 Enterprise Build 19042
【解决方案3】:

我发现了另一个未记录的 COM API,它返回触摸键盘的位置。如果键盘被隐藏,它返回键盘窗口的边界或零。我在Windows 8.1Windows 10Windows 10 Fall Creators Update 中对其进行了测试,效果很好。

现在有个坏消息:在Fall Creators Update 之前的所有版本中,如果活动窗口和触摸键盘位于同一监视器上,它只会报告准确的结果。如果不是这种情况 - API 只返回之前的缓存值。我猜这与这个 API 用于计算触摸键盘和应用程序窗口的遮挡有关。 (在Windows::UI::ViewManagement::InputPane.get_OccludedRect() UWP API 内部调用)。

因此,如果您不关心支持旧版本或多显示器方案,请使用它。否则我会建议检查 Windows 版本并回退到以前的方法(GetIsOpenLegacy() 来自我的另一个答案)。

API:

[ComImport, Guid("228826af-02e1-4226-a9e0-99a855e455a6")]
class ImmersiveShellBroker
{
}

[ComImport, Guid("9767060c-9476-42e2-8f7b-2f10fd13765c")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IImmersiveShellBroker
{
    void Dummy();
    IInputHostManagerBroker GetInputHostManagerBroker();
}

[ComImport, Guid("2166ee67-71df-4476-8394-0ced2ed05274")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IInputHostManagerBroker
{
    void GetIhmLocation(out Rect rect, out DisplayMode mode);
}

[StructLayout(LayoutKind.Sequential)]
struct Rect
{
    public int Left, Top, Right, Bottom;
}

enum DisplayMode
{
    NotSupported = 0,
    Floating = 2,
    Docked = 3,
}

使用示例:

// do this once:
var brokerClass = new ImmersiveShellBroker();
var broker = (IImmersiveShellBroker)brokerClass;
var ihm = broker.GetInputHostManagerBroker();
Marshal.ReleaseComObject(broker);

// now ihm reference can be cached and used later:
Rect rect;
DisplayMode mode;
ihm.GetIhmLocation(out rect, out mode);

注意:在 Windows 10 之前,GetIhmLocation() 看起来总是返回 DisplayMode.NotSupported,而不是实际的 mode

【讨论】:

  • 太棒了。这适用于 Windows 10 1709 和 Windows 10 1803。
猜你喜欢
  • 1970-01-01
  • 2021-08-06
  • 2011-10-28
  • 1970-01-01
  • 2013-02-14
  • 1970-01-01
  • 2016-07-03
  • 1970-01-01
  • 2020-06-08
相关资源
最近更新 更多