Controlling the location of the soft input panel in NETCF

by Nicholas Piasecki on October 12th, 2009

One of the pieces of WFS is an application that I’ve written that runs on our Honeywell Dolphin 7600. The Dolphin is a little mobile device than runs Windows CE 5.0, has 802.11g access, and has a barcode scanner. The application that I’ve written allows a warehouse employee to pick fulfillment requests, scan in inbound shipments against a previously recorded manifest, and process any returns that customers have sent back. Pretty neat stuff.

Big text, big buttons, and easy keyboard navigation.

I’ve designed most of the interface so that it can be used with just the big green “SCAN” button and the numeric keypad, since it’s cumbersome to bust out a stylus and use two hands while using it in the warehouse. There are, however, a few cases where the user does need to type something in, typically via the soft input panel, which is the stupid Microsoft name for the on-screen keyboard. Examples where the on-screen keyboard are necessary include typing in a merchant’s identifier for an inbound shipment manifest or typing in a user name and password to sign into the application.

By default, the soft input panel displays “whereever it feels like,” typically at the bottom of the screen or whatever random location the user last left it in, and while the user can drag it out of the way with the stylus if it obstructs UI elements, I thought it would be nice if I showed, hid, and positioned the soft input panel automatically when necessary. For example, if the user focuses a textbox where I expect they’ll need to type in some text, I’ll pop up the soft input panel and position it automatically just underneath that textbox.

If you load up Visual Studio, you’ll see the Microsoft.WindowsCE.Forms.InputPanel available in the toolbox. But it doesn’t provide any way to position the input panel once it is displayed. For that, I had to P/Invoke into a native method.

/// <summary>
/// Shows the input panel relative to the given control.
/// </summary>
/// <param name="control">The control.</param>
private void ShowInputPanel(Control control)
{
	NativeMethods.SIPINFO sipInfo;
	var x = 0;
	var y = control.PointToScreen(new Point(0, control.Height)).Y;
 
	this.inputPanel.Enabled = true;
 
	sipInfo = new NativeMethods.SIPINFO();
	sipInfo.cbSize = (uint)Marshal.SizeOf(sipInfo);
	if (NativeMethods.SipGetInfo(ref sipInfo))
	{
		sipInfo.rcSipRect.left = x;
		sipInfo.rcSipRect.top = y;
 
		NativeMethods.SipSetInfo(ref sipInfo);
	}
}

The above method simply takes an instance of a Control on the form and shows the soft input panel right underneath it. The input panel is shown by setting Enabled to true; it’s the SipSetInfo() call that actually positions it. Since our screen is super tiny, I only worry about the y coordinate; no sense in positioning in the horizontal direction because otherwise I’d crop off part of the input panel from view. The NativeMethods class is where I dump all of my P/Invoke declarations:

/// <summary>
/// This structure contains information about the current state of the 
/// software-based input panel, such as the software-based input panel 
/// size, screen location, docked status, and visibility status.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SIPINFO
{
	/// <summary>
	/// Size, in bytes, of the SIPINFO structure. This member must be 
	/// filled in by the application with the size of operator. Because
	/// the system can check the size of the structure to determine 
	/// the operating system version number, this member allows for
	/// future enhancements to the SIPINFO structure while maintaining 
	/// backward compatibility.
	/// </summary>
	public uint cbSize;
 
	/// <summary>
	/// Specifies flags representing state information of the 
	/// software-based input panel. The following table shows the
	/// possible bit flags. These flags can be used in combination. 
	/// </summary>
	public uint fdwFlags;
 
	/// <summary>
	/// Rectangle, in screen coordinates, that represents the area of 
	/// the desktop not obscured by the software-based input panel. 
	/// If the software-based input panel is floating, this rectangle 
	/// is equivalent to the working area. Full-screen applications 
	/// that respond to software-based input panel size changes can 
	/// set their window rectangle to this rectangle. If the
	/// software-based input panel is docked but does not occupy 
	/// an entire edge, then this rectangle represents the largest 
	/// rectangle not obscured by the software-based input panel. 
	/// If an application wants to use the screen space around the
	/// software-based input panel, it needs to reference rcSipRect.
	/// </summary>
	public RECT rcVisibleDesktop;
 
	/// <summary>
	/// Rectangle, in screen coordinates of the window rectangle and 
	/// not the client area, the represents the size and location of 
	/// the software-based input panel. An application does not
	/// generally use this information unless it needs to wrap
	/// around a floating or a docked software-based input panel 
	/// that does not occupy an entire edge.
	/// </summary>
	public RECT rcSipRect;
 
	/// <summary>
	/// Specifies the size of the data pointed to by the pvImData member.
	/// </summary>
	public uint dwImDataSize;
 
	/// <summary>
	/// Void pointer to IM-defined data. The IM calls the
	/// IInputMethod::GetImData and IInputMethod::SetImData methods to
	/// send and receive information from this structure.
	/// </summary>
	public IntPtr pvImData;
}
 
/// <summary>
/// This structure defines the coordinates of the upper-left and
/// lower-right corners of a rectangle. 
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
	/// <summary>
	/// Specifies the x-coordinate of the upper-left corner of the rectangle. 
	/// </summary>
	public int left;
 
	/// <summary>
	/// Specifies the y-coordinate of the upper-left corner of the rectangle. 
	/// </summary>
	public int top;
 
	/// <summary>
	/// Specifies the x-coordinate of the lower-right corner of the rectangle. 
	/// </summary>
	public int right;
 
	/// <summary>
	/// Specifies the y-coordinate of the lower-right corner of the rectangle. 
	/// </summary>
	public int bottom;
}
 
/// <summary>
/// This function receives information including the state of the
/// software-based input panel, the area of the desktop that is not 
/// obscured by the software-based input panel, the screen coordinates
/// of the software-based input panel, and information about the input 
/// method (IM) that the software-based input panel is currently using.
/// </summary>
/// <param name="sipInfo">[out] Pointer to the SIPINFO structure that
/// contains information about the current software-based input panel.</param>
/// <returns>TRUE indicates success. FALSE indicates failure. To get 
/// extended error information, call GetLastError. </returns>
[DllImport("coredll.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SipGetInfo(
	ref SIPINFO sipInfo);
 
/// <summary>
/// This function sets information including the state of the 
/// software-based input panel, the area of the desktop that is not 
/// obscured by the software-based input panel, the screen coordinates 
/// of the software-based input panel, and application-defined information
/// about the input method (IM) that the software-based input panel is
/// currently using.
/// </summary>
/// <param name="sipInfo">ointer to the SIPINFO structure that contains
/// information about the current software-based input panel.</param>
/// <returns>TRUE indicates success. FALSE indicates failure. 
/// To get extended error information, call GetLastError. </returns>
[DllImport("coredll.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SipSetInfo(
	ref SIPINFO sipInfo);

Yes, they really did call it “coredll.dll”.

You can imagine how calling these methods is now pretty simple. Assume that these methods are attached to the GotFocus and LostFocus events of a TextBox control:

/// <summary>
/// Fired when the user name textbox gets focus.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance 
/// containing the event data.</param>
private void WhenUserNameGotFocus(object sender, EventArgs e)
{
	this.ShowInputPanel(this.userNameTextBox);
}
 
/// <summary>
/// Fired when the user name textbox loses focus.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance 
/// containing the event data.</param>
private void WhenUserNameLostFocus(object sender, EventArgs e)
{
	this.HideInputPanel();
}

I’ve only tested this on our Windows CE 5.0-based device, but I imagine that it’d work on any later platform that was supported by the InputPanel control. Good luck!

2 Comments
  1. Hi,
    I was looking for a similar solution when I came across your article. What I am trying to do is, to position the inputpanel at the bottom of a maximized form. In a maximized form with no “main menu, the input panel always displays at a height equal to that of the “main menu”.
    Screenshot : http://i38.tinypic.com/2uikenp.png

    Any idea?

  2. @Aditya

    I’m not sure. I remember running across that line in the MSDN documentation and wondering what it was talking about, since my form is both maximized and has no main menu and seems to not exhibit any unusual behavior.

    Judging from your screenshot, it appears that you’re targeting some version of Windows Mobile, whereas I’m running on a customized build of Windows CE that Honeywell made with Platform Builder. My thinking is that Windows Mobile, which is itself just essentially a customized build of Windows CE that happens to be provided by a division of Microsoft, has special, modified behavior for the input panel.

    Indeed, when I show the input panel, it generally appears as a just a dialog box: it has a draggable, Windows XP-style title bar. In Windows Mobile, it seems to always appear attached to the bottom of the screen. My guess would be that we are seeing two separate implementations of the same COM interface for software input panels, and the one that I happen to be using responds to my location changing commands, and the one provided with Windows Mobile happens to ignore them.

    Try checking out this article and see if it there is more than one SIP implementation on your device. My guess? You’re ending up with the “default” one, the one that is hard-coded to appear as part of a main menu, but there might be additional, moveable ones available to you.

    (I find the documentation to be very murky when it comes to managing the landscape of Windows CE and its seemingly infinite derivative platforms.)

    Good luck!

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS