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