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

2

15

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?

Totally answered 4/1, 2012 at 10:40 Comment(3)
I'm afraid you will have to conform to this because EM_SETSEL is the common edit control message, not the rich edit specific one.Collator
As far as i know, TRichEdit is only a wrapper for the windows rich edit control. So there is maybe not much you can do but to calculate around this to get the correct positions. Or you use an other control such as WPRichText.Needleful
TRichEdit is not well suited as a syntax highlighting editor. Consider using an actual syntax highlighting editor component, such as SynEdit (synedit.sourceforge.net).Denby
M
4

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;
Mutual answered 6/1, 2012 at 21:47 Comment(4)
Just as an FYI note: FreeAndNil(Memo) is totally unnecessary; as Memo is a local variable, and therefore goes out of scope at the final end; in the procedure, whether or not it is set to nil is meaningless. Just calling Memo.Free is 100% sufficient, and IMO is more readable. (FreeAndNil to me makes it appear that Memo has a wider scope than the current procedure.)Domiciliate
First of all, thank you very much for your reply. I don't think you really need a hidden TMemo. As long as we're talking workarounds, all you have to do is count the number of CRLF pairs up to the desired position and then subract one from the latter for each pair found. The resulting number will be your SelStart. However, even that is not really acceptable, since it would have been too slow and cumbersome. I still can't believe that we're not missing something obvious here.Totally
@Ken - agreed. This was my first attempt to kludge something together. It can use some work.Mutual
@Totally - I have been looking into the vcl.comctrls.pas unit at the TRichEditStrings code and am seeing areas where the code looks like it is trying to account for the CRLF/CR mismatch by counting CRLFs but does not work. Take a look at TRichEditStrings.Insert.Mutual
P
1

How about subtracting EM_LINEFROMCHAR from the caret position? (OR the position of EM_GETSEL) whichever you need.

You could even get two EM_LINEFROMCHAR variables. One from the selection start and the other from the desired caret/selection position, if you only want to know how many cl/cr pairs are in the selection.

Pastelist answered 4/3, 2016 at 18:8 Comment(1)
Dear Joshua, welcome to Stack Overflow. -- Have you noticed that the question you answered is for years old :-) ? So don't be disappointed if nothing happens after your answer. -- Try to use appropriate syntax format like the OP did: stackoverflow.com/help/formatting. -- Suggestions like yours are better placed as comments. Learn how to use them. Good luck!Assert

© 2022 - 2024 — McMap. All rights reserved.