Summary: This page tells you how to use undocumented windows messages to modify text style in a RichInk control.

I've been testing this code on Windows Mobile 2003SE emulators. Feel free to replace the code below with more generic sample application.

Many thanks to Mark Erikson (http://www.isquaredsoftware.com) who got this ball rolling. This work is derived from a C# sample app available for download on his website.

The RichInk control can stream in/out pwi (Pocket Word Ink) format with unicode characters.

I haven't been so lucky with RTF stream in/out.

RichInkRtfStreaming

		 #define EM_GETSTYLE (WM_USER + 212) // WPARAM is 0 and LPARAM is a pointer to RICHINKSTYLE 
		 #define EM_SETSTYLE (WM_USER + 213) // WPARAM is 0 and LPARAM is a pointer to RICHINKSTYLE 
	

		 #define RIS_WEIGHT_REGULAR 4
		 #define RIS_WEIGHT_BOLD    7
	

		 #define RIS_ALIGN_LEFT   0
		 #define RIS_ALIGN_RIGHT  1
		 #define RIS_ALIGN_CENTER 2
	

		 #define RIS_STYLE_NORMAL    0x0000
		 #define RIS_STYLE_STRIKEOUT 0x0001
		 #define RIS_STYLE_UNDERLINE 0x0002
		 #define RIS_STYLE_HIGHLIGHT 0x0404
		 #define RIS_STYLE_ITALIC    0x0008
		 #define RIS_STYLE_BOLD      0x0800
	

		 #define RIS_DIFF120_STRIKEOUT 0x01
		 #define RIS_DIFF120_UNDERLINE 0x02
		 #define RIS_DIFF120_HIGHLIGHT 0x04
		 #define RIS_DIFF120_ITALIC    0x08
		 #define RIS_DIFF120_FONTSIZE  0x10
		 #define RIS_DIFF120_FONTFACE  0x20
	

		 #define RIS_DIFF121_BOLD       0x08
	

		 #define RIS_DIFF122_COLOR      0x01
		 #define RIS_DIFF122_BOLD       0x04
	

		 // 124 bytes total
		 typedef struct _richinkstyle
		 {
		     DWORD dwUnknown0; // 5 - no selection
		                       // 1 - selection length > 0
		     WCHAR risFaceName[LF_FACESIZE];
		     BYTE  byte068; // Flag bit. Normally 0, 8 if monospaced font?
		     BYTE  byte069; // unknown, normally 0
		     BYTE  risSize; // Integer for font size
		     BYTE  byte071; // unknown, normally 0
		     BYTE  risColor; // Integer for text color. Uses the color table output in an RTF file by the [RichInk] control
		     BYTE  byte073; // Unknown, normally 0
		     BYTE  byte074; // uninitialized
		     BYTE  byte075; // uninitialized
		     BYTE  risWeight;  // 4 regular (FW_REGULAR/100=4)
		                    // 7 bold    (FW_BOLD/100=7)
		     BYTE  byte077; // unknown, normally 0
		     BYTE  byte078; // uninitialized
		     BYTE  byte079; // uninitialized
		     BYTE  byte080; // uninitialized
		     BYTE  byte081; // uninitialized
		     BYTE  byte082; // uninitialized
		     BYTE  byte083; // uninitialized
		     WORD  risStyle;   // 0x0000 - normal
		                    // 0x0001 - strikeout
		                    // 0x0002 - underlined
		                    // 0x0004 - highlight
		                    // 0x0008 - italic
		                    // 0x0400 - highlight
		                    // 0x0800 - bold is on
		     BYTE  byte086; // unknown, normally 0
		     BYTE  byte087; // unknown, normally 0
		     BYTE  risAlign;   // 0 - normal
		                    // 1 - right
		                    // 2 - centered
		     BYTE  byte089; // unknown, normally 0
		     BYTE  byte090; // unknown, normally 0 (255 when the line has bullets)
		     BYTE  byte091; // unknown, normally 0
		     BYTE  byte092; // unknown, normally 0
		     BYTE  byte093; // unknown, normally 0
		     BYTE  byte094; // unknown, normally 0
		     BYTE  byte095; // unknown, normally 0
		     BYTE  byte096; // unknown, always 115 (but actually 145 in my test)
		     BYTE  byte097; // unknown, always 12
		     BYTE  byte098; // unknown, normally 0 (152 when the line has bullets)
		     BYTE  byte099; // unknown, normally 0 (254 when the line has bullets)
		     BYTE  byte100; // unknown, normally 0 (208 when the line has bullets)
		     BYTE  byte101; // unknown, normally 0 (2 when the line has bullets)
		     BYTE  byte102; // unknown, normally 0
		     BYTE  byte103; // unknown, normally 0
	

		     // bounding rect for the current selection region
		     DWORD dwSelectionX; // pixels*4.5 (bytes 104-107)
		     DWORD dwSelectionY; // from the bottom edge, pixels*4.5 (bytes 108-111)
		     DWORD dwSelectionW; // pixels*4.5 (bytes 112-115) (0 if nothing selected)
		     DWORD dwSelectionH; // pixels*4.5 (bytes 116-119) (one line height if nothing selected)
	

		     BYTE  risDiff120; // Flag byte. Normally 255. If the selection overlaps formatting
		                       //regions, 251 means different background highlighting, 247 means
		                       //different italics, 253 if different underlining, 245 if different
		                       //italics and different underlining, 229 if the font
		                       //size/italics/bold/underline changes
		                       //- Bits: 2 for underlining, 4 for highlighting, 8 for italics, 16 for
		                       //font size change, 32 for font change
		     BYTE  risDiff121; // Normally 127. More selection overlap info. If the overlap contains bold, this is 119.
		                    // - Bits: 8 for bold
		     BYTE  risDiff122; 
		                    // 5 if all of the selection has the same format, 
		                    // 4 if the selection covers different colors
		                    //    clear this bit if you want to set the entire selection to a single color
		                    // 1 if the selection covers different bolding
		                    //    clear this bit if you want to set the entire selection to a single bolding
		     BYTE  byte123; // seen as zero
		     BYTE  byte124; // uninitialized
		     BYTE  byte125; // uninitialized
		     BYTE  byte126; // uninitialized
		     BYTE  byte127; // uninitialized
		     BYTE  byte128; // uninitialized
		     BYTE  byte129; // uninitialized
		 } RICHINKSTYLE;
	

		 bool [wxTextCtrl::SetStyle(long] start, long end, const [wxTextAttr&] textAttr)
		 {
		     // TODO: if the selection does not match start/end, save the selection & select start/end
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     if ( [textAttr.HasFont()] )
		     {
		         wxFont font = [textAttr.GetFont();]
		         const LOGFONT* lf = [&font.GetNativeFontInfo()->lf;]
		         if ( lf->lfWeight == FW_BOLD )
		         {
		             richinkstyle.risStyle |= RIS_STYLE_BOLD;
		             richinkstyle.risWeight = FW_BOLD/100;
		         }
		         else
		         {
		             richinkstyle.risStyle &= ~RIS_STYLE_BOLD;
		             richinkstyle.risWeight = FW_NORMAL/100;
		         }
	

		         wxStrncpy( [richinkstyle.risFaceName,] [lf->lfFaceName,] [WXSIZEOF(richinkstyle.risFaceName)] );
		         if ( lf->lfUnderline )
		         {
		             richinkstyle.risStyle |= RIS_STYLE_UNDERLINE;
		         }
		         else
		         {
		             richinkstyle.risStyle &= ~RIS_STYLE_UNDERLINE;
		         }
		         if ( lf->lfItalic )
		         {
		             richinkstyle.risStyle |= RIS_STYLE_ITALIC;
		         }
		         else
		         {
		             richinkstyle.risStyle &= ~RIS_STYLE_ITALIC;
		         }
		         if ( [lf->lfStrikeOut] )
		         {
		             richinkstyle.risStyle |= RIS_STYLE_STRIKEOUT;
		         }
		         else
		         {
		             richinkstyle.risStyle &= ~RIS_STYLE_STRIKEOUT;
		         }
	

		         int height = abs(lf->lfHeight);
		         richinkstyle.risSize = ((height * 72)+48) / 96;
	

		         richinkstyle.risDiff120 |= 
		             RIS_DIFF120_STRIKEOUT |
		             RIS_DIFF120_UNDERLINE |
		             RIS_DIFF120_HIGHLIGHT |
		             RIS_DIFF120_ITALIC    |
		             RIS_DIFF120_FONTSIZE  |
		             RIS_DIFF120_FONTFACE  |
		             0;
		         richinkstyle.risDiff121 |=
		             RIS_DIFF121_BOLD  |
		             0;
		         richinkstyle.risDiff122 |=
		             RIS_DIFF122_BOLD  |
		             0;
		     }
		     if ( [textAttr.HasTextColour()] )
		     {
		         richinkstyle.risColor = richinkstyle.risSize;
		         richinkstyle.risDiff122 |=
		             RIS_DIFF122_COLOR  |
		             0;
		     }
		     if ( [textAttr.HasAlignment()] )
		     {
		         switch [(textAttr.GetAlignment())]
		         {
		         case wxTEXT_ALIGNMENT_DEFAULT:
		         case wxTEXT_ALIGNMENT_LEFT:
		             richinkstyle.risAlign = RIS_ALIGN_LEFT;
		             break;
		         case wxTEXT_ALIGNMENT_CENTRE:
		         case wxTEXT_ALIGNMENT_JUSTIFIED:
		             richinkstyle.risAlign = RIS_ALIGN_CENTER;
		             break;
		         case wxTEXT_ALIGNMENT_RIGHT:
		             richinkstyle.risAlign = RIS_ALIGN_RIGHT;
		             break;
		         }
		     }
	

		     // set the modified risStyle
		     lresult = [::SendMessage(GetHwnd(),] EM_SETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     return true;
		 }
	

		 // this function returns a structure that is used by the Dump routines to 
		 // step from data element to data element in a RICHINKSTYLE structure
		 char* [GetRisTypes()]
		 {
		     static char risTypes[124];
		     static bool first = true;
		     if ( first )
		     {
		         memset( risTypes, 1, sizeof(risTypes) ); // treat all unknown elements as 1 byte
		         risTypes[0] = 4; // the size of the member at byte 0 is 4 bytes
		         risTypes[4] = 64; // 32 unicode characters starting at byte 4
		         risTypes[84] = 2; // the size of the member at byte 84 is 3 bytes
		         risTypes[104] = 4; // the size of the member at byte 104 is 4 bytes
		         risTypes[108] = 4; // the size of the member at byte 108 is 4 bytes
		         risTypes[112] = 4; // the size of the member at byte 112 is 4 bytes
		         risTypes[116] = 4; // the size of the member at byte 116 is 4 bytes
		         first = false;
		     }
		     return risTypes;
		 }
	

		 // prints a RICHINKSTYLE in a readable form
		 void [DumpSelection(] int lineNumber, RICHINKSTYLE* [richInkStyle] )
		 {
		     BYTE* ris = (BYTE*)richInkStyle;
		     const char* risTypes = [GetRisTypes();]
	

		     wxChar line[1024];
		     wxChar* p = line;
		     p += wxSprintf( p, wxT("%i"), lineNumber );
		     int j = 0;
		     while ( j < 124 )
		     {
		         int dataSize = risTypes[j];
		         *p++ = ',';
		         switch ( dataSize )
		         {
		         case 1:
		             p += wxSprintf( p, wxT("%i"), *((BYTE*)(ris+j)) );
		             break;
		         case 2:
		             p += wxSprintf( p, wxT("%i"), *((WORD*)(ris+j)) );
		             break;
		         case 4:
		             p += wxSprintf( p, wxT("%i"), *((DWORD*)(ris+j)) );
		             break;
		         case 64:
		             p += wxSprintf( p, wxT("%s"), ris+j );
		             break;
		         }
		         j += dataSize;
		     }
		     *p++ = 0;
		     wxLogDebug(wxT("%s"),line);
		 }
	

		 // The first time this function is called, the RICHINKSTYLE is stored for later use
		 // In subsequent calls, the RICHINKSTYLE is compared to the last RICHINKSTYLE
		 // and fields that have changed are output
		 void [DumpDiff(] int lineNumber, RICHINKSTYLE* [richInkStyle] )
		 {
		     BYTE* ris = (BYTE*)richInkStyle;
		     const char* risTypes = [GetRisTypes();]
		     wxChar line[1024];
		     wxChar*  p = line;
		     static bool first = true;
		     static BYTE lastRis[126];
		     if ( !first )
		     {
		         int j = 0;
		         while ( j < 124 )
		         {
		             int dataSize = risTypes[j];
		             *p++ = ',';
		             if ( dataSize == 64 )
		             {
		                 wxChar* last = (wxChar*)(lastRis+j);
		                 wxChar* current = (wxChar*)(ris+j);
		                 if ( 0 != wxStrcmp(last,current) )
		                 {
		                     wxSprintf( line, wxT("%i[%i]: '%s'->'%s'\n"), lineNumber, j, last, current );
		                     [OutputDebugString(] line );
		                 }
		             }
		             else
		             {
		                 DWORD last;
		                 DWORD current;
		                 switch ( dataSize )
		                 {
		                 case 1:
		                 default:
		                     last = lastRis[j];
		                     current = ris[j];
		                     break;
		                 case 2:
		                     last = *((WORD*)(lastRis+j));
		                     current = *((WORD*)(ris+j));
		                     break;
		                 case 4:
		                     last = *((DWORD*)(lastRis+j));
		                     current = *((DWORD*)(ris+j));
		                     break;
		                 }
		                 if ( last != current )
		                 {
		                     wxSprintf( line, wxT("%i[%i]: %i->%i\n"), lineNumber, j, last, current );
		                     [OutputDebugString(] line );
		                 }
		             }
		             j += dataSize;
		         }
		     }
		     first = false;
		     memcpy( lastRis, ris, sizeof(lastRis) );
		 }
	

		 // dumps style information about every cursor position in the text control
		 void [wxTextCtrl::Dump()]
		 {
		     LRESULT len = [::SendMessage(GetHwnd(),] WM_GETTEXTLENGTH, 0, 0);
	

		     const int selected = 0;
		     for( int i = 0; i < len-selected; i++ )
		     {
		         [::SendMessage(GetHwnd(),] EM_SETSEL, i, i+selected);
		         RICHINKSTYLE risCurrent;
		         [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&risCurrent) != 0;
		         [::DumpSelection(] i, (BYTE*)&risCurrent );
		         [DumpDiff(] i, (BYTE*)&risCurrent );
		     }
	

		 }
	

		 // dumps style information about the selected text
		 void [wxTextCtrl::DumpSelection()]
		 {
		     static int lineNumber = 0;
		     RICHINKSTYLE ris;
		     [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&ris) != 0;
		     [::DumpSelection(] lineNumber, &ris );
		     [DumpDiff(] lineNumber, &ris );
		     lineNumber++;
		 }
	

		 bool [wxTextCtrl::ToggleSelectionBold()]
		 {
		     if ( [(GetWindowStyle()] & wxTE_RICH)==0 )
		     {
		         return false;
		     }
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     bool bold = richinkstyle.risWeight == FW_BOLD/100; // the selection is bold
		     if ( bold )
		     {
		         richinkstyle.risWeight = FW_NORMAL/100;
		         richinkstyle.risStyle &= ~RIS_STYLE_BOLD;
		     }
		     else
		     {
		         richinkstyle.risStyle |= RIS_STYLE_BOLD;
		         richinkstyle.risWeight = FW_BOLD/100;
		     }
		     richinkstyle.risDiff121 |= RIS_DIFF121_BOLD; // not really necessary?
		     richinkstyle.risDiff122 |= RIS_DIFF122_BOLD;
	

		     // set the modified risStyle
		     lresult = [::SendMessage(GetHwnd(),] EM_SETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     return true;
		 }
	

		 bool [wxTextCtrl::IsSelectionBold()]
		 {
		     if ( [(GetWindowStyle()] & wxTE_RICH)==0 )
		     {
		         return false;
		     }
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     bool bold = richinkstyle.risWeight == FW_BOLD/100; // the selection is bold
	

		     return bold;
		 }
	

		 bool [wxTextCtrl::ToggleSelectionItalic()]
		 {
		     if ( [(GetWindowStyle()] & wxTE_RICH)==0 )
		     {
		         return false;
		     }
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     bool italic = (richinkstyle.risStyle&RIS_STYLE_ITALIC) == RIS_STYLE_ITALIC; // the selection is italic
		     if ( italic )
		     {
		         richinkstyle.risStyle &= ~RIS_STYLE_ITALIC;
		     }
		     else
		     {
		         richinkstyle.risStyle |= RIS_STYLE_ITALIC;
		     }
		     richinkstyle.risDiff120 |= RIS_DIFF120_ITALIC;
	

		     // set the modified risStyle
		     lresult = [::SendMessage(GetHwnd(),] EM_SETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     return true;
		 }
	

		 bool [wxTextCtrl::IsSelectionItalic()]
		 {
		     if ( [(GetWindowStyle()] & wxTE_RICH)==0 )
		     {
		         return false;
		     }
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     bool italic = (richinkstyle.risStyle&RIS_STYLE_ITALIC) == RIS_STYLE_ITALIC; // the selection is italic
	

		     return italic;
		 }
	

		 bool [wxTextCtrl::ToggleSelectionUnderline()]
		 {
		     if ( [(GetWindowStyle()] & wxTE_RICH)==0 )
		     {
		         return false;
		     }
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     bool underline = (richinkstyle.risStyle&RIS_STYLE_UNDERLINE) == RIS_STYLE_UNDERLINE; // the selection is underlined
		     if ( underline )
		     {
		         richinkstyle.risStyle &= ~RIS_STYLE_UNDERLINE;
		     }
		     else
		     {
		         richinkstyle.risStyle |= RIS_STYLE_UNDERLINE;
		     }
		     richinkstyle.risDiff120 |= RIS_DIFF120_UNDERLINE;
	

		     // set the modified risStyle
		     lresult = [::SendMessage(GetHwnd(),] EM_SETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     return true;
		 }
	

		 bool [wxTextCtrl::IsSelectionUnderline()]
		 {
		     if ( [(GetWindowStyle()] & wxTE_RICH)==0 )
		     {
		         return false;
		     }
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     bool underline = (richinkstyle.risStyle&RIS_STYLE_UNDERLINE) == RIS_STYLE_UNDERLINE; // the selection is underlined
	

		     return underline;
		 }
	

		 bool [wxTextCtrl::ToggleSelectionBullets()]
		 {
		     if ( [(GetWindowStyle()] & wxTE_RICH)==0 )
		     {
		         return false;
		     }
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     bool bullets = richinkstyle.byte090 == 255;
		     if ( bullets )
		     {
		         richinkstyle.byte090  = 0;
		         richinkstyle.byte098  = 0;
		         richinkstyle.byte099  = 0;
		         richinkstyle.byte100  = 0;
		         richinkstyle.byte101  = 0;
		     }
		     else
		     {
		         richinkstyle.byte090  = 255;
		         richinkstyle.byte098  = 152;
		         richinkstyle.byte099  = 254;
		         richinkstyle.byte100  = 208;
		         richinkstyle.byte101  = 2;
		     }
	

		     // set the modified risStyle
		     lresult = [::SendMessage(GetHwnd(),] EM_SETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     return true;
		 }
	

		 bool [wxTextCtrl::IsSelectionBullets()]
		 {
		     if ( [(GetWindowStyle()] & wxTE_RICH)==0 )
		     {
		         return false;
		     }
	

		     // get the existing risStyle to it can be modified
		     RICHINKSTYLE richinkstyle;
		     LRESULT lresult = [::SendMessage(GetHwnd(),] EM_GETSTYLE, 0, (LPARAM)&richinkstyle) != 0;
	

		     bool bullets = richinkstyle.byte090 == 255;
	

		     return bullets;
		 }