SHKODRAN
SHKODRAN

Reputation: 15

How to highlight only the lines contain the 'MyString' in a RichEdit?

I have created a small program the read a text file.

Once the text file is opened in a RichEdit, I want to change the background color of lines that contain a certain string, or to hide all lines that do not contain the string. Is it possible?

I have tried to search for the string, but I haven't any idea of how to do what I'm asking for.

function SearchText(Control: TCustomEdit; Search: string; SearchOptions: TSearchOptions): Boolean;
var
  Text: string;
  Index: Integer;
begin
  if soIgnoreCase in SearchOptions then
  begin
    Search := UpperCase(Search);
    Text := UpperCase(Control.Text);
  end
  else
    Text := Control.Text;

  Index := 0;
  if not (soFromStart in SearchOptions) then
    Index := PosEx(Search, Text, Control.SelStart + Control.SelLength + 1);
  if (Index = 0) and
      ((soFromStart in SearchOptions) or
       (soWrap in SearchOptions)) then
    Index := PosEx(Search, Text, 1);
  Result := Index > 0;
  if Result then
  begin
    Control.SelStart := Index - 1;
    Control.SelLength := Length(Search);
  end;
end;

Upvotes: 0

Views: 903

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 597941

Searching and filtering text before putting it into the RichEdit is best.

However, if the text is already loaded in the RichEdit, TRichEdit does have a FindText() method you can use, you should not be searching its Text property manually. For example:

function SearchText(Control: TCustomRichEdit; const Search: string; SearchOptions: TSearchOptions): Boolean;
var
  StartPos, SearchLen, Index: Integer;
  Options: TSearchTypes;
begin
  if soIgnoreCase in SearchOptions then
    Options := []
  else
    Options := [stMatchCase];

  if soFromStart in SearchOptions then
  begin
    StartPos := 0;
    SearchLen := Control.GetTextLen;
    Index := Control.FindText(Search, StartPos, SearchLen, Options);
  end else
  begin
    StartPos := Control.SelStart + Control.SelLength;
    SearchLen := Control.GetTextLen - StartPos;
    Index := Control.FindText(Search, StartPos, SearchLen, Options);
    if (Index = -1) and (soWrap in SearchOptions) then
      Index := Control.FindText(Search, 0, StartPos, Options);
  end;
  Result := Index <> -1;
  if Result then
  begin
    Control.SelStart := Index;
    Control.SelLength := Length(Search);
  end;
end;

That being said, setting the background color of a line, or removing a line (there is no option to "hide" a line), is fairly simple.

Given any character index, you can send the RichEdit an EM_LINEFROMCHAR message to determine the line index that the character appears on.

You can then remove the line from the RichEdit by using the TRichEdit.Lines.Delete() method.

To set the line's background color takes a few steps:

  • send the RichEdit EM_LINEINDEX and EM_LINELENGTH messages to determine the line's starting and ending character indexes.

  • set the RichEdit's SelStart and SelLength properties (or send the RichEdit an EM_EXSETSEL message).

  • send the RichEdit an EM_SETCHARFORMAT message, specifying the SCF_SELECTION flag and using the CHARFORMAT2 record, to set the background color of the selection.

Upvotes: 1

Andreas Rejbrand
Andreas Rejbrand

Reputation: 109003

This is kind of a poor SO question, because it is a bit like "please write the code for me".

The natural approach would be to find the independent parts of the problem:

  1. How to represent an array of strings (lines) in Delphi?

  2. How to load a text file in Delphi into some in-memory array of strings?

  3. How to search for a substring in a string in Delphi?

  4. How to filter a Delphi in-memory array of strings? [This is trivial if you know 1 and has heard of loops. Doing it efficiently is slightly more interesting.]

  5. How to populate a Delphi TRichEdit control?

Indeed, if you know the answers to 1--5, doing what you want is trivial!

I might seem like a very grumpy old man now, but I think I do have a very important point about how to approach a programming problem.

Anyhow, let us address one issue at a time:

  1. The old-school approach of a array of string, today written TArray<string> works. This is a dynamic array of strings. Since Delphi dynamic arrays are managed by the compiler, they are convenient because you don't need to create and free them manually. However, they are a bit low-level and are sometimes misused.

    Probably a better alternative for you is to use the TStringList class.

  2. In IOUtils, you find TFile.ReadAllLines which takes a file name and returns the contents of the (text) file as an array of strings.

    Or use TStringList.LoadFromFile if you have a TStringList.

  3. Traditionally, you would use the Pos function. But today you can use the string helper: MyString.Contains(). Obviously, you need to decide if you want to treat CAPITALS and small letters as identical or not.

  4. Use a trivial for or for in loop to populate a second array from the original array, based on the test from 3.

  5. If you have a TStringList, just use TRichEdit.Lines.Assign.

Putting it all together, using a fairly smart combination of string arrays and TStringList:

procedure TForm1.Button1Click(Sender: TObject);
begin

  var Lines := TFile.ReadAllLines('K:\test.txt');

  var FilteredLines := TStringList.Create;
  try

    for var Line in Lines do
      if Line.Contains('MyString') then
        FilteredLines.Add(Line);

    RichEdit1.Lines.Assign(FilteredLines);

  finally
    FilteredLines.Free;
  end;

end;

Upvotes: 0

Related Questions