Определите, является ли сенсорная клавиатура Windows 10 видимой или скрытой

2

Я пытаюсь выяснить, видима ли виртуальная сенсорная клавиатура Windows 10 или не знать, открывать ее или нет из моего приложения. Следующий код работал нормально вплоть до последнего обновления Windows версии 15063 или можно использовать его прямо перед ним. Похоже, что Microsoft что-то изменила с помощью стилей окна, но я не могу понять.

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

Это связано с: Показать сенсорную клавиатуру (TabTip.exe) в выпуске Windows 10 Anniversary

  • 0
    Больно ли открывать, если оно уже открыто?
  • 3
    Возможно, GetKeyboardWindowHandle не работает. Мы не знаем что это. Кроме того, ваше определение WS_VISIBLE не совсем то, что должно быть (см. Стили окон ).
Показать ещё 3 комментария
Теги:
winapi
touch
virtual-keyboard

3 ответа

5
Лучший ответ

Я провел некоторое исследование с Spy++. Похоже, новая клавиатура в обновлении Fall Creators (версия 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 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).

  • 0
    Отлично, отлично. Это единственное решение, которое работает на Windows 10 1709. Спасибо.
  • 0
    Это решение не работает сейчас для Windows 10 1803.
1

Я обнаружил еще один недокументированный COM API, который возвращает положение сенсорной клавиатуры. Он возвращает границы окна клавиатуры или нулей, если клавиатура скрыта. Я тестировал его в Windows 8.1, Windows 10 и Windows 10 Fall Creators Update, и он отлично работает.

Теперь некоторые плохие новости: во всех версиях до Fall Creators Update он сообщает только точные результаты, если активное окно и сенсорная клавиатура расположены на одном мониторе. Если это не так - API просто возвращает предыдущее кешированное значение. Я предполагаю, что это связано с тем, что этот API предназначался для вычисления окклюзии сенсорной клавиатуры и окна приложения. (Он вызвал внутри Windows::UI::ViewManagement::InputPane.get_OccludedRect() API UWP).

Поэтому, если вы не заботитесь о поддержке старых версий или сценариев с несколькими мониторами, используйте его. В противном случае я бы предложил проверить версию 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);

Примечание: выглядит как GetIhmLocation() всегда возвращает DisplayMode.NotSupported вместо фактического mode до Windows 10.

  • 0
    Отлично. Это работает для Windows 10 1709 и для Windows 10 1803 тоже.
0

Я использую это решение, и оно работает на Windows 1607, 1709 и 1803:

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню