Share via


Using Rich Ink Technology in Microsoft Windows CE 3.0

Microsoft Corporation

June 2000

Summary: Rich Ink technology allows a user to write and draw on a touch-sensitive screen. This technology is supported by the Microsoft® Windows® CE RichInk edit control. This paper describes how to add a RichInk control to an application and provides an example. The paper also provides the API reference for the RichInk control, including functions, messages, structures, and data types. An Appendix describes the process of converting RichInk PWI (Pocket Word Ink) files to RTF or text formats. (10 printed pages)

Contents

Introduction Adding the RichInk Edit Control to an Application Example API Appendix: Converting RichInk PWI Data

Introduction

Rich Ink is the underlying technology that lets a user write and draw on a touch-sensitive screen using a stylus. It provides a convenient means for applications to accept input from a user without use of a keyboard.

Rich Ink technology is exposed in Windows CE through the RichInk edit control. Taking notes or drawing sketches with the RichInk control is very much like writing or drawing on paper. The control includes powerful editing and formatting capabilities. Typical examples of Rich Ink applications include an electronic form that accepts a user's handwritten signature, or a calendar that lets a user jot down a "To Do" list for a selected date.

RichInk libraries reside in the ROM of those Windows CE platforms that support Rich Ink technology. The RichInk header file (richink.h) is supplied with those platform SDKs that support it.

The functions, messages, structures, and data types included in the RichInk API are listed later in this paper.

Adding the RichInk Edit Control to an Application

Providing a RichInk edit control for a Windows CE application requires the following steps:

To embed the RichInk control in your application:

  1. Call InitCommonControls to load the common control DLL.
  2. Call InitInkX to load and initialize RichInk control.
  3. Call CreateWindow and specify the class name as WC_INKX. (Alternatively, call CreateDialog to instantiate a dialog box with a custom ink control.)

After initialization, the RichInk control communicates with the calling application using the standard Windows CE messaging system. Specifically, the control performs the following tasks:

  • Sends the IM_SHOWCMDBAR message to the ink control to show or hide the command bar.
  • Sends the IM_GETDATALEN, IM_GETDATA, and IM_SETDATA messages between the ink control and the application to transmit inking data, such as a note or sketch.
  • Sends the IM_REINIT message to the ink control to erase all the content from the control.
  • Sends the standard EM_GETMODIFY and EM_SETMODIFY messages to the ink control to determine if its content has been modified and to set the modification flag in the control, respectively.

Example

As an example of using the RichInk control, consider a calendar application that has a RichInk control (named InkX) embedded in a dialog box. Invoking SendDlgItemMessage to send an IM_SHOWCMDBAR message can toggle the control’s command bar. The state of the command bar is specified in the accompanying wParam:

SendDlgItemMessage(hInk, IM_SHOWCMDBAR, (WPARAM)m_bCmdBar, 0L);

Here hInk is a handle to the InkX control and m_bCmdBar is set to either TRUE or FALSE to specify whether or not the command bar is visible.

To save an edited or a newly created note, obtain the data length using the following:

InkDataLen=SendDlgItemMessage(hInkX, IM_GETDATALEN, 0, 0L);

For each date entry, the application keeps an ink note, pInkData, of the BYTE pointer type. The application must first allocate sufficient memory to store the ink note, and then pass the pInkData pointer to the control through the message's lParam parameter:

InkDatalen =
   SendDlgItemMessage(hInkX, IM_GETDATA, InkDataLen, (LPARAM)pInkData);

When the user taps a calendar date, the application retrieves any previously saved ink data and brings up the ink control. It then sends the following message to refresh the document view with the retrieved ink data:

SendDlgItemMessage(hInkX, IM_SETDATA, dwInkDataLen, (LPARAM)pInkData);

The dwInkDataLen parameter gives the length of the ink data, and pInkData is a pointer to the data itself. You should release the ink data, pInkData, once it has been passed to the ink control.

API

The following tables list the elements of the RichInk API. These items are documented in detail in the online Help topics for Microsoft® eMbedded Visual Tools® 3.0.

Function Description
EditStreamCallback An application-defined callback used with the EM_STREAMIN and EM_STREAMOUT messages. Transfers a data stream to or from a RichInk control.
InitRichInkDLL Loads and initializes the RichInk control.
Message Description
EM_CANREDO Determines if actions can be redone.
EM_CANUNDO Determines if actions cannot be redone.
EM_CLEARALL Clears all of the current document's data.
EM_GETPAGESTYLE Gets the page style of the RichInk Control.
EM_GETPENMODE Gets the new pen mode.
EM_GETVIEW Gets the view style.
EM_GETWRAPMODE Gets the wrap mode—window or page.
EM_GETZOOMPERCENT Gets the zoom percentage.
EM_INSERTLINKS Inserts the passed string as a link string.
EM_REDOEVENT Redoes the allowable action.
EM_SETINKLAYER Sets the page style—including ruled lines.
EM_SETPAGESTYLE Sets individual aspects of the view mode.
EM_SETPENMODE Sets the new pen mode.
EM_SETVIEW Sets the view style—typing, writing, or drawing.
EM_SETVIEWATTRIBUTES Sets the view attributes.
EM_SETWRAPMODE Sets the wrap mode.
EM_SETZOOMPERCENT Sets the zoom percentage.
EM_STREAMIN Replaces the contents of a RichInk Control with a stream of data provided by an application-defined EditStreamCallback function.
EM_STREAMOUT Causes a RichInk Control to pass its contents to an application-defined EditStreamCallback callback function.
EM_UNDOEVENT Reverts to the state of the last action.
Structure Description
EDITSTREAM Contains information that an application passes to a rich edit control in an EM_STREAMIN or EM_STREAMOUT message.
VIEWATTRIBUTES Sets mask values for EM_SETVIEWATTRIBUTES.
Data Type Description
DWORD Data type required for the RichInk stream messages.

The RichInk control also provides support for several Rich Edit and Windows messages, including the following:

EM_CANPASTE EM_GETMODIFY EM_GETSEL*
EM_REPLACESEL* EM_SETMODIFY EM_SETSEL*
WM_CLEAR WM_COPY WM_CUT
WM_GETTEXT WM_GETTEXTLENGTH WM_PASTE
WM_SETTEXT    

*Messages with an asterisk are intended for use with text in the VT_TYPINGVIEW and VT_WRITINGVIEW view modes. Use with VT_DRAWINGVIEW will produce inconsistent results, and is not recommended.

Appendix: Converting RichInk PWI Data

The following pages describe a process for converting the RichInk data found in .pwi (Pocket Word Ink) files to RTF or Text formats. These instructions are intended for knowledgeable Windows programmers who are familiar with the messaging architecture in Windows.

Preliminary notes

  • The RichInk DLL is installed on the desktop with the Windows CE Services and exists in ROM on Windows CE devices. The RichInk.dll can be used for converting from the RichInk internal format (.pwi files) to either .rtf or text file formats.
  • On the desktop, ink data containing ink strokes is converted to RTF metafile data. This technique is not currently supported on the device, so data converted on the device will lose all non-text ink data.
  • If you want to use RichInk technology to convert files on the desktop, use InkEng.dll (found in the ActiveSync folder) rather than RichInk.dll. InkEng.dll maintains the same interfaces for EM_STREAMIN and EM_STREAMOUT as the device uses.

Conversion process

// Defines and typedefs needed for the conversion:
#define EM_STREAMIN            (WM_USER + 73)
#define EM_STREAMOUT         (WM_USER + 74)
typedef DWORD (CALLBACK *EDITSTREAMCALLBACK)
      (DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
typedef struct _editstream
{
   DWORD dwCookie; /* user value passed to callback as first parameter */
   DWORD dwError;  /* last error */
   EDITSTREAMCALLBACK pfnCallback;
} EDITSTREAM;
typedef struct tagCOOKIE
{
   HANDLE hFile;
   LPBYTE pbStart;
   LPBYTE pbCur;
   LONG  bCount;
   DWORD dwError;
} COOKIE, * PCOOKIE;
// Stream formats:
#define SF_TEXT      0x0001
#define SF_RTF      0x0002
#define SF_UNICODE   0x0010      // Unicode data of some kind
#define SF_UTEXT   SF_TEXT | SF_UNICODE   // Unicode text file
#define SFF_PWI      0x0800      
#define SF_PWI      ( SF_RTF | SFF_PWI | 0x010000 ) // PWI format

The first step is to load the RichInk DLL:

HINSTANCE   hLib;
   hLib = LoadLibrary( TEXT(“richink.dll” ) );
   if (!hLib)
      return ERROR;

The next step is to create a RichInk window:

HWND   hwndInk = NULL;
   hwndInk = CreateWindow(“richink”, ES_READONLY, CW_USEDEFAULT,
   CW_USEDEFAULT, CW_USEDEFAULT, 
   CW_USEDEFAULT, NULL, 0, NULL, NULL);
   if (!hwndInk)
      return ERROR;

With the RichInk window we can send EM_STREAMIN and EM_STREAMOUT messages in a manner very similar to the RichEdit desktop control:

EDITSTREAM   es;
COOKIE      cookie;
   // Stream in the Ink Data from a memory block.
   memset(&cookie, 0, sizeof(cookie));
   cookie.dwError      = 0;
   cookie.pbStart      = (LPBYTE)pb;      // Pointer to memory block
   cookie.pbCur       = cookie.pbStart;
   cookie.bCount   = cb;         // Size of ink data
   es.dwCookie        = (DWORD)&cookie;
   es.dwError        = 0;
   es.pfnCallback      = BufferReadCallback;
   lResult = SendMessage (hwndInk, EM_STREAMIN, (WPARAM)SF_PWI, (LPARAM)&es);
   // Next call stream out to get the size of the Unicode text.
   cookie.dwError      = 0;
   cookie.pbStart      = 0;
   cookie.pbCur       = 0;
   cookie.bCount   = 0;
   es.dwCookie        = (DWORD)&cookie;
   es.dwError        = 0;
   es.pfnCallback      = BufferWriteCallback;
   lResult = SendMessage (hwndInk, EM_STREAMOUT, (WPARAM)SF_UTEXT, (LPARAM)&es);
   // Allocate memory and stream out the Unicode text to it. 
   sz = (LPWSTR)LocalAlloc(LPTR, (cbSz = Cookie.pbCur - Cookie.pbStart) + 2);
   // Check for alloc err.
   cookie.dwError   = 0;
   cookie.pbStart    = (LPBYTE)sz;
   cookie.pbCur      = cookie.pbStart;
   cookie.bCount   = cbSz;
   es.dwCookie        = (DWORD)&cookie;
   es.dwError        = 0;
   es.pfnCallback      = BufferWriteCallback;
   lResult = SendMessage (hwndInk, EM_STREAMOUT, (WPARAM)SF_UTEXT, (LPARAM)&es);

Now finally some sample Callback functions:

static DWORD CALLBACK 
   BufferReadCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
   PCOOKIE pCookie = (PCOOKIE)dwCookie;
   LONG   bytesLeft;
   LONG   bytesRead;
   // Calc the bytes read.
   bytesRead = pCookie->pbCur - pCookie->pbStart;
   // Calc bytes left to read.
   if (bytesRead < pCookie->bCount)
   {
      // Calc the bytes left to read.
      bytesLeft = pCookie->bCount - bytesRead;
   }
   else
   {
      bytesLeft = 0;
   }
   // Don’t read past the end of buffer.
   if (cb > bytesLeft) cb = bytesLeft;
   // Set bytes read.
   *pcb = cb;
   // Copy any bytes.
   if (cb)
   {
      memcpy(pbBuff, pCookie->pbCur, cb);
      pCookie->pbCur += cb;
   }
   // Return no error.
   return 0;
} // BufferReadCallback()
static DWORD CALLBACK 
   BufferWriteCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
   PCOOKIE pCookie = (PCOOKIE)dwCookie;
   PCOOKIE pCookie = (PCOOKIE)dwCookie;
   // Do not overwrite the end of the buffer.
   // If there is no output buffer, then we’re
   // only here to determine the space required
   // for the stream out.
   If (pCookie->pbStart && 
      (pCookie->pbCur + cb > pCookie->pbStart + pCookie->bCount))
   {
      // Writing all this data would overflow the buffer.
      // So only write what will fit.
      cb = pCookie->pbStart + pCookie->bCount - pCookie->pbCur;
   }
   *pcb = cb;
   if (pCookie->pbStart)
   memcpy(pCookie->pbCur, pbBuff, cb); pCookie->pbCur += cb;
   return 0;
} // BufferWriteCallback()

The foregoing example streams out Unicode text, but could be used to stream out RTF or ANSI text by changing the Stream Format to SF_RTF or SF_TEXT. By changing the callback functions, data can be read from and/or written to a file.

Sample callback functions for reading/writing

Here are some sample callback functions for file reading and writing:

static DWORD CALLBACK 
   ReadCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
   PCOOKIE pCookie = (PCOOKIE)dwCookie;
   // Zero the bytes read.
   *pcb = 0;
   // If there is an error return it.
   if (ReadFile(pCookie->hFile, pbBuff, cb, pcb, 0) == 0)
   return (pCookie->dwError = GetLastError());
   // Return no error.
   return 0;
} // ReadCallback()

static DWORD CALLBACK 
   WriteCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
   PCOOKIE pCookie = (PCOOKIE)dwCookie;
   if (WriteFile(pCookie->hFile, pbBuff, cb, pcb, 0) == 0)
   return (pCookie->dwError = GetLastError());
   return 0;
} // WriteCallback()

Compatibility issues

The previous code describes the interface for desktop Windows CE Services 2.2. This interface is also the same for Handheld PC 2.11 and Palm-sized PC 1.2 devices. For previous versions of Windows CE Services or for HPC 2.0 or Palm-size PC 1.0 devices, the following change must be made:

  • Earlier versions passed a pointer to a variable containing the Stream Format rather than just the Stream Format constant directly. For example:

    DWORDwpSF = SF_PWI;

    SendMessage (hwndInk, EM_STREAMIN, (WPARAM)&wpSF, (LPARAM)&es);

  • Earlier versions may also require the use of the following constant when writing Unicode text files:

    #define SF_UTEXT   0x0005