3

I am trying to fix an issue we are having with the Onscreenkeyboard in our C# Prism WPF app targeting .Net 4.5.2 deployed via ClickOnce, running on Windows 10 version 1803.

Our app has a TimeOut function that triggers an event and in that event I would like to either Toggle the visibility of the keyboard or close it if it is open. The Close() function in the below class does not work sometimes, I see in the logs(not displayed in below code) the app finds the handle (which is>0) and issues close command on it but the keyboard does not close.

This question is related to the 2 questions mentioned and linked below! But I cannot comment so I have to ask a new question.

We had a sort of working solution in v1709 before as proposed by Determine if Windows 10 Touch Keyboard is Visible or Hidden

Now I am trying to implement the latest post in that question to be able to toggle the visibility of the keyboard on timeout or app closed event if it detects the keyboard being open, I am trying to combine it with the Keyboard class you see below but I am having trouble with it.

So we have the touchkeyboard class combined from the above post and Show touch keyboard (TabTip.exe) in Windows 10 Anniversary edition :

static class TouchKeyboard
{


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

This was working decently enough in 1709 but not anymore in 1803 and the new customer requirement to close keyboard on timeout. So now I have added the code from the latest post of that stackoverflow question to the above class as a function (I do not think this is the correct way):

   static class TouchKeyboard
   {
    ///Above code omitted

    public static bool GetIsOpen1803()
    {
        // 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);

    }

    [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,
    }
    }

I think I am implementing it incorrectly since it doesnt work all the time, when I call GetIsOpen1803 from the above mentioned timeout event, it sometimes returns the rectangle as 0,0,0,0 while I see the keyboard is up. In the code it says "only do this once" and "can be cached and used later" in the comments, but I cannot seem to grasp how to do this in the Touchkeyboard class we have.

So far getting the GetIsOpen methods to reliably return if the keyboard is on screen from anywhere in the app and being able to close it is eluding me. Even terminating all tabtip processess does not always close the keyboard! The only thing that works is terminating the service but I cannot do that because of elevated privileges needed.

So what would absolutely make my day is finding a reliable way of seeing if the keyboard is open for all the windows 10 versions and a reliable way of closing it or toggling it s visibility (if there is any!)

Any help would be greatly appreciated!

Tim Bijnens
  • 310
  • 2
  • 11

3 Answers3

0

Can you try updated variant of my library - https://github.com/AlexeiScherbakov/osklib

You can use OnScreenKeyboardWatcher/DispatcherOnScreenKeyboardWatcher/WinFormsKeyboardWatcher for: 1. Detection of current keyboard state 2. Watch how long keyboard is opened (for timeout)

I have tested this code on old versions(1709,1703 and 1607).On my 1803 (build 17134) it also works

Alexei Shcherbakov
  • 1,125
  • 13
  • 10
  • Thank you for your answer! I will try it out once I am back from holiday, since the solution in the other answer by JUN did not work well. – Tim Bijnens Jun 27 '18 at 09:59
  • Hi, we have tried out your solution, but in our WPF Prism app the above library sometimes throws exceptions, we cannot use in in our projects. Instead we have upgraded to .Net Framework 4.7.2, this solved all our problems and the Keyboard is now working as expected. – Tim Bijnens Sep 10 '18 at 12:42
  • What exceptions? – Alexei Shcherbakov Sep 10 '18 at 20:25
  • If I remember it correctly it was the GetIhmLocation function that was sometimes throwing the exception. The solution that we have now is much better since it works out of the box and does not require any code at all, only an upgrade to the .Net Framework – Tim Bijnens Sep 11 '18 at 06:09
  • I'm trying to integrate the library but I'm getting "Requires elevation". Any idea what can I try to make it work? – Macaret Jun 17 '20 at 03:51
0

We could NOT get the keyboard to work reliably with the solutions provided. The only (and easy) solution that worked for us was to Upgrade to .Net Framework 4.7.2 and upgrade all Nuget packages in our solutions with it. We could not use 4.7.2 a couple of months ago since ClickOnce did not have to right references built in, this was solved in a recent update.

After we upgraded to 4.7.2 we removed all keyboard related code from our app and now everything works as expected (in tablet mode, which is what we wanted), without any code from our end I might add! So the reason that I am not posting any code is simply because there isn t any!

Tim Bijnens
  • 310
  • 2
  • 11
-2

As Tim Bijnens noted, when he started this thread, the keyboard toggle ( ITipInvocation.Toggle() ) does not always bring up the keyboard.

In Win10 Ver 1803, DesktopMode, there is no reliable way to
toggle the "Touch Keyboard" on|off [ ITipInvocation.Toggle() ];
nor can you reliably discover if it's "up" ( on screen )
[ IFrameworkInputPane.Location() ]; both routines fail randomly.

Instead, ensure that "TabTIP.EXE" and "....InputApp.EXE"
only run when the keyboard is "up" ( on screen ); see:
https://stackoverflow.com/a/51376030/5732431

Jeff Relf
  • 55
  • 3