Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RichEdit 2.0's usage of single CR character as linebreak throws off SelStart calculations (Delphi XE2)

When transitioning from Delphi 2006 to Delphi XE2, one of the things that we learned is that RichEdit 2.0 replaces internally CRLF pairs with a single CR character. This has the unfortunate effect of throwing off all character index calculations based on the actual text string on the VCL's side.

The behavior I can see by tracing through the VCL code is as follows:

  1. Sending a WM_GETTEXT message (done in TControl.GetTextBuf) will return a text buffer that contains CRLF pairs.
  2. Sending a WM_GETTEXTLENGTH message (done in TControl.GetTextLen) will return a value as if the text still contains CRLF characters.
  3. In contrast, sending an EM_SETSELEX message (i.e. setting SelStart) will treat the input value as if the text contains only CR characters.

This causes all sorts of things to fail (such as syntax highlighting) in our application. As you can tell, everything is off by exactly one character for every new line up to that point.

Obviously, since this is inconsistent behavior, we must be missing something or doing something very wrong.

Does anybody else has any experience with the transition from a RichEdit 1.0 to a RichEdit 2.0 control and how did you solve this issue? Finally, is there any way to force RichEdit 2.0 to use CRLF pairs just like RichEdit 1.0?

like image 367
user1127813 Avatar asked Jan 04 '12 10:01

user1127813


1 Answers

We also ran into this very issue.

We do a "mail merge" type of thing where we have templates with merge codes that are parsed and replaced by data from outside sources.

This index mismatch between pos(mystring, RichEdit.Text) and the positioning index into the RichEdit text using RichText.SelStart broke our merge.

I don't have a good answer but I came up with a workaround. It's a bit cumbersome (understatment!) but until a better solution comes along...

The workaround is to use a hidden TMemo and copy the RichEdit text to it and change the CR/LF pairs to CR only. Then use the TMemo to find the proper positioning using pos(string, TMemo) and use that to get the selstart position to use in the TRichEdit.

This really sucks but hopefully this workaround will help others in our situation or maybe spark somebody smarter than me into coming up with a better solution.

I'll show a little sample code...

Since we are replacing text using seltext we need to replace text in BOTH the RichEdit control and the TMemo control to keep the two synchronized.

StartToken and EndToken are the merge code delimiters and are a constant.

function TEditForm.ParseTest: boolean;
var TagLength: integer;
var ValueLength: integer;
var ParseStart: integer;
var ParseEnd: integer;
var ParseValue: string;
var Memo: TMemo;
begin
  Result := True;//Default
  Memo := TMemo.Create(nil);
  try
    Memo.Parent := self;
    Memo.Visible := False;
    try
      Memo.Lines.Clear;
      Memo.Lines.AddStrings(RichEditor.Lines);
      Memo.Text := stringreplace(Memo.Text,#13#10,#13,[rfReplaceAll]);//strip CR/LF pairs and replace with CR

      while (Pos(StartToken, Memo.Text) > 0) and (Pos(EndToken, Memo.Text) > 0) do begin
        ParseStart := Pos(StartToken, Memo.SelText);
        ParseEnd := Pos(EndToken, Memo.SelText) + Length(EndToken);
        if ParseStart >= ParseEnd then begin//oops, something's wrong - bail out
          Result := true;
          myEditor.SelStart := 0;
          exit;
        end;
        TagLength := ParseEnd - ParseStart;
        ValueLength := (TagLength - Length(StartToken)) - Length(EndToken);
        ParseValue := Copy(Memo.SelText, (ParseStart + Length(StartToken)), ValueLength);
        Memo.selstart := ParseStart - 1; //since the .text is zero based, but pos is 1 based we subtract 1
        Memo.sellength := TagLength;
        RichEditor.selstart := ParseStart - 1; //since the .text is zero based, but pos is 1 based we subtract 1
        RichEditor.sellength := TagLength;

        TempText := GetValue(ParseValue);
        Memo.SelText := TempText;
        RichEditor.SelText := TempText;
      end;

    except
       on e: exception do
          begin
          MessageDlg(e.message,mtInformation,[mbOK],0);
          result := false;
          end;
       end;//try..except
  finally
    FreeAndNil(Memo);
  end;
end;
like image 84
TJ Asher Avatar answered Nov 11 '22 07:11

TJ Asher