【问题标题】:Hiding items in TListBox while filtering by String在按字符串过滤时隐藏 TListBox 中的项目
【发布时间】:2020-01-23 20:33:36
【问题描述】:

短版:有没有办法单独控制或修改 LisBox 项目?例如,将它们的 Visible 属性分别设置为 False。 我在搜索的时候在Fire Monkey中发现了一个TListBoxItem类,但是我不想在VCL中使用Fire Monkey。

详细版本: 我尝试使用两个 TStringList 和一个 Edit 过滤我的 ListBox,一个 StringList 是全局的以保留原始列表 (list_files_global),另一个 StringList 帮助过滤过程 (list_files_filter),我的主要文件列表是我的 ListBox (@ 987654323@)。 当程序开始存储我的原始列表时,我在 onCreate 事件上创建了我的全局 StringList:

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 帮助文件,没有任何有用的东西出现。

【问题讨论】:

  • 当您填充与过滤器匹配的列表框项目时,您可以使用 AddObject,而不是 Add,其中“object”是指向 list_files_global 中条目的指针(或索引,但更复杂),所以您可以根据需要返回以对其进行编辑或删除。
  • @MartynA 我在我正在编写的原始程序中使用类和“AddObject”,我只是避免制作有问题的长代码和细节。您引用的方式几乎是正确的,但是它使代码更加复杂且有些混乱,程序包含许多复杂的代码,过程,交叉引用等……这个问题的目的是使所有这些都变得不那么复杂和扭曲,更整洁。感谢您的评论。
  • @Armin 事实上,我在回复你的最后一条评论,你说“这个问题的目的是让它们变得不那么复杂和扭曲,更整洁。”任何比提议的解决方案“不那么复杂”的解决方案(您似乎拒绝了)都将求助于第三方解决方案,这在此处是题外话。
  • 对 [short version] 问题的简单回答是否定的,没有办法单独隐藏列表框中的列表项。至少在 VCL 中没有。 TListBox 的 FMX 版本只是对齐的控件,所以你可以在那里做。但就 VCL 而言,您要么咬紧牙关,自己编写以根据需要添加/删除项目(这是我一直做的),要么求助于第三方解决方案,这取决于您选择的内容,甚至可以更多复杂,比如Virtual TreeView。顺便说一句,否决票不是来自我。
  • 对简短版本的简单回答:在虚拟模式下使用列表框。在 VCL 中,这就是“样式”属性。将记录/对象列表保存为数据,并拥有您想要的所有信息。该框会询问您要显示的内容。

标签: delphi vcl tlistbox


【解决方案1】:

VCL 列表框的项目,API 中的List Box,没有任何可见性属性。不显示项目的唯一选择是删除它。

您可以在虚拟模式下使用控件,此时根本没有任何项目。您决定要保留哪些数据,要显示什么。这就是 API 中的 LBS_NODATA 窗口样式。在 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;

这里发生的是列表框需要一个项目来显示提供它的索引。我们遍历主列表计数可见项目以找出与该索引匹配的项目,并提供其文本。

【讨论】:

  • 感谢您的回答,但我使用您的示例创建了一个测试项目,它与我试图解决的问题相同。当我删除或修改一个项目时,它将通过关闭过滤(通过使 EditBox 为空)恢复到原始状态。我不知道我是否做错了什么,但我使用了您的确切示例,然后我添加的唯一代码是从 ListBox 中删除一个项目,不多也不少。
  • @Armini - 正如答案所示,列表框中没有项目。您需要从TListItems 中删除一个元素,然后更新列表框的Count 属性。
  • 是的……就是这样。工作只是文件。太好了……谢谢。这绝对是答案。
【解决方案2】:

这是我经常做的事情,但使用列表视图而不是列表框。不过基本原理是一样的。

我倾向于将单个项目存储为对象,它们是 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;

结果:

请注意,每只狗始终只有一个内存对象,因此如果您重命名一只狗,更改将反映在列表视图中,无论是否过滤。 (但别忘了让它失效!)

【讨论】:

  • 这是一个很好的解决方案。非常感谢。但正如问题所说,我的问题是关于 TListBox,所以 Sertac 的解决方案就是我的问题的答案。然而,您的解决方案给了我一个完全不同的视角,这很好,因为我要改变我的整个程序并使用 ListView 而不是 ListBox。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-30
  • 1970-01-01
  • 1970-01-01
  • 2011-08-14
  • 1970-01-01
  • 1970-01-01
  • 2021-12-26
相关资源
最近更新 更多