3

OK, this is a problem that's been nagging and I can't see to find a definitive answer. How do you find and mark all instances of a word?

What I mean is, I search for a word (say: Person). If the word exists the I mark (using red or whatever color) all instances of that word in the richedit. If I press Esc then it gets deselected.

Any ideas?

code is appreciated.

wonderer
  • 3,487
  • 11
  • 49
  • 59

2 Answers2

3

wonderer, I wrote this code, I hope it will be useful :

Procedure MarkString(RichEdit:TRichEdit;strtomark : string);
Var
FoundAt : integer;
begin
    FoundAt:=RichEdit.FindText(strtomark,0,maxInt,[stWholeWord]);
    while FoundAt <> -1 do
    begin
             RichEdit.SelStart := FoundAt;
             RichEdit.SelLength := Length(strtomark);
             RichEdit.SelAttributes.Style := [fsBold];
             RichEdit.SelAttributes.Color := clRed;
             RichEdit.SelText :=strtomark;
             FoundAt:=RichEdit.FindText(strtomark,FoundAt + length(strtomark),maxInt,[stWholeWord]);
    end;
end;


Procedure UnMarkString(RichEdit:TRichEdit;strtomark : string);
Var
FoundAt : integer;
begin
    FoundAt:=RichEdit.FindText(strtomark,0,maxInt,[stWholeWord]);
    while FoundAt <> -1 do
    begin
             RichEdit.SelStart := FoundAt;
             RichEdit.SelLength := Length(strtomark);
             RichEdit.SelAttributes.Style := [];
             RichEdit.SelAttributes.Color := clBlack;
             RichEdit.SelText :=strtomark;
             FoundAt:=RichEdit.FindText(strtomark,FoundAt + length(strtomark),maxInt,[stWholeWord]);
    end;
end;


MarkString(RichEdit1,'delphi'); //To Mark a string

UnMarkString(RichEdit1,'delphi'); //To UnMark a string

Bye.

RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • Great! thanks. Now I need to modify it a bit to also set a background color on that string and i'm all set. – wonderer Sep 04 '09 at 14:09
3

The following code will search all occurrences of the given word (case sensitive) in the rich edit control, change the font colour to red, and finally restore the original selection of the control (all with as little flicker as possible I hope):

procedure TForm1.FindWord(const AWord: string; AOptions: TSearchTypes);
var
  OrigSelStart, OrigSelLen: integer;
  Start, Found: integer;
begin
  if AWord = '' then
    exit;

  OrigSelStart := RichEdit1.SelStart;
  OrigSelLen := RichEdit1.SelLength;

  RichEdit1.Perform(WM_SETREDRAW, 0, 0);
  try
    Start := 0;
    Found := RichEdit1.FindText(AWord, Start, MaxInt, AOptions);
    while Found <> -1 do begin
      RichEdit1.SelStart := Found;
      RichEdit1.SelLength := Length(AWord);
      // TODO: save start of search match and original font colour
      RichEdit1.SelAttributes.Color := clRed;
      Start := Found + Length(AWord);
      Found := RichEdit1.FindText(AWord, Start, MaxInt, AOptions);
    end;
  finally
    RichEdit1.SelStart := OrigSelStart;
    RichEdit1.SelLength := OrigSelLen;
    RichEdit1.Perform(WM_SETREDRAW, 1, 0);
    RichEdit1.Repaint;
  end;
end;

Now you only need to save the matches together with the original text attributes to a list, and use the information in this list to revert all the changes on the press of Esc. This can however get quite tricky to do correctly, if you assume that the matches may contain different font styles, colours and such. I have therefore not provided any code to save the formatting, it depends on your requirements.

Oh, make sure that the highlighted matches are removed before the text can be changed again, otherwise you will not correctly restore the original text formatting.

mghie
  • 32,028
  • 6
  • 87
  • 129
  • To help avoid flicker, and to speed up processing, you should also use the EM_SETEVENTMASK message to disable/reenable the RichEdit's internal notifications to itself, which are not necessary during this kind of text processing. – Remy Lebeau Sep 03 '09 at 21:49
  • @Remy: This [http://msdn.microsoft.com/en-us/library/bb774366(VS.85).aspx] suggests that the RichEdit default mask is ENM_NONE, so my impression is this is likely to be unnecessary. Makes sense to try, though, and it appears that EM_GETEVENTMASK will provide the current mask, which can be saved for restoration after using EM_SETEVENTMASK (EM_NONE) and making the updates. Just laying this out in case I'm missing something. – Argalatyr Sep 04 '09 at 01:33
  • Just a followup on this - it looks like the VCL modifies the event mask, meaning that the default EM_NONE does not apply when you use a TRichEdit component as-is: it flickers. To reduce flicker - the best work-around seems to be to send EM_SETEVENTMASK to none as suggested by Remy. Doing this gives a fairly good result. Doing all the updates inside a BeginUpdate / EndUpdate seems to be worse. Doing the updates inside .Perform(WM_SETREDRAW, 0, 0) .. .Perform(WM_SETREDRAW, 1, 0) // .Repaint seems to be just as bad. There seems to be no really good solution, this control is not double-buffered. – quickly_now Apr 16 '11 at 06:21
  • And another followup. The best results seem to be to use SendMessage to set an event mask of 0, then LockWindowUpdate(handle to the richedit control).... do updates.... LockWindowUpdate(NULL or nil). There is still flicker, its just the least annoying. – quickly_now Apr 16 '11 at 06:41