|
Chapter 15: Extending the Pocket PC continued
Writing an Input MethodThe soft input panel, or SIP, provides Pocket PC users with a method of "keyboard"-style input. I put keyboard in quotes because although the application sees keyboard messages from the SIP, the user might be entering those characters using a handwriting recognizer. The Pocket PC comes bundled with two ways of entering character data: a tiny drawing of a keyboard on which the user can tap in characters and a handwriting recognizer that interprets strokes that the user makes with a stylus. You can also design your own method of input rather easily. A component that provides this functionality is called an input method (IM), and it’s merely a COM object that exports an IinputMethod interface, and optionally either an IInputMethodEx or an IInputMethod2 interface. The IInputMethodEx and IInputMethod2 interfaces are both derived from the IInputMethod interface. IInputMethodEx is supported by Windows CE 2.11 and later versions, while IInputMethod2 was added with Windows CE 3.0 and the Pocket PC.These new interfaces add new methods for dealing with the Input Method Editor (IME). The IME is used to propose a series of candidate characters in response to input in the SIP. The SIP doesn’t provide enough room to allow the user to enter thousands of discrete characters, so the IME is used when working with Asian languages. Unless your SIP needs to interface with the IME, the IInputMethod interface should be sufficient since it is compatible with all versions of Windows CE that support a SIP. No matter which interface is exposed, the purpose of the COM object is to create an input method window in response to requests from the input panel.
The Components of a SIPA SIP is composed of two main componentsthe input panel and the input method. The input panel is supplied by the system. It creates the input panel window and provides both the message loop processing for the SIP and the window procedure for the input panel window. The input panel cooperates with the taskbar or another shell program to provide the user with the ability to switch between a number of installed input methods.The input method is the installable portion of the SIP. It’s responsible for translating pen strokes and taps into keyboard input. The input method is also responsible for the look and feel of the SIP. In almost all cases, the input method creates a window that is a child of the input panel window. Within that child window, the input method draws its interface and interprets mouse messages. The input method then calls back to the input panel when it wants to generate a key event. Each of these two components implements a COM interface, which then becomes the interface between them. The input method implements one of the IInputMethodxx interfaces, while the input panel implements three very similar interfaces: IIMCallback, IIMCallbackEx, and IIMCallback2. In the following paragraphs, I’ll talk about the IInputMethod, IInputMethod2, IIMCallback, and IIMCallback2 interfaces. The interaction between the input panel and the input method is driven by the input panel. For the most part, the input method simply responds to calls made to its IInputMethod methods. Calls are made when the input method is loaded, when it’s unloaded, and when it’s shown or hidden. In response, the input method must draw in its child window, interpret the user’s actions, and call methods in the IIMCallback interface to send keys to the system or to control the input panel’s window. Input methods are implemented as COM in-proc servers. Because of this, they must conform to the standard COM in-proc server specifications. This means that an input method is implemented as a DLL that exports DllGetClassObject and DllCanUnloadNow functions. Input methods must also export DllRegisterServer and DllUnregisterServer functions that perform the necessary registry registration and deregistration for the server DLL.
Threading Issues with Input MethodsBecause the input panel and input method components are so tightly interrelated, you must follow a few rules when writing an input method. While you can use multiple threads in an input method, the interaction between the input panel and the input method is strictly limited to the input panel’s primary thread. This means that the input method should create any windows during calls to methods in the IInputMethod interface. This ensures that these windows will use the same message loop as the input panel’s window. This, in turn, allows the input panel to directly call the input method’s window procedures, as necessary. In addition, that same thread should make all calls made back to the IIMCallback interface.In short, try not to multithread your input method. If you must use multiple threads, create all windows in your input method using the input panel’s thread. Secondary threads can be created, but they can’t call the IIMCallback interface and they shouldn’t create any windows.
The IInputMethod and IInputMethod2 InterfacesThe IInputMethod interface is the core of an IM. Using the interface’s methods, an IM should create any windows, react to any changes in the parent input panel window, and provide any cleanup when it’s released. The IInputMethod interface exports the following methods in addition to the standard IUnknown methods:
In addition to the preceding methods, the IInputMethod2 interface has the following methods:
Let’s now look at these methods in detail so that we can understand the processing necessary for each. The descriptions of the methods for the IInputMethod interface also apply for the similarly named methods in the IInputMethod2 interface. IInputMethod::Select When the user chooses your input method, the DLL that contains your IM is loaded and the Select method is called. This method is prototyped as
HRESULT IInputMethod::Select (HWND hwndSip); The only parameter is the handle to the SIP window that’s the parent of your input method’s main window. You should return S_OK to indicate success or E_FAIL if you can’t create and initialize your input method successfully. When the Select method is called, the IM will have just been loaded into memory and you’ll need to perform any necessary initialization. This includes registering any window classes and creating the input method window. The IM should be created as a child of the SIP window because the SIP window is what will be shown, hidden, and moved in response to user action. You can call GetClientRect with the parent window handle to query the necessary size of your input window. IInputMethod::GetInfo After the input panel has loaded your IM, it calls the GetInfo method. The input panel calls this method to query the bitmaps that represent the IM. These bitmaps appear in the SIP button on the taskbar. In addition, the IM can provide a set of flags and the size and location on the screen where it would like to be displayed. This method is prototyped as
HRESULT IInputMethod::GetInfo (IMINFO *pimi); The only parameter is a pointer to an IMINFO structure that the IM must fill out to give information back to the SIP. The IMINFO structure is defined as
typedef struct {The first field, cbSize, must be filled with the size of the IMINFO structure. The next two fields, hImageNarrow and hImageWide, should be filled with handles to image lists that contain the bitmaps that will appear on the taskbar SIP button. The Pocket PC’s menu bar uses the narrow image. However, for embedded systems, the shell has the flexibility to use either the wide 32-by-16-pixel bitmap or the narrow 16-by-16-pixel bitmap, depending on its needs. The input method must create these image lists and pass the handles in this structure. The IM is responsible for destroying the image lists when a user or an application unloads it. You can create these image lists in the GetInfo method as long as you design your application to know not to create the image lists twice if GetInfo is called more than once. Another strategy is to create the image lists in the Select method and store the handles as member variables of the IInputMethod object. Then when GetInfo is called, you can pass the handles of the already created image lists to the input panel. The next two fields, iNarrow and iWide, should be set to the index in the image lists for the bitmap you want the SIP to use. For example, you might have two different bitmaps for the SIP button, depending on whether your IM is docked or floating. You can then have an image list with two bitmaps, and you can specify the index depending on the state of your IM. The fdwFlags field should be set to a combination of the flags SIPF_ON, SIPF_DOCKED, SIPF_LOCKED, and SIPF_DISABLECOMPLETION, all of which define the state of the input panel. The first three flags are the same flags that I described in Chapter 14. When the SIPF_DISABLECOMPLETION flag is set, the auto-completion function of the SIP is disabled. Finally, the rcSipRect field should be filled with the default rectangle for the input method. Unless you have a specific size and location on the screen for your IM, you can simply query the client rectangle of the parent SIP window for this rectangle. Note that just because you request a size and location of the SIP window doesn’t mean that the window will have that rectangle. You should always query the size of the parent SIP window when laying out your IM window. IInputMethod::ReceiveSipInfo The ReceiveSipInfo method is called by the input panel when the input panel is shown and then again when an application moves or changes the state of the input panel. The method is prototyped as
HRESULT IInputMethod::ReceiveSipInfo (SIPINFO *psi); The only parameter is a pointer to a SIPINFO structure that I described in Chapter 14. When this method is called, only two of the fields are validthe fdwFlags field and the rcSipRect field. The rcSipRect field contains the size and location of the input panel window, while the fdwFlags field contains the SIPF_xxx flags previously described. In response to the ReceiveSipInfo method call, the IM should save the new state flags and rectangle. IInputMethod::RegisterCallback The input panel calls the RegisterCallback method once, after the input method has been selected. The method is prototyped as
HRESULT IInputMethod::RegisterCallback (IIMCallback *lpIMCallback); This method is called to provide a pointer to the IIMCallback interface. The only action the IM must take is to save this pointer so that it can be used to provide feedback to the input panel. IInputMethod::Showing and IInputMethod::Hiding The input panel calls the Showing and Hiding methods just before the IM is shown or hidden. Both these methods have no parameters and you should simply return S_OK to indicate success. The Showing method is also called when the panel is moved or resized. This makes the Showing method a handy place for resizing the IM child window to properly fit in the parent input panel window. IInputMethod::GetImData and IInputMethod::SetImData The GetImData and SetImData methods give you a back door into the IM for applications that need to have a special communication path between the application and a custom IM. This arrangement allows a specially designed IM to provide additional data to and from applications. The two methods are prototyped as
HRESULT IInputMethod::GetImData (DWORD dwSize, void* pvImData); For both functions, pvImData points to a block of memory in the application. The dwSize parameter contains the size of the memory block. When an application is sending data to a custom IM, it calls SHSipInfo with the SPI_SETSIPINFO flag. The pointer to the buffer and the size of the buffer are specified in the pvImData and dwImDataSize fields of the SIPINFO structure. If these two fields are nonzero, the input panel then calls the SetImData method with the pointer and the size of the buffer contained in the two parameters of the method. The input method then accepts the data in the buffer pointed to by pvImData. When an application calls SHSipInfo with the SPI_GETSIPINFO structure and nonzero values in pvImData and dwImDataSize, the input panel then calls the GetImData method to retrieve data from the input method. IInputMethod::Deselect When the user or a program switches to a different default IM, the input panel calls Deselect. Your input method should save its state (its location on the screen, for example), destroy any windows it has created, and unregister any window classes it has registered. It should also destroy any image lists it’s still maintaining. The prototype for this method is
HRESULT IInputMethod::Deselect (void); After the Deselect method is called, the SIP will unload the input method DLL. IInputMethod::UserOptionsDlg The UserOptionsDlg method isn’t called by the input panel. Instead, the input panel’s Control Panel applet calls this method when the user taps the Options button. The IM should display a dialog box that allows the user to configure any settable parameters in the input method. The UserOptionsDlg method is prototyped as
HRESULT IInputMethod::UserOptionsDlg (HWND hwndParent); The only parameter is the handle to the window that should be the parent window of the dialog box. Because the IM might be unloaded after the dialog box is dismissed, any configuration data should be saved in a persistent place such as the registry, where it can be recalled when the input panel is loaded again. The following two methods are supported only in the IInputMethod2 interface. The IInputMethod2 interface is derived from IInputMethod; all the methods previously described are therefore implemented in IInputMethod2. IInputMethod2::RegisterCallback2 The input panel calls the RegisterCallback2 method once, after the input method has been selected. The method is prototyped as
HRESULT IInputMethod2::RegisterCallback2 (IIMCallback2 *lpIMCallback); This method is called to provide a pointer to the IIMCallback2 interface. The only action the IM must take is to save this pointer so that it can be used to provide feedback to the input panel. IInputMethod2::SetIMMActiveContext The input panel calls SetIMMActiveContext to inform the input method of changes in state of the IME. The method is prototyped as
HRESULT SetIMMActiveContext (HWND hwnd, BOOL bOpen, DWORD dwConversion, The hwnd parameter is the handle of window control that has changed state. The bOpen parameter indicates whether the IME is on or off. The dwConversion and dwSentence parameters provide status on the current mode of the IME. The hkl parameter contains the handle to the current active keyboard layout.
The IIMCallback and IIMCallback2 InterfacesThe IIMCallback interface allows an IM to call back to the input panel for services such as sending keys to the operating system. Aside from the standard IUnknown methods that can be ignored by the IM, IIMCallback exposes only four methods:
It’s appropriate that the IIMCallback interface devotes three of its four methods to sending keys and characters to the system because that’s the primary purpose of the IM. The IIMCallback2 interface adds one method:
Let’s take a quick look at each of these methods. IIMCallback::SetImInfo The SetImInfo method allows the IM control over its size and location on the screen. This method can also be used to set the bitmaps representing the IM. The method is prototyped as
HRESULT IIMCallback::SetImInfo (IMINFO *pimi); The only parameter is a pointer to an IMINFO structure. This is the same structure that the IM uses when it calls the GetInfo method of the IInputMethod interface, but I’ll repeat it here for clarity.
typedef struct {This structure enables an IM to provide the input panel with the information that the IM retrieved in GetInfo. The IM must correctly fill in all the fields in the IMINFO structure because it has no other way to tell the input panel to look at only one or two of the fields. You shouldn’t re-create the image lists when you’re calling SetImInfo; instead, use the same handles you passed in GetInfounless you want to change the image lists used by the input panel. In that case, you’ll need to destroy the old image lists after you’ve called SetImInfo. You can use SetImInfo to undock the input panel and move it around the screen by clearing the SIPF_DOCKED flag in fdwFlags and specifying a new size and location for the panel in the rcSipRect field. Because Windows CE doesn’t provide system support for dragging an input panel around the screen, the IM is responsible for providing such a method. The sample IM that I present beginning on page 879 supports dragging the input panel around by creating a gripper area on the side of the panel and interpreting the stylus messages in this area to allow the panel to be moved around the screen. IIMCallback::SendVirtualKey The SendVirtualKey method is used to send virtual key codes to the system. The difference between this method and the SendCharEvents and SendString methods is that this method can be used to send noncharacter key codes, such as those from cursor keys and shift keys, that have a global impact on the system. Also, key codes sent by SendVirtualKey are affected by the system key state. For example, if you send an a character and the Shift key is currently down, the resulting WM_CHAR message contains an A character. SendVirtualKey is prototyped as
HRESULT IIMCallback::SendVirtualKey (BYTE bVk, DWORD dwFlags); The first parameter is the virtual key code of the key you want to send. The second parameter can contain one or more flags that help define the event. The flags can be either 0 or a combination of flags. You would use KEYEVENTF_KEYUP to indicate that the event is a key up event as opposed to a key down event and KEYEVENTF_SILENT, which specifies that the key event won’t cause a key click to be played for the event. If you use SendVirtualKey to send a character key, the character will be modified by the current shift state of the system. IIMCallback::SendCharEvents The SendCharEvents method can be used to send specific characters to the window with the current focus. The difference between this method and the SendVirtualKey method is that SendCharEvents gives you much more control over the exact information provided in the WM_KEYxxx and WM_CHAR messages generated. Instead of simply sending a virtual key code and letting the system determine the proper character, this method allows you to specify the virtual key and associate a completely different character or series of characters generated by this event. For example, in a simple case, calling this method once causes the messages WM_KEYDOWN, WM_CHAR, and WM_KEYUP all to be sent to the focus window. In a more complex case, this method can send a WM_KEYDOWN and multiple WM_CHAR messages, followed by a WM_KEYUP message. This method is prototyped as
HRESULT IIMCallback::SendCharEvents (UINT uVK, UINT uKeyFlags, The first parameter is the virtual key code that will be sent with the WM_KEYDOWN and WM_KEYUP messages. The second parameter is an unsigned integer containing the key flags that will be sent with the WM_KEYDOWN and WM_KEYUP messages. The third parameter is the number of WM_CHAR messages that will be generated by this one event. The next parameter, puShift, should point to an array of key state flags, while the final parameter, puChar, should point to an array of Unicode characters. Each entry in the shift array will be joined with the corresponding Unicode character in the character array when the WM_CHAR messages are generated. This allows you to give one key on the IM keyboard a unique virtual key code and to generate any number of WM_CHAR messages, each with its own shift state. IIMCallback::SendString You use the SendString method to send a series of characters to the focus window. The advantage of this function is that an IM can easily send an entire word or sentence, and the input panel will take care of the details such as key down and key up events. The method is prototyped as
HRESULT IIMCallback::SendString (LPTSTR ptszStr, DWORD dwSize); The two parameters are the string of characters to be sent and the number of characters in the string. IIMCallback2:: SendAlternatives2 The SendAlternatives2 method provides a mechanism for the input method to send alternative characters to the IME. For languages with hundreds or thousands of characters, the input method might have to guess at the intended character entered by the user. These guesses or alternative characters are sent using SendAlternatives2 to the IME so that it can present the alternatives to the active control. If the control doesn’t handle the alternative suggestions, the first character in the list is used as the correct character. The prototype of SendAlternatives2 is
HRESULT SendAlternatives2(LMDATA * plmd); The one parameter is a pointer to an LMDATA structure defined as
typedef struct _tagLMDATA {The version field should be set to 0x10000. The flags field describes the format of the data in the table provided. The cnt field contains the number of entries in the table. The dwOffsetSymbols, dwOffsetSkip, and dwOffsetScore fields contain the offset of the start of the respective tables containing the alternative data. The data in the tables vary depending on how the IME and the input method agree to share data.
The NumPanel Example Input MethodThe NumPanel example code demonstrates a simple IM. NumPanel gives a user a simple numeric keyboard including keys 0 through 9 as well as the four arithmetic operators: +, –, *, and / and the equal sign key (=). Although it’s not particularly useful to the user, NumPanel does demonstrate all the requirements of an input method. The NumPanel example is different from the standard IMs that come with the Pocket PC in that it can be undocked. The NumPanel IM has a gripper bar on the left side of the window that can be used to drag the SIP around the screen. When a user double-taps the gripper bar, the SIP snaps back to its docked position. Figure 15-4 shows the NumPanel IM in its docked position, while Figure 15-5 shows the same panel undocked.Figure 15-4 The NumPanel IM window in its docked position
Click to view graphic Figure 15-5 The NumPanel IM window undocked The source code that implements NumPanel is divided into two main files, IMCommon.cpp and NumPanel.c. IMCommon.cpp provides the COM interfaces necessary for the IM, including the IInputMethod interface and the IClassFactory interface. IMCommon.cpp also contains DllMain and the other functions necessary to implement a COM in-proc server. NumPanel.c contains the code that implements the NumPanel window. This code comprises the NumPanel window procedure and the supporting message-handling procedures. The source code for NumPanel is shown in Figure 15-6. Figure 15-6 The NumPanel source code NumPanel.def
; IMCommon.rc
//====================================================================== IMCommon.h
//======================================================================
#define ID_ICON 1
STDMETHODIMP_(ULONG) AddRef (THIS); NumPanel.h
//======================================================================
#define MSGCODE_SETINFO 2 IMCommon.cpp
//======================================================================
STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID *ppv) {
// Set the friendly name of the SIP.
// Object constructor
Last Updated: Saturday, July 7, 2001 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||