Question

In an application I am using an onscreen keyboard(OSK) for when it is running on a tablet. We've made a class called OSK which has a show and hide method.

When the user presses 'enter' on the onscreen keyboard, the osk hides. The problem is when the user closes the OSK with the close (x) button. The OSK hides, but some things need to change in the UI when this happens.

Is there a way (an event or something like that) to know when the user pushes the close button on the OSK?

I'll show some of the code I"ve used for showing and hiding the OSK. The code shown is in Oxygene (but it looks a lot like C# I think)

First we've have some dllImports:

[DllImport("user32.dll", SetLastError := true)]
class method PostMessage(hWnd: IntPtr; Msg: UInt32; wParam, lParam: IntPtr): Boolean; external;
[DllImport("user32.dll", SetLastError := true)]
class method FindWindow(lpClassName, lpWindowName: String): IntPtr; external; 

In the show method there is this code:

  using p := new Process do
  begin
    p.StartInfo.UseShellExecute := true;
    p.StartInfo.FileName := 'C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe';
    p.Start();
  end; 

In the Hide method the next code is used to hide the OSK:

      var oskWindow := FindWindow("IPTip_Main_Window", nil);
      var WM_SYSCOMMAND  := 274;
      var SC_CLOSE := 61536;
      PostMessage(oskWindow, WM_SYSCOMMAND, SC_CLOSE, 0);

Update: Found a working solution for windows 7....not working for windows 8 (What i need)

This is what i did to solve the problem in windows 7: The main idea is that in the OSK class i start a Dispatchertimer when the osk is shown. Now every second is checked if the osk window is visible. If so an event is fired which can be handled in several places. (I also check a _firstshown boolean in the timer because it sometimes takes a while for the osk to appear.

Here's how I did that: first i made a dllImport of the IsWindowVisible method

[DllImport("user32.dll", CharSet:=CharSet.Auto)]
class method IsWindowVisible(hWnd:IntPtr):Boolean; external;

In OSK.Show i start the timer and set _firstShown to false (because it can take a while for the osk to appear) Before this i've set the timer interval on 1 second and added an eventhandlerf to timer.Tick:

  _timer.Interval := new TimeSpan(0,0,1);
  _timer.Tick += new EventHandler(_timer_Tick);

This is the code in _timer_tick:

class method OSK._timer_Tick(sender: Object; e: EventArgs);
begin
  var oskWindow := FindWindow("IPTip_Main_Window", nil);
  var IsOSKOpen := IsWindowVisible(oskWindow);

  if not _firstShown then begin
      if IsOSKOpen then _firstShown := true;

      exit;
  end;
  if not IsOSKOpen then begin        
      OSKClosed(nil,new EventArgs());      
      _timer.Stop();
      _firstShown := false;
  end;
end;

There was pleasure when this worked on my development machine (windows 7), the joy was shortlived because when i tested it on the tablet (windows 8) it didn't work. the timer etc works fine, it just looks like windows 8 doesn't handle the iswindowVisible method.

Anyway all help is very much appreciated

Was it helpful?

Solution 2

Finally found a solution to my problem using the registry. The osk uses a registrykey called UserClosed. If the user closes the osk the userclosed registry key will be one.

So the first thing to do is to set this registry key to 0 (in App.Xaml.cs override OnStartup)

    var regKeyUserClosed := Registry.CurrentUser.OpenSubKey("Software\Microsoft\TabletTip\1.7",true);
    regKeyUserClosed.SetValue("UserClosed",0);

Now the registry key is set to 0 when the application starts.

Now when the osk is called the timer starts and on every tick we need to see if the registry key is 1 (the user has closed the OSK) if so we trigger the event, stop the timer and set the registry key back to 0.

This is my new timer_tick method:

   class method OSK._timer_Tick(sender: Object; e: EventArgs);
begin

var regKeyUserClosed := Registry.CurrentUser.OpenSubKey("Software\Microsoft\TabletTip\1.7",true);

  var UserClosed := Convert.ToInt32(regKeyUserClosed.GetValue("UserClosed"));

  if UserClosed.Equals(1) then begin 

  OSKClosed(nil,new EventArgs()); 

  _timer.Stop();

  regKeyUserClosed.SetValue("UserClosed",0);

  end; 

end;

OTHER TIPS

To be honest, I don't know Oxygene, but it looks like Pascal for .NET :)

Since your program starts the OSK itself, you can just subscribe to the Process.Exited event which will be called when the user closes the OSK.

So, make p a global variable and subscribe to Exited

p.Exited += new EventHandler(_osk_Exited);

class method OSK._osk_Exited(sender: Object; e: EventArgs);
begin
    // will be called when OSK gets closed
end;

Also, I don't see why you need FindWindow, can't you use p.MainWindowHandle instead?

UPDATE2

Previous update didn't work, but suddenly I thought: why not use Microsoft's Ink API instead of hacking?

Add a reference to the Microsoft.Ink assembly to your project, you'll need to browse to the file, because it is not in the list by default (search for microsoft.ink.dll under Program Files).

Then create a TextInputPanel object for each input that needs the OSK:

tip = new TextInputPanel(textBox1);
tip.InPlaceVisibleOnFocus = true;
tip.DefaultInPlaceState = InPlaceState.Expanded;

Now, the text input panel will show up when textBox1 gets focus and hide when it loses focus. You can even attach a handler to the InPlaceVisibilityChanged event if you're interested in when the user hides the panel:

tip.InPlaceVisibilityChanged += tip_InPlaceVisibilityChanged;

void tip_InPlaceVisibilityChanged(object sender, InPlaceVisibilityChangeEventArgs e)
{
    // do something with e.Visible
}

If you're using WPF you can use this method to get the HWND of the TextBox:

HwndSource textHandle = (HwndSource)PresentationSource.FromVisual(wpfTextBox);
tip = new TextInputPanel();
tip.AttachedEditWindow = textHandle.Handle;
tip.InPlaceVisibleOnFocus = true;
tip.DefaultInPlaceState = InPlaceState.Expanded;

you can start a timer when process start osk. On that timer you can check

var oskprocess = Process.GetProcessesByName("osk");

When oskprocess.Length=0

Then there is no osk running. Then you can stop the timer

sometime back I wrote code that used taskkill to end osk.exe wont work in windows10 anymore but when I wrote it I think I was using windows8 this was done in processing (java) but this mighthelp???

launch(new String[] {"c:/Windows/system32/osk.exe" }); // OSK open

launch(new String[] {"taskkill /IM osk.exe"}); // OSK close
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top