RichEdit 2.0在使用单个回车符作为换行符时会影响SelStart计算(Delphi XE2)

15
从 Delphi 2006 迁移到 Delphi XE2 后,我们学到的一件事情是 RichEdit 2.0 内部用单个 CR 字符代替了 CRLF 对。这导致基于 VCL 实际文本字符串的所有字符索引计算都会出现偏差。
通过跟踪 VCL 代码,我能够看到以下行为:
1. 发送 WM_GETTEXT 消息(在 TControl.GetTextBuf 中执行)将返回一个包含 CRLF 对的文本缓冲区。 2. 发送 WM_GETTEXTLENGTH 消息(在 TControl.GetTextLen 中执行)将返回一个值,就好像文本仍然包含 CRLF 字符。 3. 相反,发送 EM_SETSELEX 消息(即设置 SelStart)会将输入值视为文本仅包含 CR 字符。
这导致我们应用程序中的很多问题失败(例如语法高亮)。正如您所看到的,对于每个新行,所有内容都会偏差一个字符。
显然,由于这种不一致的行为,我们肯定是遗漏了某些东西或者做错了什么。
有没有其他人经历过从 RichEdit 1.0 到 RichEdit 2.0 控件的迁移,并且他们是如何解决这个问题的?最后,有没有办法强制 RichEdit 2.0 使用像 RichEdit 1.0 一样的 CRLF 对呢?

1
很抱歉,您必须遵守这个规定,因为EM_SETSEL是常见的编辑控件消息,而不是特定于富文本编辑的消息。 - TLama
2
据我所知,TRichEdit只是Windows Rich Edit控件的包装器。因此,你可能无法做太多事情,只能计算正确的位置。或者你可以使用其他控件,比如WPRichText。 - Andreas
1
TRichEdit并不适合作为语法高亮编辑器。建议使用真正的语法高亮编辑器组件,比如SynEdit(http://synedit.sourceforge.net)。 - Remy Lebeau
2个回答

4
我们也遇到了这个问题。
我们执行的是类似“邮件合并”的操作,其中包含有模板和合并代码,这些代码会被解析并替换为来自外部来源的数据。
RichEdit.Text中的pos(mystring, RichEdit.Text)与使用RichText.SelStart在RichEdit文本中的定位索引之间的索引不匹配破坏了我们的合并。
我没有很好的答案,但我想出了一个解决方法。虽然有点麻烦(轻描淡写!),但在更好的解决方案出现之前...
解决方法是使用隐藏的TMemo,并将RichEdit文本复制到其中,并将CR/LF对更改为仅CR。然后使用TMemo查找正确的位置,使用pos(string, TMemo)获取selstart位置以在TRichEdit中使用。
这真的很糟糕,但希望这个解决方法能帮助处于我们情况下的其他人,或者可能激发比我更聪明的人想出更好的解决方案。
我将展示一些示例代码...
由于我们正在使用seltext替换文本,因此我们需要在RichEdit控件和TMemo控件中同时替换文本,以保持两者同步。
StartToken和EndToken是合并代码分隔符,是一个常量。
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;

1
仅供参考:FreeAndNil(Memo) 是完全不必要的;因为 Memo 是局部变量,因此在过程的最终end;中,无论它是否设置为 nil 都是没有意义的。只需调用 Memo.Free 就足以了,而且在我看来更易读。(FreeAndNil 让我觉得 Memo 比当前过程有更广泛的范围。) - Ken White
1
首先,非常感谢您的回复。我认为您并不需要一个隐藏的TMemo。只要我们在讨论解决方法,您只需要计算到所需位置的CRLF对数,然后每找到一对就从后者中减去一。得到的数字将是您的SelStart。然而,即使如此,这仍然不是真正可接受的,因为它会太慢且繁琐。我仍然无法相信我们没有漏掉什么很明显的东西。 - user1127813
@Ken - 同意。这是我第一次试图把一些东西拼凑在一起。它需要一些改进。 - TJ Asher
1
@user1127813 - 我一直在研究vcl.comctrls.pas单元中的TRichEditStrings代码,并看到代码似乎试图通过计算CRLFs来解决CRLF / CR不匹配的问题,但并没有起作用。请查看TRichEditStrings.Insert。 - TJ Asher

1

你可以从插入符位置减去EM_LINEFROMCHAR(或者EM_GETSEL的位置),这样怎么样?

如果你只想知道选择中有多少个cl/cr对,甚至可以获取两个EM_LINEFROMCHAR变量。一个来自选择开始,另一个来自所需的插入符/选择位置。


亲爱的Joshua,欢迎来到Stack Overflow。-- 你有没有注意到你回答的问题已经几年了 :-) ? 所以如果你的回答后面没有任何反应,请不要失望。-- 尝试使用适当的语法格式,就像OP所做的那样:http://stackoverflow.com/help/formatting。-- 像你这样的建议最好放在评论中。学习如何使用它们。祝你好运! - peter_the_oak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接