Скрытие элементов в TListBox при фильтрации по String - PullRequest
4 голосов
/ 23 сентября 2019

Короткая версия : Есть ли способ индивидуального управления или изменения элементов LisBox?например, установите для их свойства Visible значение False отдельно.Я нашел класс TListBoxItem в Fire Monkey , когда искал, но я не хочу использовать Fire Monkey и хочу его в VCL.

Подробная версия : я попытался отфильтровать свой ListBox, используя два TStringList и Edit, один StringList является глобальным, чтобы сохранить исходный список (list_files_global), а другой StringList, чтобы помочь процедуре фильтрации (list_files_filter) и мой основной списокфайлы - это мой ListBox (list_files).Я создал свой глобальный StringList для события onCreate, когда программа начинает сохранять мой исходный список:

procedure Tfrm_main.FormCreate(Sender: TObject);
Begin
  list_files_global := TStringList.Create;
  list_files_global.Assign(list_files.Items);
End;

, и использовал событие Edit onChange для фильтрации:

procedure Tfrm_main.edit_files_filterChange(Sender: TObject);
Var
  list_files_filter: TStringList;
  i: Integer;
Begin
  list_files_filter := TStringList.Create;
  list_files_filter.Assign(list_files.Items);

  list_files.Clear;

  for i := 0 to list_files_filter.Count - 1 do 
    if pos(edit_files_filter.text, list_files_filter[i]) > 0 then 
      list_files.Items.Add(list_files_filter[i]);

End;

и дляотключив фильтр, просто восстановите список из моего глобального списка, который я создал сначала:

list_files.Items := list_files_global;

здесь пока все работает просто отлично, но проблема в том, что я пытаюсь отредактировать / переименовать /удалить элементы из отфильтрованного списка, например, я изменить элемент:

list_files.Items[i] := '-- Changed Item --';

список будет отредактирован, но когда я отключу фильтр, первоначальный список вернется, и все изменения будут потеряны.поэтому я хочу знать, есть ли правильный способ решить эту проблему?Что-то вроде отдельного скрытия элементов или изменения видимости элементов и т. Д., Поэтому я могу изменить алгоритм фильтрации и избавиться от всего этого, создавая дополнительные списки.Я искал в интернете и целый день просматривал файл справки Delphi, но ничего полезного не нашлось.

Ответы [ 2 ]

7 голосов
/ 24 сентября 2019

Элементы списка VCL, Список в API, не имеют свойства видимости.Единственный вариант не показывать элемент - это удалить его.

Однако вы можете использовать элемент управления в виртуальном режиме, когда элементов вообще нет.Вы сами решаете, какие данные хранить, какие отображать.Это LBS_NODATA стиль окна в API.В VCL установите для свойства style значение lbVirtual.

Ниже приведен чрезвычайно упрощенный пример.

Давайте сохраним массив записей, по одной записи на виртуальный элемент.

type
  TListItem = record
    FileName: string;
    Visible: Boolean;
  end;

  TListItems = array of TListItem;

Вы можете расширить поля в соответствии с вашими требованиями.Видимость - одна из главных проблем в вопросе, добавил я.Вы, вероятно, добавили бы что-то, представляющее исходное имя, чтобы вы знали, какое имя было изменено, и т. Д.

Иметь один массив в каждом спискеЭтот пример содержит один список.

var
  ListItems: TListItems;

Хотя лучше сделать это поле, это только для демонстрации.

Необходимые единицы.

uses
  ioutils, types;

Некоторая инициализация при создании формы.Очистите фильтр.Установите стиль списка соответственно.Заполните некоторые имена файлов.Все элементы будут видны при запуске.

procedure TForm1.FormCreate(Sender: TObject);
var
  ListFiles: TStringDynArray;
  i: Integer;
begin
  ListFiles := ioutils.TDirectory.GetFiles(TDirectory.GetCurrentDirectory);

  SetLength(ListItems, Length(ListFiles));
  for i := 0 to High(ListItems) do begin
    ListItems[i].FileName := ListFiles[i];
    ListItems[i].Visible := True;
  end;

  ListBox1.Style := lbVirtual;
  ListBox1.Count := Length(ListFiles);

  Edit1.Text := '';
end;

В виртуальном режиме список интересует только свойство Count.Это определит, сколько элементов будет отображаться, соответственно область прокрутки.

Вот часть фильтра, она чувствительна к регистру.

procedure TForm1.Edit1Change(Sender: TObject);
var
  Text: string;
  Cnt: Integer;
  i: Integer;
begin
  Text := Edit1.Text;
  if Text = '' then begin
    for i := 0 to High(ListItems) do
      ListItems[i].Visible := True;
    Cnt := Length(ListItems);
  end else begin
    Cnt := 0;
    for i := 0 to High(ListItems) do begin
      ListItems[i].Visible := Pos(Text, ListItems[i].FileName) > 0;
      if ListItems[i].Visible then
        Inc(Cnt);
    end;
  end;
  ListBox1.Count := Cnt;
end;

Особый случай в редакторе OnChange заключается в том, чтокогда текст пуст.Тогда все предметы покажут.В противном случае код от вопроса.Здесь мы также сохраняем общее количество видимых элементов, чтобы мы могли соответствующим образом обновить список.

Теперь единственная интересная часть, список требует данных.

procedure TForm1.ListBox1Data(Control: TWinControl; Index: Integer;
  var Data: string);
var
  VisibleIndex: Integer;
  i: Integer;
begin
  VisibleIndex := -1;
  for i := 0 to High(ListItems) do begin
    if ListItems[i].Visible then
      Inc(VisibleIndex);
    if VisibleIndex = Index then begin
      Data := ListItems[i].FileName;
      Break;
    end;
  end;
end;

Что здесь происходит, так это то, чтодля списка требуется элемент, показывающий его индекс.Мы перебираем главный список, подсчитывающий видимые элементы, чтобы выяснить, какой из них соответствует этому индексу, и предоставляем его текст.

6 голосов
/ 24 сентября 2019

Это то, что я часто делаю, но с представлениями списка вместо списков.Однако основные принципы одинаковы.

Я склонен хранить отдельные элементы как объекты, которые являются ссылочными типами в Delphi.И я храню их все в одном основном нефильтрованном списке, который владеет объектами, в то время как я поддерживаю отфильтрованный список (который не владеет объектами) для целей отображения.Как и @Sertac, я комбинирую это с виртуальным представлением списка.

Чтобы увидеть, как это работает на практике, создайте новое приложение VCL и удалите представление списка (lvDisplay) и элемент управления для редактирования (eFilter).) в основной форме:

Screenshot of main form with edit and list view control

Обратите внимание, что в элемент управления представления списка добавлены три столбца: «Имя», «Возраст» и «Цвет»».Я также делаю его виртуальным (OwnerData = True).

Теперь определим класс для отдельных элементов данных:

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;

, где

{ 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;

И давайте создадимнефильтрованный список собак:

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

где

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<TDogInfo>.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;

Идея состоит в том, что элемент управления представления списка всегда отображает FFilteredList, который либо указывает на тот же экземпляр объекта, что и FList, или указывает на отфильтрованную (или отсортированную) версию:

// 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<TDogInfo>.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;

Результат:

Screenshot of populated list, no filtering

Screenshot of filtered list

Обратите внимание, что для каждой собаки всегда есть только один объект в памяти, поэтому, если вы переименуете собаку, изменения будут отражены в виде списка, отфильтрованы или нет.(Но не забудьте аннулировать его!)

...