Скрытие элементов в TListBox при фильтрации по строке

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

Подробная версия: я попытался отфильтровать свой ListBox, используя два TStringList и Edit, один StringList является глобальным, чтобы сохранить исходный список (list_files_global), а другой StringList, чтобы помочь процедуре фильтрации (list_files_filter) и мой основной список files - это мой 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 целый день, но ничего полезного не обнаружил.


person Armin Taghavizad    schedule 23.09.2019    source источник
comment
Вы можете использовать AddObject вместо Add при заполнении элементов списка, соответствующих фильтру, при этом объект является указателем (или индексом, но более сложным) на запись в вашем list_files_global, чтобы вы могли вернуться к нему для редактирования или удаления это по мере необходимости.   -  person MartynA    schedule 23.09.2019
comment
@MartynA Я использую классы и AddObject в своей исходной программе, которую я пишу, я просто избегал создания длинных кодов и рассматриваемых деталей. То, как вы упомянули, почти правильное, но оно делает код более сложным и немного беспорядочным, программа содержит множество сложных кодов, процедур, перекрестных ссылок и т.д ... Цель этого вопроса заключалась в том, чтобы сделать их все менее сложными и скрученный и аккуратнее. Спасибо за ваш комментарий.   -  person Armin Taghavizad    schedule 23.09.2019
comment
Иногда сложный код - единственный способ что-то сделать. Лучший способ очистить его - создать отдельный многократно используемый класс, который выполняет нужную вам работу за пределами пользовательского интерфейса. ИМО, радуюсь, что вам не нужно добавлять ассемблерный код! FWIW, вы можете начать с сокращения имен идентификаторов, например, удаления этих подчеркиваний. Судя по всему, у вас есть тонны кода прямо внутри основной формы, а не разделяющая логика.   -  person Jerry Dodge    schedule 23.09.2019
comment
@JerryDodge Я прекрасно понимаю, что сложный код - это единственный способ иногда что-то сделать ... мы сбились с сути ... из-за заголовка сообщения моя проблема не в написании сложных кодов, а я ' Я не новичок, чтобы бояться строк кода, вопрос заключается в поиске доступа к элементам ListBox другим способом, например TListBoxItem, давайте перейдем к этой теме .... можно ли сделать такую ​​вещь (или что-то в этом роде) как-то? Однако ... спасибо за ваш комментарий ????   -  person Armin Taghavizad    schedule 24.09.2019
comment
@Armin Действительно, я отвечал на ваш последний комментарий, в котором вы сказали, что цель этого вопроса - сделать их все менее сложными, запутанными и более аккуратными. Любые менее сложные решения, чем предложенное (и вы, казалось бы, отклонили), будут прибегать к сторонним решениям, которые здесь не по теме.   -  person Jerry Dodge    schedule 24.09.2019
comment
Простой ответ на ваш [краткий вариант] вопрос - нет, нет способа индивидуально скрыть элементы списка в окне списка. По крайней мере, не в VCL. FMX-версия TListBox - это просто выровненные элементы управления, так что вы можете сделать это там. Но что касается VCL, вы либо кусаете пулю и пишете сами, чтобы добавлять / удалять элементы по мере необходимости (что я всегда делаю), либо прибегаете к стороннему решению, которое в зависимости от того, что вы выберете, может получить даже более сложные, такие как Virtual TreeView. Кстати, отрицательный голос был не от меня.   -  person Jerry Dodge    schedule 24.09.2019
comment
Немного менее простой ответ на короткий вариант: используйте список в виртуальном режиме. В VCL это свойство стиля. Храните список записей / объектов в виде данных и располагайте всей необходимой информацией. Окно спрашивает, что отображать.   -  person Sertac Akyuz    schedule 24.09.2019


Ответы (2)


Элементы списка VCL, List Box в 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;

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

person Sertac Akyuz    schedule 24.09.2019
comment
Спасибо за ответ, но я создал тестовый проект с вашим примером, и у него та же проблема, которую я пытаюсь решить. Когда я удаляю или изменяю элемент, он вернется в исходное состояние, отключив фильтрацию (сделав EditBox пустым). Я не знаю, делаю ли я что-то не так, но я использовал ваш точный пример, тогда только код, который я добавил, должен был удалить элемент из ListBox, не меньше и не больше. - person Armin Taghavizad; 30.09.2019
comment
@Armini - Как видно из ответа, в списке нет элементов. Вам нужно удалить элемент из TListItems, а затем обновить свойство Count окна списка. - person Sertac Akyuz; 30.09.2019
comment
да ... вот оно что. Работает только файл. Было здорово ... спасибо. Это определенно ответ. - person Armin Taghavizad; 30.09.2019

Я часто это делаю, но со списками, а не со списками. Однако основные принципы остались прежними.

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

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

Скриншот основной формы с возможностью редактирования и просмотра списка

Обратите внимание, что я добавил три столбца к элементу управления представлением списка: «Имя», «Возраст» и «Цвет». Я также делаю его виртуальным (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;

Результат:

«Скриншот

Снимок экрана отфильтрованного списка

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

person Andreas Rejbrand    schedule 24.09.2019
comment
это было отличное решение. Большое спасибо. Но, как говорится в вопросе, моя проблема была связана с TListBox, поэтому решение Sertac было ответом на мою проблему. Однако ваше решение дало мне совершенно другую перспективу, и это было хорошо, поскольку я собираюсь изменить всю свою программу и использовать ListView вместо ListBox. - person Armin Taghavizad; 30.09.2019