Haifisch
Haifisch

Reputation: 909

ListBoxItem Visible Error

There is something that I didn't understand with TListBox and TListBoxItem in Delphi 10.2 Tokyo.

Some values (TListBoxItem) are load to my ListBox, when the first letter change I add a TListBoxGroupHeader.

procedure TForm1.Button1Click(Sender: TObject);
var
   lbItem: TListBoxItem;
   Letter: string;
   ListBoxGroupHeader: TListBoxGroupHeader;
   i: integer;
   ListValue: TStringList;
begin
   Letter := '';

   ListValue := TStringList.Create;
   try
      ListValue.Add('Germany');
      ListValue.Add('Georgie');
      ListValue.Add('France');
      ListValue.Add('Venezuela');
      ListValue.Add('Poland');
      ListValue.Add('Russia');
      ListValue.Add('Sweden');
      ListValue.Add('Denmark');

      ListBox1.BeginUpdate;

      for i := 0 to ListValue.Count - 1 do
      begin
         if Letter <> Copy(ListValue[i], 0, 1).ToUpper then
         begin
            ListBoxGroupHeader        := TListBoxGroupHeader.Create(ListBox1);
            ListBoxGroupHeader.Text   := Copy(ListValue[i], 0, 1).ToUpper;
            ListBox1.AddObject(ListBoxGroupHeader);
         end;

         lbItem := TListBoxItem.Create(ListBox1);
         lbItem.Text   := ListValue[i];
         lbItem.Tag    := i;

         ListBox1.AddObject(lbItem);
         Letter := Copy(ListValue[i], 0, 1).ToUpper;
      end;

   finally
      ListBox1.EndUpdate; 
      FreeAndNil(ListValue);
   end;
end;

I use a TEdit to search in this ListBox. That's here that I have a problem. If ListBoxItem contain the content of the Edit I set Visible to True, else I set it to False.

procedure TForm1.Edit1ChangeTracking(Sender: TObject);
var
   i           : integer;
   ListBoxItem: TListBoxItem;
begin
   ListBox1.BeginUpdate;
   try
      for i := 0 to ListBox1.Items.Count - 1 do
      begin
         if ListBox1.ListItems[i] is TListBoxItem then
         begin
            ListBoxItem := TListBoxItem(ListBox1.ListItems[i]);

            if Edit1.Text.Trim = '' then
            begin
               ListBoxItem.Visible := True
            end
            else
            begin
               if ListBox1.ListItems[i] is TListBoxGroupHeader then
                  ListBoxItem.Visible := False
               else
                  ListBoxItem.Visible := ListBoxItem.Text.ToLower.Contains(Edit1.Text.Trim.ToLower);
            end;
         end;
      end;
   finally
      ListBox1.EndUpdate;
   end;
end;

The first GroupHeader (letter G) is always visible ! and it's look like there is a ListBoxItem behind the GroupHeader.. When I use a checkpoint Visible is set to false .. so I didn't understand..

If I write the letter "V" I only see the GroupHeader with letter "G".

I have evene try to change the text value if it's a GroupHeader.

if ListBox1.ListItems[i] is TListBoxGroupHeader then
   ListBoxItem.Text := '>>' + ListBoxItem.Text + '<<'   

Thats change text but not for the first GroupHeader (letter G) ...

Don't know if I use it bad, or if it's a bug ??

Upvotes: 1

Views: 1220

Answers (1)

Victoria
Victoria

Reputation: 7912

I could have reproduce what you've described and it has something to do with hiding header whilst keeping item under that header visible. In such case application shows header rather than the item. I haven't checked what's wrong inside but it seems it is not what you want. IMHO you want to keep visible items that match to a search text with their respective header and hide only headers with no items under.

If that is so, try this:

procedure FilterItems(const Text: string; ListBox: TListBox);
var
  I: Integer; { ← loop variable }
  Hide: Boolean; { ← flag indicating if we want to hide the last header we passed }
  Item: TListBoxItem; { ← currently iterated item }
  Head: TListBoxGroupHeader; { ← last header item we passed during iteration }
begin
  Head := nil;
  Hide := True;

  ListBox.BeginUpdate;
  try
    { if search text is empty, show all items }
    if Text.IsEmpty then
      for I := 0 to ListBox.Content.ControlsCount - 1 do
        ListBox.ListItems[I].Visible := True
    else
    { otherwise compare text in non header items }
    begin
      for I := 0 to ListBox.Content.ControlsCount - 1 do
      begin
        Item := ListBox.ListItems[I];
        { if the iterated item is header }
        if Item is TListBoxGroupHeader then
        begin
          { set the previous header visibility by at least one visible item }
          if Assigned(Head) then
            Head.Visible := not Hide;
          { assume hiding this header and store its reference }
          Hide := True;
          Head := TListBoxGroupHeader(Item);
        end
        else
        { if the iterated item is a regular item }
        if Item is TListBoxItem then
        begin
          { set the item visibility by matching text; if the item remains visible, it
            means we don't want to hide the header, so set our flag variable as well }
          if Item.Text.ToLower.Contains(Text) then
          begin
            Hide := False;
            Item.Visible := True;
          end
          else
            Item.Visible := False;
        end;
      end;
      { the iteration finished, so now setup visibility of the last header we passed }
      if Assigned(Head) then
        Head.Visible := not Hide;
    end;
  finally
    ListBox.EndUpdate;
  end;
end;

procedure TForm1.Edit1ChangeTracking(Sender: TObject);
begin
  FilterItems(Edit1.Text.Trim.ToLower, ListBox1);
end;

Upvotes: 1

Related Questions