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!