Using tables in RichEdit in Delphi

I am trying to use Tables in a TRichEdit control in Delphi XE2 Starter Edition. (In other words, I do not have source code for XE2, but I have it for TurboDelphi). I understand that the RichEdit control by default does not use the version of MS RichEdit that supports tables, so I subclassed it to use MS RichEdit v4.1, as described here 1 and 6 here, and also modeled the code in JEDI TjvRichEdit. (For brevity, I did not include a code segment that defines RichEdit version numbers for DLLs other than v4.1 that I borrowed from JEDI. A simplified version is shown here.)

The MSDN 2 blog claims that Windows messages for supporting RTF tables were an undocumented feature of MS RichEdit version 4.1 and that the EM_INSERTTABLE message is available with Windows XP Service Pack 2 (SP2). For more information about which versions are available, see here 3 .

The comment following this blog post, posted by David Kinder on September 26, 2008, states that he was able to receive the EM_INSERTTABLE message to work with RichEdit v4.1 using the same code as I showed below (except that he did not use Delphi).

For more information on the EM_INSERTTABLE message and its supporting structures, see MSDN 4 documents (which indicate that they were introduced with Windows 8, but which clearly preceded this with at least two major versions of the OS). Also note that the definition (structure) of structures has changed a bit since Murray wrote his blog 2 in 2008. I searched until the end of the Internet and can’t find the version of MS richedit.h that comes with RichEdit 4.1 and includes the “undocumented” structures TABLEROWPARMS and TABLECELLPARMS since they existed at that time, so I am limited to existing MSDN documents as existing for Win8 4 and the Murrays 2 blog, as they supposedly existed in Win XP and Win7.

Here is my RichEdit:

unit MyRichEdit; //Customized RichEdit to use MS RichEdit v4.1 // Some stuff borrowed from Michael Lam REdit (ca. 1998), found on the Torry page. interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, StdCtrls, ComCtrls, Printers, RichEdit; type {TableRowParms} PTableRowParms = ^TTableRowParms; _tableRowParms = packed record cbRow : BYTE ; // Count of bytes in this structure cbCell : BYTE ; // Count of bytes in TABLECELLPARMS cCell : BYTE ; // Count of cells cRow : BYTE ; // Count of rows dxCellMargin : LONG ; // Cell left/right margin (\trgaph) dxIndent : LONG ; // Row left (right if fRTL indent (similar to \trleft) dyHeight : LONG ; // Row height (\trrh) nAlignment{:3}: DWORD; // Row alignment (like PARAFORMAT::bAlignment, \trql, trqr, \trqc) fRTL{:1} : DWORD; // Display cells in RTL order (\rtlrow) fKeep{:1} : DWORD; // Keep row together (\trkeep} fKeepFollow{:1} : DWORD; // Keep row on same page as following row (\trkeepfollow) fWrap{:1} : DWORD; // Wrap text to right/left (depending on bAlignment) (see \tdfrmtxtLeftN, \tdfrmtxtRightN) fIdentCells{:1} : DWORD; // lparam points at single struct valid for all cells //cpStartRow : LONG ; // not in Murray blog version, so commented here... //bTableLevel : BYTE; // not in Murray blog version //iCell : BYTE; // not in Murray blog version end; TABLEROWPARMS = _tableRowParms; TTableRowParms = TABLEROWPARMS; {TableCellParms} PTableCellParms = ^TTableCellParms; _tableCellParms = packed record dxWidth : LONG ; // Cell width (\cellx) nVertAlign{:2} : WORD ; // Vertical alignment (0/1/2 = top/center/bottom \clvertalt (def), \clvertalc, \clvertalb) fMergeTop{:1} : WORD ; // Top cell for vertical merge (\clvmgf) fMergePrev{:1} : WORD ; // Merge with cell above (\clvmrg) fVertical{:1} : WORD ; // Display text top to bottom, right to left (\cltxtbrlv) wShading : WORD ; // Shading in .01% (\clshdng) eg, 10000 flips fore/back dxBrdrLeft : SHORT ; // Left border width (\clbrdrl\brdrwN) (in twips) dyBrdrTop : SHORT ; // Top border width (\clbrdrt\brdrwN) dxBrdrRight : SHORT ; // Right border width (\clbrdrr\brdrwN) dyBrdrBottom : SHORT ; // Bottom border width (\clbrdrb\brdrwN) crBrdrLeft : COLORREF; // Left border color (\clbrdrl\brdrcf) crBrdrTop : COLORREF; // Top border color (\clbrdrt\brdrcf) crBrdrRight : COLORREF; // Right border color (\clbrdrr\brdrcf) crBrdrBottom : COLORREF; // Bottom border color (\clbrdrb\brdrcf) crBackPat : COLORREF; // Background color (\clcbpat) crForePat : COLORREF; // Foreground color (\clcfpat) end; TABLECELLPARMS = _tableCellParms; TTableCellParms = TABLECELLPARMS; TMyRichEdit = class(ComCtrls.TRichEdit) private function GetRTF: string; // get the RTF string procedure SetRTF(InRTF: string); // set the RTF string protected procedure CreateParams(var Params: TCreateParams); override; published property RTFText: string read GetRTF write SetRTF; end; //-------------------------------------------------------------- // GLOBAL VARIABLES //-------------------------------------------------------------- var RichEditVersion : Integer; //Version of the MS Windows RichEdit DLL const RichEdit10ModuleName = 'RICHED32.DLL'; RichEdit20ModuleName = 'RICHED20.DLL'; RichEdit41ModuleName = 'MSFTEDIT.DLL'; MSFTEDIT_CLASS = 'RichEdit50W'; //goes with RichEdit 4.1 (beginning with Win XP SP2) EM_INSERTTABLE = WM_USER + 232; implementation function TMyRichEdit.GetRTF: string; var FStream : TStringStream; begin // get the RTF string FStream := TStringStream.Create; // RTF stream FStream.Clear; FStream.Position := 0; Lines.SaveToStream(FStream); Result := FStream.DataString; FStream.Free; // free the RTF stream end; //ok procedure TMyRichEdit.SetRTF(InRTF: string); var FStream : TStringStream; begin // set the RTF string // LoadFromStream uses an EM_STREAMIN windows msg, which by default REPLACES the contents of a RichEdit. FStream := TStringStream.Create; // RTF stream FStream.Clear; FStream.Position := 0; FStream.WriteString(InRTF); FStream.Position := 0; Lines.LoadFromStream(FStream); Self.Modified := false; FStream.Free; // free the RTF stream end; //ok //=========================================================================== //Defaults: RICHEDIT_CLASS = 'RichEdit20W'; RICHEDIT_CLASS10A = 'RICHEDIT'; //It needs to use RichEdit50W for version 4.1, which I defined in a constant above as MSFTEDIT_CLASS. procedure TMyRichEdit.CreateParams(var Params: TCreateParams); begin inherited CreateParams(Params); If RichEditVersion = 1 then CreateSubClass(Params, RICHEDIT_CLASS10A) else If RichEditVersion = 4 then CreateSubClass(Params, MSFTEDIT_CLASS) else CreateSubClass(Params, RICHEDIT_CLASS); end; //================================================================ {Initialization Stuff} //================================================================ var GLibHandle: THandle = 0; procedure InitRichEditDll; begin //Try to load MS RichEdit v 4.1 into memory... RichEditVersion := 4; GLibHandle := SafeLoadLibrary(RichEdit41ModuleName); if (GLibHandle > 0) and (GLibHandle < HINSTANCE_ERROR) then GLibHandle := 0; //this means it could not find the DLL or it didn't load right. if GLibHandle = 0 then begin RichEditVersion := 2; GLibHandle := SafeLoadLibrary(RichEdit20ModuleName); if (GLibHandle > 0) and (GLibHandle < HINSTANCE_ERROR) then GLibHandle := 0; if GLibHandle = 0 then begin RichEditVersion := 1; GLibHandle := SafeLoadLibrary(RichEdit10ModuleName); if (GLibHandle > 0) and (GLibHandle < HINSTANCE_ERROR) then begin RichEditVersion := 0; GLibHandle := 0; end; end; end; end; procedure FinalRichEditDll; begin if GLibHandle > 0 then begin FreeLibrary(GLibHandle); GLibHandle := 0; end; end; initialization InitRichEditDll; finalization FinalRichEditDll; End. 

Using:

 Uses … MyRichEdit … type TRichEdit = class(TMyRichEdit); TfrmEdit = class(TForm) … memNotes: TRichEdit;end; procedure TfrmEdit.actTableAddExecute(Sender: TObject); var rows: TABLEROWPARMS; cells: TABLECELLPARMS; rc : LRESULT; begin //Insert a table into the RTF. ZeroMemory(@rows,sizeof(rows)); rows.cbRow := sizeof(TABLEROWPARMS); rows.cbCell := sizeof(TABLECELLPARMS); rows.cCell := 3; rows.cRow := 2; rows.dxCellMargin := 5; //50 rows.nAlignment := 1; rows.dyHeight := 100; //400 rows.fIdentCells := 1; rows.fRTL := 0; rows.fKeep := 1; rows.fKeepFollow := 1; rows.fWrap := 1; //rows.cpStartRow := -1; ZeroMemory(@cells,sizeof(cells)); cells.dxWidth := 600; //1000 cells.dxBrdrLeft := 1; cells.dyBrdrTop := 1; cells.dxBrdrRight := 1; cells.dyBrdrBottom := 1; cells.crBackPat := RGB(255,255,255); cells.crForePat := RGB(0,0,0); cells.nVertAlign := 0; //cells.fMergeTop := 1; //cells.fMergePrev := 1; cells.fVertical := 1; rc := SendMessage(memNotes.Handle,EM_INSERTTABLE, WPARAM(@rows),LPARAM(@cells)); //rc := memNotes.Perform(EM_INSERTTABLE, WPARAM(@rows),LPARAM(@cells)); end; 

After execution, rc contains -2147024809 ( E_INVALIDARG ). I do not know why this fails, or what the problem is with the message arguments. As a disclaimer, I am new to working with RichEdit in Delphi, but I tried to learn as much as possible before publishing for reference.

In my extensive search, I found this website 5 , which can help fix the problem. This site hosts the RTFLabel utility. Download the zip file and look at richedit2.pas, where they explain that “the definition of CHARFORMAT2A and CHARFORMAT2W in the richedit.h file (2005 SDK) has an error in the C-part” and that they need to insert a new dummy “fix” structure byte alignment field to get her to work with Delphi. I have the feeling that there may be problems aligning with one or both of the TABLEROWPARMS and TABLECELLPARMS structures that cause this error.

I would like to help figure out why SendMessage returns E_INVALIDARG, and what I can do to fix it. Any help would be greatly appreciated!
-Jeff Aylor

Related websites:
1 [ http://fgaillard.com/2010/09/using-richedit-4-1-with-d2010/] 1
2 [ http://blogs.msdn.com/b/murrays/archive/2008/09/15/richedit-s-nested-table-facility.aspx] 2
3 [ http://blogs.msdn.com/b/murrays/archive/2006/10/14/richedit-versions.aspx] 3
4 http://msdn.microsoft.com/en-us/library/windows/desktop/hh768373%28v=vs.85%29.aspx] 4
5 [ http://flocke.vssd.de/prog/code/pascal/rtflabel/] 5
6 Delphi 7 TRichTextEdit Text in a field that does not display correctly 6

+7
delphi richedit
source share
2 answers

tl; dr :

Do not use it for anything else, except that it is advertised / documented (i.e. lower than W8), even then I will keep my eyes open. This is a mess.


Please note that there are other differences between the blog post that you take with you and the documentation, except for the additional three TABLEROWPARMS fields. It is located in TABLECELLPARMS . Here's a side-by-side comparison, on the left is a blog post entry, on the right is a documentation entry.

 typedef struct _tableCellParms { typedef struct _tableCellParms { LONG dxWidth; LONG dxWidth; WORD nVertAlign:2; WORD nVertAlign:2; WORD fMergeTop:1; WORD fMergeTop:1; WORD fMergePrev:1; WORD fMergePrev:1; WORD fVertical:1; WORD fVertical:1; WORD fMergeStart:1; WORD fMergeCont:1; WORD wShading; WORD wShading; SHORT dxBrdrLeft; SHORT dxBrdrLeft; SHORT dyBrdrTop; SHORT dyBrdrTop; SHORT dxBrdrRight; SHORT dxBrdrRight; SHORT dyBrdrBottom; SHORT dyBrdrBottom; COLORREF crBrdrLeft; COLORREF crBrdrLeft; COLORREF crBrdrTop; COLORREF crBrdrTop; COLORREF crBrdrRight; COLORREF crBrdrRight; COLORREF crBrdrBottom; COLORREF crBrdrBottom; COLORREF crBackPat; COLORREF crBackPat; COLORREF crForePat; COLORREF crForePat; } TABLECELLPARMS; } TABLECELLPARMS; 


Here you see two additional fields inserted intuitively in the middle of the record. Or one of them is mistaken, perhaps both of them ... But from the blog post we know that there are people who were able to get him to work with the left. Maybe there is a specific version against a specific DLL.

In any case, given the very high probability that the cause of E_INVALIDARG is the size of the structures, because even with minimal testing it does not work, it led me to check using brute force to determine the correct size of the records.

 var rows: TABLEROWPARMS; cells: TABLECELLPARMS; begin ZeroMemory(@rows,sizeof(rows)); rows.cbRow := 1; rows.cbCell := 1; rows.cCell := 1; rows.cRow := 1; ZeroMemory(@cells,sizeof(cells)); while SendMessage(RichEdit1.Handle, EM_INSERTTABLE, WPARAM(@rows), LPARAM(@cells)) <> S_OK do begin if rows.cbCell < 120 then // arbitrary upper limit Inc(rows.cbCell) else begin Inc(rows.cbRow); rows.cbCell := 1; end; if rows.cbRow = 120 then raise Exception.Create('no match'); end; end; 

By placing a breakpoint at the end of the procedure, rows.cbRow appears at the top 28, and rows.cbRow appears at 40. They are much smaller than both links. I also tested for a possible match more than the first hit, they are not. My test is against version msftedit.dll 5.41.21.2510 located in the '\ syswow64' of the W7 window using XE2.

So where should we cut out the structures from the end? As you can see from the links above, maybe not. I don’t see any sound way of moving from here, but I submit my best attempt if you have a similar environment and want to continue (which I do not recommend - mind you, I had to change the order of the fields so go this far). This is definitely wrong since it cannot insert a table with columns of more than one, for example.

  _tableRowParms = packed record cbRow : BYTE ; cbCell : BYTE ; cCell : BYTE ; cRow : BYTE ; dxCellMarginOrVertAlign : LONG ; // when there more than one cell dxIndent : LONG ; dyHeight : LONG ; nAlignment : DWORD; fRTL : DWORD; fKeep : DWORD; end; _tableCellParms = packed record dxWidth : LONG ; nVertAlign : WORD ; fVertical : WORD ; dxBrdrLeft : SHORT ; dyBrdrTop : SHORT ; dxBrdrRight : SHORT ; dyBrdrBottom : SHORT ; crBrdrLeft : COLORREF; crBrdrTop : COLORREF; crBrdrRight : COLORREF; crBrdrBottom : COLORREF; crBackPat : COLORREF; crForePat : COLORREF; end; .. var rows: TABLEROWPARMS; cells: TABLECELLPARMS; rc : LRESULT; begin ZeroMemory(@rows,sizeof(rows)); rows.cbRow := sizeof(TABLEROWPARMS); rows.cbCell := sizeof(TABLECELLPARMS); rows.cCell := 1; // ?? rows.cRow := 3; rows.dxCellMarginOrVertAlign := 120; rows.dxIndent := 200; rows.dyHeight := 400; rows.nAlignment := 1; // don't leave at 0 rows.fRTL := 0; // ??? rows.fKeep := 0; // ??? ZeroMemory(@cells,sizeof(cells)); cells.dxWidth := 1000; cells.nVertAlign := 1; cells.fVertical := 1; cells.dxBrdrLeft := 50; cells.dyBrdrTop := 10; cells.dxBrdrRight := 50; cells.dyBrdrBottom := 20; cells.crBrdrLeft := RGB(255,0,0); cells.crBrdrTop := RGB(0, 255, 0); cells.crBrdrRight := RGB(0, 0, 255); cells.crBrdrBottom := RGB(255, 255, 0); cells.crBackPat := RGB(255, 255, 255); cells.crForePat := RGB(128, 64, 64); // ? rc := SendMessage(RichEdit1.Handle,EM_INSERTTABLE, WPARAM(@rows),LPARAM(@cells)); end; 
+1
source share

here are the correct structures ...

  _tableRowParms = record cbRow : BYTE; // Count of bytes in this structure cbCell : BYTE; // Count of bytes in TABLECELLPARMS cCell : BYTE; // Count of cells cRow : BYTE; // Count of rows dxCellMargin : LONGINT; // Cell left/right margin (\trgaph) dxIndent : LONGINT; // Row left (right if fRTL indent (similar to \trleft) dyHeight : LONGINT; // Row height (\trrh) nParams : DWORD; // 0 - 2 bits - Row alignment (like PARAFORMAT::bAlignment, 1/2/3) (\trql, trqr, \trqc) // 3 bit - Display cells in RTL order (\rtlrow) // 4 bit - Keep row together (\trkeep} // 5 bit - Keep row on same page as following row (\trkeepfollow) // 6 bit - Wrap text to right/left (depending on bAlignment) (see \tdfrmtxtLeftN, \tdfrmtxtRightN) // 7 bit - lparam points at single struct valid for all cells cpStartRow : LONGINT; // The character position that indicates where to insert table. A value of –1 indicates the character position of the selection. bTableLevel : BYTE; // The table nesting level (EM_GETTABLEPARMS only). iCell : BYTE; // The index of the cell to insert or delete (EM_SETTABLEPARMS only). end; TABLEROWPARMS = _tableRowParms; TTableRowParms = TABLEROWPARMS; PTableRowParms = ^TTableRowParms; _tableCellParms = record dxWidth : LONGINT; // Cell width (\cellx) nParams : Word; // 0 - 1 bits - Vertical alignment (0/1/2 = top/center/bottom) (\clvertalt (def), \clvertalc, \clvertalb) // 2 bit - Top cell for vertical merge (\clvmgf) // 3 bit - Merge with cell above (\clvmrg) // 4 bit - Display text top to bottom, right to left (\cltxtbrlv) // 5 bit - Start set of horizontally merged cells (\clmgf). // 6 bit - Merge with the previous cell (\clmrg). wShading : WORD; // Shading in .01% (\clshdng) eg, 10000 flips fore/back dxBrdrLeft : SHORT; // Left border width (\clbrdrl\brdrwN) (in twips) dyBrdrTop : SHORT; // Top border width (\clbrdrt\brdrwN) dxBrdrRight : SHORT; // Right border width (\clbrdrr\brdrwN) dyBrdrBottom : SHORT; // Bottom border width (\clbrdrb\brdrwN) crBrdrLeft : COLORREF; // Left border color (\clbrdrl\brdrcf) crBrdrTop : COLORREF; // Top border color (\clbrdrt\brdrcf) crBrdrRight : COLORREF; // Right border color (\clbrdrr\brdrcf) crBrdrBottom : COLORREF; // Bottom border color (\clbrdrb\brdrcf) crBackPat : COLORREF; // Background color (\clcbpat) crForePat : COLORREF; // Foreground color (\clcfpat) end; TABLECELLPARMS = _tableCellParms; TTableCellParms = TABLECELLPARMS; PTableCellParms = ^TTableCellParms; 
0
source share

All Articles