http://www.developer.com/

Back to article

Managing the Software Input Panel in Your Applications


February 8, 2006

Software Input Panel: Two Sides of a Development

The Software Input Panel (SIP)is presented on every mobile platform powered by Windows CE (for example, the Windows Mobile family or CE.NET). This is one of the methods the user can use to enter the data the on PDA. I'm probably not the only one, but I believe it's the most useful way for many applications. Hence, in talking about SIP, you usually mean two different things: the SIP itself, and how to manage it from within the application.

SIP is just a COM object implementing IInputMethod or IInputMethod2 interfaces. It should be loaded by the OS, so you can't develop SIP in C#. C or C++ are your helpful languages here. Because SIP is just another COM object, ATL makes all the development process significantly easier. I won't discuss SIP development in this article. The SDK samples contain a perfect example located under ...SamplesATLDvoraksip, so please refer to that sample for more info.

What I would like to talk about is how to manage SIP from within your own application. The task may look trivial, but if you want to make your application smarter and more convenient for the user because of the lack of screen, SIP management becomes a more than important issue. In addition, if you have developed different SIPs suitable for different areas (for example, multilingual, numeric, or whatever else), you might want to use a specific SIP in a specific case. Such capability allows you to achive many goals; you can show a big numeric keyboard when the user has to enter numbers only, thus allowing him to work with his fingers instead of a stylus. You probably have your own ideas. This said, see what a developer can do here.

Win32 API Details

The SIP API is pretty simple. There are less than a dozen functions (taken from sipapi.h):

DWORD WINAPI SipStatus();
BOOL  WINAPI SipSetDefaultRect(RECT *);
BOOL  WINAPI SipRegisterNotification(HWND);
BOOL  WINAPI SipShowIM(DWORD);
BOOL  WINAPI SipGetInfo(SIPINFO *);
BOOL  WINAPI SipSetInfo(SIPINFO *);
int   WINAPI SipEnumIM(IMENUMPROC);
BOOL  WINAPI SipGetCurrentIM(CLSID *);
BOOL  WINAPI SipSetCurrentIM(CLSID *);

I have placed this material first because it is supported on both Windows Mobile and CE.NET platforms. If you are an adept of Windows Mobile powered devices, the aygshell.h header file provides more SIP-related functions that partially duplicate those noted above. Which set to use depends on your requirements. The situation with SIP on Windows Mobile is even nicer. If you take several devices with the same OS version (for example, Windows Mobile SE version 4.21.XXXX) but with different build numbers, you can easily get a slightly different SIP behavior. Hence, one strategy just doesn't always work on every PDA as you might intend. Sounds optimistic, doesn't it?

Enumerating Available SIPs

The very first step to know the enemy is to enumerate SIPs. To accomplish this task, look at the following code snippet:

CTypedPtrMap<CMapStringToPtr,CString,CLSID*> g_SipMap;

int SipEnumIMProc(IMENUMINFO *pIMInfo)
{
   CLSID* pCLSID = new CLSID;
   memcpy(pCLSID,&pIMInfo->clsid,sizeof(CLSID));
   g_SipMap.SetAt(CString(pIMInfo->szName),pCLSID);

   TRACE(_T("%sn"),CString(pIMInfo->szName));

   return 1;

}

void CSIPDemoDlg::OnButtonEnum()
{
   SipEnumIM(SipEnumIMProc);

   CString sSipName;
   CLSID *pCLSID = NULL;
   for (POSITION pos = g_SipMap.GetStartPosition(); pos; )
   {
      g_SipMap.GetNextAssoc(pos,sSipName,pCLSID);
      m_SipList.AddString(sSipName);
   }
}

All it does is fill in the global map, which contains "SIP name"/CLSID pairs. This and the other following samples use MFC, but you can do exactly the same with Win32 API or any other framework you've gotten used to. As a result, my old buddy Dell Axim x50 shows the following:

How to Select, Show, and Hide Specific SIPs

Once you know a CLSID of desired SIP, you can select it by demand. And vice versa, at any point currently selected SIP may be obtained:

void CSIPDemoDlg::OnButtonEnum()
{
   SipEnumIM(SipEnumIMProc);

   CLSID CurrSip;
   SipGetCurrentIM(&CurrSip);

   int nCurrSip = LB_ERR, nSipCount = 0;
   CString sSipName, sCurrSipName;
   CLSID *pCLSID = NULL;
   for (POSITION pos = g_SipMap.GetStartPosition(); pos; )
   {
      g_SipMap.GetNextAssoc(pos,sSipName,pCLSID);
      m_SipList.AddString(sSipName);

      if ( memcmp(&CurrSip,pCLSID,sizeof(CLSID)) == 0 )
      {
         nCurrSip = nSipCount;
         sCurrSipName = sSipName;
      }
      nSipCount++;
   }
   m_SipList.SelectString(0,sCurrSipName);
}


void CSIPDemoDlg::OnButtonSelect()
{
   int nSel = m_SipList.GetCurSel();
   if ( LB_ERR == nSel )
      return;

   CString sSipName;
   m_SipList.GetText(nSel,sSipName);
   CLSID *pCLSID = NULL;
   if ( !g_SipMap.Lookup(sSipName,pCLSID) )
      return;

   BOOL bRes = SipSetCurrentIM(pCLSID);
   if ( !bRes )
      TRACE(L"SipSetCurrentIM returned %lun",GetLastError());
}

void CSIPDemoDlg::OnButtonShowHide()
{
   if ( !g_bShow )
   {
      SipShowIM(SIPF_ON);
      g_bShow = TRUE;
   }
   else
   {
      SipShowIM(SIPF_OFF);
      g_bShow = FALSE;
   }
}

void CSIPDemoDlg::OnButtonShowHide2()
{
   SIPINFO SipInfo;
   memset(&SipInfo,0,sizeof(SipInfo));
   SipInfo.cbSize=sizeof(SIPINFO);
   BOOL bRes = SipGetInfo(&SipInfo);
   if ( bRes )
   {
      if ( !g_bShow )
      {
         SipInfo.fdwFlags |= SIPF_ON;
         g_bShow = TRUE;
      }
      else
      {
         SipInfo.fdwFlags = SIPF_OFF;
         g_bShow = FALSE;
      }
      bRes = SipSetInfo(&SipInfo);
   }
   else
   {
      TRACE(L"SipGetInfo returned %lun",GetLastError());
   }
}

Here, you see slightly modified sample (CSIPDemoDlg::OnButtonEnum()), which detects which SIP is active and selects the appropriate line in the listbox. Other sample dialogs' methods select SIP and show/hide it. Note that, in order to get SipGetInfo or SipSetInfo working, you have to initialize SIPINFO.cbSize to sizeof(SIPINFO) value, so the OS will act correctly. This is trivial and a usual Win32 approach, but the following it helps you avoid wasting your time in attempts to understand why it doesn't work.

SipGetInfo gives us also visible desktop and SIP sizes that you can use to reposition SIP if needed. In turn, SipSetInfo doesn't change SIP dimentions. If you need to move SIP, use SipSetDefaultRect. I will give you some example of SIP repositioning later in this article.

Using Shell Functions

If you have decided to use aygshell-based functions, SHSipPreference will do similar job for you. Like SipShowIM, this API shows and hides input panel. Its last parameter defines what to do, and there you may request rise or lower input panel, lower it immediately (because the OS sets a timer before hiding SIP in a regular case) or disregard all pending down requests. SHSipInfo allows you to perform the same operations as SipXXX functions.

Usually, you will use all above APIs in respond to WM_SETTINGCHANGE or WM_ACTIVATE messages. According to the documentation, you have to be careful when using SHSipInfo around WM_SETTINGCHANGE processing due to several reasons. First, SIP changes may cause WM_SETTINGCHANGE messages to be sent by shell, so beware of infinite loops there when you're calling it in your handler. Next, SHSipInfo interacts with Device.exe and input panel thread, which takes up to 100 ms. This results in that WM_SETTINGCHANGE is sent to all running applications, so the system can be freezed for some time. Passing lParam value coming along with WM_SETTINGCHANGE allows you avoiding such delays.

Speaking about shell functions, there are a number of them on Windows Mobile platform that can either help you or deliver you an additional headache. Under the "help" section, I mean SHInputDialog, SHFullScreen, and similar calls. On the other hand, SHHandleWMActivate and SHHandleWMSettingChange may get you a lot of fun time enjoying a keyboard popping up in MFC application, because they are used in CDialog and CFrameWnd classes in appropriate message handlers. If you don't want the default behavior, you have to override the OnActivate or OnSettingChange handlers.

Custom Controls

Let me say a couple of words regarding handling SIP in custom controls. Suppose you want to implement a 'smart behavior' in our editbox-related classes; in other words, every time the user taps inside such an edit, a keyboard will automatically pop up and then hide upon removing the focus from the editbox. Adding WM_SETFOCUS and WM_KIILLFOCUS handlers will do to achieve this goal. You can decorate it even more, setting up the specific SIPs or input panel position on the screen. The next snippet gives you a clue:

void CSipEdit::OnSetFocus(CWnd* pOldWnd)
{
   CEdit::OnSetFocus(pOldWnd);

   SHSipPreference(m_hWnd,SIP_UP);
}

void CSipEdit::OnKillFocus(CWnd* pNewWnd)
{
   CEdit::OnKillFocus(pNewWnd);

   SHSipPreference(m_hWnd,SIP_FORCEDOWN);
}

Put Your Hands On SIP

Well, let me finally discuss how to move the SIP to the desired position on the screen. SipSetDefaultRect will change a default rectangle of the SIP, but this new value won't be taken until you re-select IM:

void CSIPDemoDlg::OnButtonMove()
{
   SIPINFO SipInfo;
   memset(&SipInfo,0,sizeof(SipInfo));
   SipInfo.cbSize=sizeof(SIPINFO);
   BOOL bRes = SipGetInfo(&SipInfo);
   if ( bRes )
   {
      CRect rc(SipInfo.rcSipRect);
      rc.OffsetRect(0,-20);
      SipSetDefaultRect(&rc);

      CLSID clsid;
      if ( SipGetCurrentIM(&clsid) )
      {
         SipSetCurrentIM(&clsid);
      }
      SipShowIM(SIPF_ON);
   }
}

The above sample sequentially moves SIP up the screen. Repositioning the SIP may be useful when you have to place some controls at the bottom of the screen. This is quite out of Microsoft's standards of Pocket PC GUI style, but sometimes you have just no other choice.

Now, turn your sights to the Registry keys. There are a couple of them that allow you dig deeper into SIP's waters:

[HKEY_CURRENT_USERControlPanelSip]

Name Value
MenuBarHeight defines bottom-most position of the default SIP, defaulted to 26
DragStyle defines if SIP has a title bar, so user can drag it, defaulted to 0

If you have changed the Registry, it requires a warm boot to apply new values.

We can move even further in our investigation. Taking in mind that SIP has a window, we can use Spy++ to explore existing windows. You'll find, for example, "SipWndClass" on Windows Mobile 2003 SE among them. Thus, you may obtain a handle of such window. Once this is done, it is up to your needs or imagination what to do with it. For instance, you can change the SIP's window style:

long lStyle=GetWindowLong(hwndSIP,GWL_STYLE);
lStyle |= WS_CAPTION|WS_SYSMENU;
SetWindowLong(hwndSIP,GWL_STYLE,lStyle);

Conclusion

We have discussed here several different aspects of managing the SIP on Windows CE devices. Now, you have to be able to manipulate an input panel as easily as ever. Far East languages were left out of our discussion due to their complexity, but it is also possible to apply similar approach there. I hope that all stuff above will help you make your own application the winner in the user's eyes.

Download

Download the accompanying code's zip file here.

About the Author

Alex Gusev started to play with mainframes in the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date