0

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
SHKODRAN
  • 15
  • 1
  • 8
  • Yes, it is certainly possible, even very easy. What have you tried? – Andreas Rejbrand Jan 29 '22 at 18:55
  • I have try to search, but i haven't how to do what i'm asking for. – SHKODRAN Jan 29 '22 at 18:59
  • 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? (6) How to set ... – Andreas Rejbrand Jan 29 '22 at 18:59
  • ... background colour of a line in a `TRichEdit`. Indeed, if you know the answers to (1)--(6), doing what you want is trivial! I am sure you know most of these already, so which one in particular are you having issues with? Have you search Google or SO for that particular issue? – Andreas Rejbrand Jan 29 '22 at 19:00
  • 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. – Andreas Rejbrand Jan 29 '22 at 19:01
  • I wrote the example, but I can't do what I asked for. If you have some example, shuold be great. Thanks. – SHKODRAN Jan 29 '22 at 19:14

2 Answers2

1

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
0

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;
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • Of course, there are a thousand variations on this theme. – Andreas Rejbrand Jan 29 '22 at 19:21
  • Thank you, it's working fine, it's showing only the lines contains my string. Another question: you code Var Lines := TFile.ReadAllLines('C:\myfile.txt'); read the file from location, in case i have already opened the file in my TRichedit? Thank you. – SHKODRAN Jan 29 '22 at 19:37
  • @SHKODRAN: Yes, certainly. I think you can figure out how to do that. – Andreas Rejbrand Jan 29 '22 at 19:46
  • Either you make a very small change to the code above, or you can do simply `for var i := RichEdit1.Lines.Count - 1 downto 0 do if not RichEdit1.Lines[i].Contains('MyString') then RichEdit1.Lines.Delete(i);`. Yes, that's a three-liner! – Andreas Rejbrand Jan 29 '22 at 20:03