Hiding items in TListBox while filtering by String

后端 未结 2 568
深忆病人
深忆病人 2021-01-13 06:02

Short Version: Is there any way to control or modify LisBox items individually? for example set their Visible property to False separately. I foun

2条回答
  •  春和景丽
    2021-01-13 06:45

    This is something I often do, but with list views instead of list boxes. The basic principles are the same, though.

    I tend to store the individual items as objects, which are reference types in Delphi. And I keep them all in one main unfiltered list, which owns the objects, while I maintain a filtered list (which does not own the objects) for display purposes. Like @Sertac, I combine this with a virtual list view.

    To see how this works in practice, create a new VCL application and drop a list view (lvDisplay) and an edit control (eFilter) on the main form:

    Notice I have added three columns to the list view control: "Name", "Age", and "Colour". I also make it virtual (OwnerData = True).

    Now define the class for the individual data items:

    type
      TDogInfo = class
        Name: string;
        Age: Integer;
        Color: string;
        constructor Create(const AName: string; AAge: Integer; const AColor: string);
        function Matches(const AText: string): Boolean;
      end;
    

    where

    { TDogInfo }
    
    constructor TDogInfo.Create(const AName: string; AAge: Integer;
      const AColor: string);
    begin
      Name := AName;
      Age := AAge;
      Color := AColor;
    end;
    
    function TDogInfo.Matches(const AText: string): Boolean;
    begin
      Result := ContainsText(Name, AText) or ContainsText(Age.ToString, AText) or
        ContainsText(Color, AText);
    end;
    

    And let us create the unfiltered list of dogs:

    TForm1 = class(TForm)
      eFilter: TEdit;
      lvDisplay: TListView;
      procedure FormCreate(Sender: TObject);
      procedure FormDestroy(Sender: TObject);
    private
      FList, FFilteredList: TObjectList;
    public
    end;
    

    where

    function GetRandomDogName: string;
    const
      DogNames: array[0..5] of string = ('Buster', 'Fido', 'Pluto', 'Spot', 'Bill', 'Rover');
    begin
      Result := DogNames[Random(Length(DogNames))];
    end;
    
    function GetRandomDogColor: string;
    const
      DogColors: array[0..2] of string = ('Brown', 'Grey', 'Black');
    begin
      Result := DogColors[Random(Length(DogColors))];
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    var
      i: Integer;
    begin
    
      FList := TObjectList.Create(True); // Owns the objects
    
      // Populate with sample data
      for i := 1 to 1000 do
        FList.Add(
          TDogInfo.Create(GetRandomDogName, Random(15), GetRandomDogColor)
        );
    
      FFilteredList := FList;
    
      lvDisplay.Items.Count := FFilteredList.Count;
      lvDisplay.Invalidate;
    
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      if FFilteredList <> FList then
        FreeAndNil(FFilteredList);
      FreeAndNil(FList);
    end;
    

    The idea is that the list view control always displays the FFilteredList, which either points to the same object instance as FList, or points to a filtered (or sorted) version of it:

    // The list view's OnData event handler
    procedure TForm1.lvDisplayData(Sender: TObject; Item: TListItem);
    begin
    
      if FFilteredList = nil then
        Exit;
    
      if not InRange(Item.Index, 0, FFilteredList.Count - 1) then
        Exit;
    
      Item.Caption := FFilteredList[Item.Index].Name;
      Item.SubItems.Add(FFilteredList[Item.Index].Age.ToString);
      Item.SubItems.Add(FFilteredList[Item.Index].Color);
    
    end;
    
    // The edit control's OnChange handler
    procedure TForm1.eFilterChange(Sender: TObject);
    var
      i: Integer;
    begin
    
      if string(eFilter.Text).IsEmpty then // no filter, display all items
      begin
        if FFilteredList <> FList then
        begin
          FreeAndNil(FFilteredList);
          FFilteredList := FList;
        end;
      end
      else
      begin
        if (FFilteredList = nil) or (FFilteredList = FList) then
          FFilteredList := TObjectList.Create(False); // doesn't own the objects
        FFilteredList.Clear;
        for i := 0 to FList.Count - 1 do
          if FList[i].Matches(eFilter.Text) then
            FFilteredList.Add(FList[i]);
      end;
    
      lvDisplay.Items.Count := FFilteredList.Count;
      lvDisplay.Invalidate;
    
    end;
    

    The result:

    Notice that there always is only one in-memory object for each dog, so if you rename a dog, the changes will reflect in the list view, filtered or not. (But don't forget to invalidate it!)

提交回复
热议问题