Input Method
By Vassili Philippov, December 21, 2001.
Print version
Introduction
SIP (Soft Input Panel) technology is based on input methods (IMs). Each IM is a
way to enter characters like keyboard, letter recognizer, block recognizer. You
can create your own input method. This paper describes creating a custom input
method and gives working sample.
What you need
What we create
We will create a very simple IM that will give
you possibility to enter only three symbols: 1, A
and 3. Of course no one will use this input
method :) But you can learn the main ideas of the
input method implementation.
Step by step
The next 10 sections describe creating your own input method step by step. Let's start
with a short overview of these sections:
- Create a new project. We will use ATL with MFC support.
- Create a COM object that will implement our input method functionality.
- Implement IInputMethod. Now all methods are blank. We will implement them later.
- We have to change registry settings to register our COM as an input method.
- We will change an icon that is used in SIP button for our input method.
- We can avoid using dvoraksip.tlb.
- Now we will create a panel that implements all business logic.
- The systen gives us callback interface that should be called to generate symbols.
- Our panel handles mouse event to generate symbols when needed and ...
- Input method works!
Step 1. Create new project

Create new project using "WCE ATL COM AppWizard". Set "Support MFC" at the second step
of the wizard because we want to use MFC classes. Now an empty project is created. ATL wizard
does not create any COM objects we have to do it ourselves.
Step 2. Create a new COM object

Create new ATL Object. "Simple Object" is the most suitable template for our purposes.
Let's call the object BitmapIM. Close the workspace and open it again. I do not know if you
have this bug but my eMbedded Visual Studio does not show created class in the ClassView
and I have to close and re-open the project.
Step 3. Implement IInputMethod

Now we have to implement IInputMethod interface. It is easy if you have a .tlb file
that contains this interface. You can fint such file in Pocket PC 2002 SDK. After
installing this SDK look at
<Windows CE Tools>\wce300\Pocket PC 2002\samples\ATL\dvoraksip\dvoraksip.tlb
or your can download this file dvoraksip.tlb (6 Kb).
Right-click on the class name, add this template library and choose IInputMethod
interface.
Step 4. Change registry settings

Now we have empty Input Method. Pocket PC uses registry to detect that some COM is
an input method. There should be a special key 'IsSIPInputMethod' with value '1'
in class section. Open .rgs file and add the following line just after
VersionIndependentProgID value:
IsSIPInputMethod = s '1'

Now we have an input method that can be registered but does nothing. This input method is shown
in the SIP menu as 'BitmapIM Class'.
We can change the input method name (how it is
called in a menu shown after the SIP button
is pressed) by changing the class name in
registry.
Change the name in .rgs file in the line that
contains "ForceRemove" string.

Step 5. Change icon
Let's change icon shown in the SIP button. We have to rewrite GetInfo
method for that. In this method we have to fill the given structure and add image list handles.
Here is code for that:
STDMETHOD(GetInfo)(_tagImInfo * pimi)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
if (pimi == NULL)
return E_POINTER;
pimi->hImageNarrow = (HIMAGELIST)m_imageList1;
pimi->iNarrow = 0;
pimi->hImageWide = (HIMAGELIST)m_imageList2;
pimi->iWide = 0;
return S_OK;
}
Also we have to add two class members (m_imageList2 and m_imageList1)
and initialize them. Here is a code that defines these members:
CImageList m_imageList1;
CImageList m_imageList2;
Then you have to initialize these two image lists. I suggest doing it in FinalConstruct
method:

HRESULT CBitmapIM::FinalConstruct()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
m_imageList1.Create(IDB_LIST1, 16, 1, RGB(0,255,0));
m_imageList2.Create(IDB_LIST2, 32, 1, RGB(0,255,0));
return S_OK;
}
Do not forget to add:
AFX_MANAGE_STATE(AfxGetStaticModuleState())
in the begining of each method where you use resources and MFC.
Step 6. Avoid using dvoraksip.tlb
Now we can remove #import of dvoraksip.tlb. But we should #include
of sip.h and make 3 defines:
#include
extern "C" const GUID __declspec(selectany) IID_IIMCallback =
{0x42429669,0xae04,0x11d0,{0xa4,0xf8,0x00,0xaa,0x00,0xa7,0x49,0xb9}};
extern "C" const GUID __declspec(selectany) IID_IInputMethod =
{0x42429666,0xae04,0x11d0,{0xa4,0xf8,0x00,0xaa,0x00,0xa7,0x49,0xb9}};
extern "C" const GUID __declspec(selectany) CLSID_CMSDvorakIm =
{0x42429695,0xae04,0x11d0,{0xa4,0xf8,0x00,0xaa,0x00,0xa7,0x49,0xb9}};
Several errors will appear because the same types have different names in sip.h
and in the header file generated by #import. You will have the following compiler error
(77) : error C2061: syntax error : identifier '_tagSipInfo'
just change _tagSipInfo to SIPINFO in the header file.
Then you have to change wireHWND to HWND. Now you can compile your code. To make
the input method better we will change all E_NOTIMPL return values to S_OK.
Let's upload our input method and see how it works now (it is still dummy and does not
contail business logic). Pocket PC should be soft-reset before the upload because the system
uses the .dll file.
Step 7. Create panel object
Now we have created a dummy input method. In the next sections we will implement business logic.
The main idea is having a bitmap with some zones. Each zone corresponds to a character.
We will create a class derived from CWnd implementing necessary behaviour. It
will show the bitmap and control mouse clicks. Then we will insert the object in our SIP.
Create a bitmap resource 240x80 - it will be our SIP. Create a new class CSipPanel
derived from CWnd.
Override OnPain method (WM_PAINT message) and draw the bitmap in this method:
void CSipPanel::OnPaint()
{
CPaintDC dc(this); // device context for painting
CDC memDc;
if (!memDc.CreateCompatibleDC(&dc)) return;
m_bmpNormal.LoadBitmap(IDB_SIP_NORMAL);
HBITMAP m_hOldBitmap = (HBITMAP)::SelectObject(memDc.GetSafeHdc(), m_bmpNormal);
dc.BitBlt(0, 0, 240, 80, &memDc, 0, 0, SRCCOPY);
::SelectObject(memDc.GetSafeHdc(), m_hOldBitmap);
memDc.DeleteDC();
m_bmpNormal.DeleteObject();
}
Add CSipPanel member in our input method.
We will create it in Select method and destroy in Deselect.
STDMETHOD(Select)(HWND hwndSip)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
m_pSipPanel = new CSipPanel();
m_pSipPanel->CreateEx(0, NULL, _T("SampleSipPanel"), WS_VISIBLE, 0,0, 240, 80, hwndSip, NULL);
return S_OK;
}
Step 8. Using callback to generate character
If we want to work as a keyboard we have to put characters in the input
queue. IIMCallback interface is used for that. The system gives us this interface
by calling RegisterCallback method. We have to save given pointer into a member variable.
STDMETHOD(RegisterCallback)(IIMCallback * lpIMCallback)
{
m_callback = lpIMCallback;
return S_OK;
}
In my implementation I have to give callback interface to a panel. I have created
RegisterCallback method in CSipPanel for that and call it when necessary
from IInputMethod implementation.
Step 9. Handle mouse events
Now let's add mouse handlers. We will handle WM_LBUTTONUP event and call
SendString method of the call back interface. My implementation is very simple:
void CSipPanel::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_callback==NULL) return;
if (point.x<80) {
TCHAR* s = _T("1");
m_callback->SendString(s, 1);
return;
}
if (point.x<160) {
TCHAR* s = _T("A");
m_callback->SendString(s, 1);
return;
}
if (point.x<240) {
TCHAR* s = _T("3");
m_callback->SendString(s, 1);
return;
}
}
I think yours will contain a lot of business login, additional classes,
additional bitmaps and much more.
Step 10. Input method works!
Now we have a working IM.
Related resources:
Discuss
Discuss this article.
Here you can write your comments and read comments of other developers.
|