Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading long RTF-Text in TRichEdit does not work

If a long RTF-sequenz (eg 150 000 chars) is streamed into a TRichEdit control (in XE4), the control does not display the text but instead shows the raw RTF code:

{\rtf1\ansi\ansicpg1252\deff0...

What is wrong?

procedure TForm1.Button1Click(Sender: TObject);
var
    RtfText: string;
    Stream: TStringStream;
begin
    RtfText := GenerateRtfText();

    Stream := TStringStream.Create(RtfText);
    try
        RichEdit2.PlainText := False;
        RichEdit2.Lines.LoadFromStream(Stream); //<--- ERROR: RichEdit displays raw RTF-Code
                                                //     if RtfText is too long
        if StartsText('{\rtf', RichEdit2.Lines.Text) then
        begin
            ShowMessage('Oh no, not converted!');
            //WORKAROUND: 2nd try seems to work...
            //Stream.Position := 0;
            //RichEdit2.Lines.LoadFromStream(Stream);
        end;
    finally
        Stream.Free;
    end;
end;

For instance with following RTF generation function:

function TForm1.GenerateRtfText: string;
var
    I: Integer;
    Stream: TStringStream;
const
    DOES_WORK_COUNT = 10000;
    DOES_NOT_WORK_COUNT = 15000;
begin
    //Fill
    RichEdit1.Lines.BeginUpdate;
    try
        //for I := 0 to DOES_WORK_COUNT do
        for I := 0 to DOES_NOT_WORK_COUNT do
          RichEdit1.Lines.Add(IntToStr(I));
    finally
        RichEdit1.Lines.EndUpdate;
    end;
    //Convert to RTF
    Stream := TStringStream.Create;
    try
        RichEdit1.Lines.SaveToStream(Stream);
        Result := Stream.DataString;
    finally
        Stream.Free;
    end;
end;

Edited: Even copy and paste does not work correctly:

This is what I did:

  • I copied the generated content of RichEdit1 (lines 1..15000 with numbers 1..15000) into notpad.exe to remove any RTF
  • I copied the content of notepad into RichEdit2

Result:

  • only 12773 lines are displayed correctly. The last line is only 12
  • if I try to add another char into the TRichEdit nothing happens
  • if I remove 10 chars (per backspace) I can add exactly 10 chars afterwards...

Is there a hidden character limit for TRichEdit?

like image 572
yonojoy Avatar asked Apr 28 '17 11:04

yonojoy


2 Answers

Rich edit control has a text limit.

Try using EM_EXLIMITTEXT message, which sets an upper limit to the amount of text the user can type or paste into a rich edit control. This message also limit the amount of text that you can stream into a rich edit control when streaming RTF (PlainText = False). but does not limit the control when streaming plain text.

e.g.:

const
  RE_MAX_TEXT_SIZE = 256000;

SendMessage(RichEdit1.Handle, EM_EXLIMITTEXT, 0, RE_MAX_TEXT_SIZE);

or:

SendMessage(RichEdit1.Handle, EM_EXLIMITTEXT, 0, $7FFFFFF0);

for the maximum limit as implemented in TRichEditStrings.LoadFromFile(): RichEdit.DoSetMaxLength($7FFFFFF0); However, DoSetMaxLength() is not correctly used in the sources, as it should be called before the stream is loaded. Also, DoSetMaxLength() is not used at all for TRichEditStrings.LoadFromStream(). Remy mentioned this in the comments of his answers.

like image 108
kobik Avatar answered Oct 12 '22 12:10

kobik


In addition to what kobik said:

TRichEdit.Lines.LoadFromStream() uses EM_STREAMIN internally. When TRichEdit.PlainText is false, LoadFromStream() will first try to load the stream data as RTF, and if any error is encountered than it will re-load the stream data as plain text instead. That is why you see the raw RTF code appear.

RTF is an ASCII-based format, and so LoadFromStream() expects 8-bit RTF data (and in the case of PlainText=True, will try to convert it to Unicode). Try using AnsiString and TMemoryStream instead of (Unicode)String and TStringStream for your RTF stream.

type
  TReadOnlyMemoryBufferStream = class(TCustomMemoryStream)
  public
    constructor Create(APtr: Pointer; ASize: NativeInt);
    function Write(const Buffer; Count: Longint): Longint; override;
  end;

constructor TReadOnlyMemoryBufferStream.Create(APtr: Pointer; ASize: NativeInt);
begin
  inherited Create;
  SetPointer(APtr, ASize);
end;

function TReadOnlyMemoryBufferStream.Write(const Buffer; Count: Longint): Longint;
begin
  Result := 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  RtfText: AnsiString;
  Stream: TReadOnlyMemoryBufferStream;
begin
  RtfText := GenerateRtfText();

  Stream := TReadOnlyMemoryBufferStream.Create(PAnsiChar(RtfText), Length(RtfText));
  try
    RichEdit2.PlainText := False;
    RichEdit2.Lines.LoadFromStream(Stream);
    ...
  finally
    Stream.Free;
  end;
end;

function TForm1.GenerateRtfText: AnsiString;
var
  I: Integer;
  Stream: TMemoryStream;
const
  DOES_WORK_COUNT = 10000;
  DOES_NOT_WORK_COUNT = 15000;
begin
  //Fill
  RichEdit1.Lines.BeginUpdate;
  try
    //for I := 0 to DOES_WORK_COUNT do
    for I := 0 to DOES_NOT_WORK_COUNT do
      RichEdit1.Lines.Add(IntToStr(I));
  finally
    RichEdit1.Lines.EndUpdate;
  end;

  //Convert to RTF
  Stream := TMemoryStream.Create;
  try
    RichEdit1.PlainText := False;
    RichEdit1.Lines.SaveToStream(Stream);
    SetString(Result, PAnsiChar(Stream.Memory), Stream.Size);
  finally
    Stream.Free;
  end;
end;
like image 44
Remy Lebeau Avatar answered Oct 12 '22 12:10

Remy Lebeau