Kristian Sander
Kristian Sander

Reputation: 291

Color Listbox.Item[N] where N is generated by code

I have a Listbox. I populate it with a file using this:

IF Opendialog1.Execute then
   BEGIN
      Listbox1.Items.LoadfromFile(OpenDialog1.FileName);
   END;

The file loaded contains numbers, and numbers only (I assume). To be 100 pct. sure, I now starts a scan: (pseudocode :)

for N := 0 til Listbox1.Items.Count -1 DO
   BEGIN
      NUM := ScanForNotNumberInListbox1Item(Listbox1.Items[N]);
      // 
      // returns NUM = -1 if non digit is met..
      //    
      IF NUM <> 0 then      
         begin
            LISTBOX1.Items[N].BackGroundColor := RED;
            Exit;  (* or terminate *)
         END;
  END;  

I know I have to use LIstbox1.DrawItem (); and have tried several af the examples shown here in Stack Exchange, but none of the used examples seems to be code-generated.

So how Can I do that ?

Kris

Upvotes: 1

Views: 1780

Answers (2)

Andreas Rejbrand
Andreas Rejbrand

Reputation: 108963

Introduction

You can store additional information about each list item in its associated "object". This can be a (pointer to a) real object, or you can use this pointer-sized integer to encode any simple information you want.

As a simple example, let's put the item's background colour in this field (uses Math):

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin

  ListBox1.Items.BeginUpdate;
  try
    ListBox1.Clear;
    for i := 1 to 100 do
      ListBox1.Items.AddObject(i.ToString, TObject(IfThen(Odd(i), clSkyBlue, clMoneyGreen)));
  finally
    ListBox1.Items.EndUpdate;
  end;

end;

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  ListBox: TListBox;
  Canvas: TCanvas;
  S: string;
begin
  ListBox := Control as TListBox;
  Canvas := ListBox.Canvas;

  Canvas.Brush.Color := TColor(ListBox.Items.Objects[Index]);
  Canvas.FillRect(Rect);
  S := ListBox.Items[Index];
  Canvas.TextRect(Rect, S, [tfSingleLine, tfVerticalCenter]);
end;

Don't forget to set the list box's Style property to lbOwnerDrawFixed (say).

Screenshot of a form with a list box with alternating row colours.

A more "advanced" approach would be to associate an actual object with each item:

type
  TItemFormat = class
    BackgroundColor: TColor;
    TextColor: TColor;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  ItemFormat: TItemFormat;
begin

  ListBox1.Items.BeginUpdate;
  try
    ListBox1.Clear;
    for i := 1 to 100 do
    begin
      ItemFormat := TItemFormat.Create;
      ItemFormat.BackgroundColor := IfThen(Odd(i), clSkyBlue, clMoneyGreen);
      ItemFormat.TextColor := IfThen(Odd(i), clNavy, clGreen);
      ListBox1.Items.AddObject(i.ToString, ItemFormat);
    end;
  finally
    ListBox1.Items.EndUpdate;
  end;

end;

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  ListBox: TListBox;
  Canvas: TCanvas;
  ItemFormat: TItemFormat;
  S: string;
begin
  ListBox := Control as TListBox;
  Canvas := ListBox.Canvas;
  ItemFormat := ListBox.Items.Objects[Index] as TItemFormat;

  Canvas.Brush.Color := ItemFormat.BackgroundColor;
  Canvas.FillRect(Rect);
  S := ListBox.Items[Index];
  Canvas.Font.Color := ItemFormat.TextColor;
  Canvas.TextRect(Rect, S, [tfSingleLine, tfVerticalCenter]);
end;

Screenshot of a form with a list box with alternating row colours; in this image, the text colours also alternate.

(In this case, you own the objects, so you are responsible for freeing them when they are no longer needed.)

Putting everything in action

In your particular case, I'd try something like

procedure TForm1.Button1Click(Sender: TObject);
var
  i, dummy, FirstInvalidIndex: Integer;
begin

  with TOpenDialog.Create(Self) do
    try
      Filter := 'Text files (*.txt)|*.txt';
      Options := [ofPathMustExist, ofFileMustExist];
      if Execute then
        ListBox1.Items.LoadFromFile(FileName);
    finally
      Free;
    end;

  FirstInvalidIndex := -1;
  ListBox1.Items.BeginUpdate;
  try
    for i := 0 to ListBox1.Count - 1 do
      if not TryStrToInt(ListBox1.Items[i], dummy) then
      begin
        ListBox1.Items.Objects[i] := TObject(1);
        if FirstInvalidIndex = -1 then
          FirstInvalidIndex := i;
      end;
  finally
    ListBox1.Items.EndUpdate;
  end;

  if FirstInvalidIndex <> -1 then
  begin
    ListBox1.ItemIndex := FirstInvalidIndex;
    MessageBox(Handle, 'An invalid row was found.', PChar(Caption), MB_ICONERROR);
  end;

end;

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  ListBox: TListBox;
  Canvas: TCanvas;
  S: string;
begin
  ListBox := Control as TListBox;
  Canvas := ListBox.Canvas;

  Canvas.Font.Assign(ListBox.Font);

  if odSelected in State then
  begin
    Canvas.Brush.Color := clHighlight;
    Canvas.Font.Color := clHighlightText;
  end
  else
  begin
    Canvas.Brush.Color := clWindow;
    Canvas.Font.Color := clWindowText;
  end;

  if ListBox.Items.Objects[Index] = TObject(1) then
  begin
    Canvas.Font.Color := clRed;
    Canvas.Font.Style := [fsBold, fsStrikeOut]
  end;

  Canvas.FillRect(Rect);
  S := ListBox.Items[Index];
  Canvas.TextRect(Rect, S, [tfSingleLine, tfVerticalCenter]);

end;

A form with a list box containing numbers and the occasional non-numeric row. The non-numeric rows are highlighted in red and strike through.

The fine print: Notice that the above snippets are only simple examples intended to demonstrate the basic approach. In a real application, you need to be more careful about the details. For instance, you cannot use a hard-coded red text colour if the background colour is a system colour (because that colour might very well be red too!).

In addition, what happens if the text file is empty (try it!)?

Upvotes: 3

MBo
MBo

Reputation: 80197

Set lbOwnerDrawFixed (or another ownerdraw) style for Listbox

Listbox items have auxiliary property Objects[] and you can set Objects[i] to non-nil value for invalid items

IF NUM <> 0 then      
    LISTBOX1.Objects[N] := TObject(1);

Use some example for OnDrawItem event treatment and use Objects[] to define background color during drawing

Upvotes: 0

Related Questions