MRoth
MRoth

Reputation: 63

Search and Replace in a Trichedit

We are working with XE7. We are trying to build a search and replace feature into a TRichEdit.

We would like it to work just like it works in the Delphi compiler. We display our own search and replace dialog, we are not using the Delphi replace dialog.

We display the dialog, and when the user clicks the Replace button, the dialog closes, and for each instance found we want to popup a message dialog asking the user whether they want to replace that occurrence.

When we do this, the user cannot see what is selected, and if they click Yes on the message dialog, nothing is being replaced.

Our code is below:

procedure Tviewform.select_text(fndpos:integer);
begin
   setfocus;
   asciieditor.selstart:=fndpos;
   asciieditor.sellength:=length(vwvars[curviewwin]^.finddata);
end;

procedure Tviewform.search_and_replace;
var position,endpos,fndans,cont:integer; foptions:tsearchtypes; msgques,stopall:word;
begin
  with asciieditor do
  begin
    endpos:=length(asciieditor.text)-vwvars[curviewwin]^.sstartpos;
    foptions:=[];
    stopall:=0;
    cont:=0;
    if findinfo.onbut.checked then foptions:=foptions+[stMatchCase];
    lines.beginupdate;
    fndans:=findtext(vwvars[curviewwin]^.finddata,vwvars[curviewwin]^.sstartpos,endpos,foptions);
    while (fndans<>-1) and (stopall=0) do
    begin
      select_text(fndans);
      msgques:=mainform.silang1.messagedlg('Replace with '+vwvars[curviewwin]^.replace+'?.',mtinformation,[mbyes,mbno,mbcancel],0);
      if msgques=mryes then
      begin
        asciieditor.clearselection;
        seltext:=vwvars[curviewwin]^.replace;
      end else
      begin
        if msgques=mrcancel then
        begin
          cont:=0;
          stopall:=1;
        end else cont:=1;
      end;
      asciieditor.Repaint;
      if cont=1 then
      begin
        inc(vwvars[curviewwin]^.sstartpos,length(vwvars[curviewwin]^.finddata));
        endpos:=length(asciieditor.text)-vwvars[curviewwin]^.sstartpos;
        fndans:=findtext(vwvars[curviewwin]^.finddata,vwvars[curviewwin]^.sstartpos,endpos,foptions);
      end;
    end;
    lines.endupdate;
  end;
end;

Upvotes: 0

Views: 334

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 598279

I see a number of issues with your code:

  • You are moving input focus away from the TRichEdit when you call select_text() and MessageDlg(), so make sure that TRichEdit.HideSelection is set to False (it is True by default) in order to actually see the selection.

  • You are calling BeginUpdate() on the TRichEdit, which disables screen redraws for it, including Repaint().

  • You don't need to call TRichEdit.ClearSelection() before setting the TRichEdit.SelText.

  • When updating vwvars[curviewwin]^.sstartpos after each replacement, you need to reset it to fndAns (the current selection's position) plus the length of vwvars[curviewwin]^.replace (the new text), not the length of vwvars[curviewwin]^.finddata (the old text). You are not moving it beyond the new text, so you are searching older text over and over.

  • If the user chooses No in the MessageDlg(), you are not updating vwvars[curviewwin]^.sstartpos to move past the text that the user didn't want to replace.

  • When setting endpos, length(asciieditor.text) can be asciieditor.gettextlen() instead for better efficiency. But more importantly, you should not be subtracting vwvars[curviewwin]^.sstartpos from endpos at all. By doing that, you are skipping more and more text at the end of the TRichEdit from the search with each replacement made. In fact, you should be using just the asciieditor's current text length as the end position of each search, so you don't actually need endpos at all.

  • Depending on the size of your TRichEdit and its content, if it has scrollbars enabled, you should send the TRichEdit a EM_SCROLLCARET message each time you find a matching text and before you prompt the user with the MessageDlg(), in case the matching text is off-screen.

With that said, try something more like this:

procedure Tviewform.search_and_replace;
var
  fndAns: Integer;
  fOptions: TSearchTypes;
  msgQues: Word;

  procedure select_text;
  begin
    {AsciiEditor.}SetFocus;
    AsciiEditor.SelStart := fndAns;
    AsciiEditor.SelLength := Length(vwvars[curviewwin]^.finddata);
    AsciiEditor.Perform(EM_SCROLLCARET, 0, 0);
    AsciiEditor.Update;
  end;

begin
  fOptions := [];
  if FindInfo.OnBut.Checked then Include(fOptions, stMatchCase);
  repeat
    fndAns := AsciiEditor.FindText(vwvars[curviewwin]^.finddata, vwvars[curviewwin]^.sstartpos, AsciiEditor.GetTextLen, fOptions);
    if fndAns = -1 then Break;
    select_text;
    msgQues := MainForm.SiLang1.MessageDlg('Replace with ' + vwvars[curviewwin]^.replace + '?', mtinformation, [mbYes,mbNo,mbCancel], 0);
    if msgQues = mrYes then
    begin
      AsciiEditor.SelText := vwvars[curviewwin]^.replace;
      AsciiEditor.Update;
      Inc(fndAns, Length(vwvars[curviewwin]^.replace));
    end
    else if msgQues = mrNo then
    begin
      Inc(fndAns, Length(vwvars[curviewwin]^.finddata));
    end else
      Break;
    vwvars[curviewwin]^.sstartpos := fndAns;
  until False;
end;

Upvotes: 1

Related Questions