按字符串过滤时隐藏 TListBox 中的项目
Hiding items in TListBox while filtering by String
精简版: 有没有办法单独控制或修改 LisBox 项目?例如分别将它们的 Visible 属性 设置为 False。
我在搜索的时候在Fire Monkey中找到了一个TListBoxItem class,但是我不想用Fire Monkey,想要它在 VCL 中。
详细版本:
我尝试使用两个 TStringList 和一个 Edit 来过滤我的 ListBox,一个 StringList 是全局的以保留原始列表(list_files_global
),另一个 StringList 用于帮助过滤过程(list_files_filter
),我的主要文件列表是我的列表框(list_files
)。
当程序开始存储我的原始列表时,我在 onCreate
事件上创建了全局 StringList:
procedure Tfrm_main.FormCreate(Sender: TObject);
Begin
list_files_global := TStringList.Create;
list_files_global.Assign(list_files.Items);
End;
并使用编辑的 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;
到目前为止,一切正常,但问题是当我尝试 edit/rename/delete 过滤列表中的项目时,例如我更改了一个项目:
list_files.Items[i] := '-- Changed Item --';
列表将被编辑,但当我关闭过滤器时,原始列表将返回并且所有更改都将丢失。
所以我想知道有什么正确的方法可以解决这个问题吗?诸如单独隐藏项目或更改项目可见性等之类的东西......因此我可以更改过滤算法并摆脱所有这些制作额外列表。
我在互联网上搜索了一整天,并查看了 Delphi 的帮助文件,但没有找到任何有用的信息。
VCL 列表框的项目 List Box 在 API 中没有任何可见性 属性。不显示项目的唯一选择是将其删除。
但是您可以在虚拟模式下使用控件,其中根本没有任何项目。您决定保留哪些数据,显示哪些数据。那是 LBS_NODATA
window style in the API. In VCL, set the style
property to 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;
这里发生的是列表框需要一个项目来显示它的索引。我们循环遍历主列表,计算可见项目以找出与该索引匹配的项目,并提供其文本。
这是我经常做的事情,但使用列表视图而不是列表框。不过,基本原则是相同的。
我倾向于将单个项目存储为对象,它们是 Delphi 中的引用类型。我将它们全部保存在一个主要的未过滤列表中,该列表拥有对象,同时我维护一个过滤列表(不拥有对象)用于显示目的。与@Sertac 一样,我将它与虚拟列表视图结合起来。
要了解这在实践中是如何工作的,请创建一个新的 VCL 应用程序并在主窗体上放置一个列表视图 (lvDisplay
) 和一个编辑控件 (eFilter
):
请注意,我已将三列添加到列表视图控件:"Name"、"Age" 和 "Colour"。我也将其设为虚拟 (OwnerData = True
)。
现在为单个数据项定义 class:
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;
结果:
请注意,每只狗总是只有一个 in-memory 对象,因此如果您重命名一只狗,更改将反映在列表视图中,无论是否过滤。 (但别忘了让它失效!)
精简版: 有没有办法单独控制或修改 LisBox 项目?例如分别将它们的 Visible 属性 设置为 False。 我在搜索的时候在Fire Monkey中找到了一个TListBoxItem class,但是我不想用Fire Monkey,想要它在 VCL 中。
详细版本:
我尝试使用两个 TStringList 和一个 Edit 来过滤我的 ListBox,一个 StringList 是全局的以保留原始列表(list_files_global
),另一个 StringList 用于帮助过滤过程(list_files_filter
),我的主要文件列表是我的列表框(list_files
)。
当程序开始存储我的原始列表时,我在 onCreate
事件上创建了全局 StringList:
procedure Tfrm_main.FormCreate(Sender: TObject);
Begin
list_files_global := TStringList.Create;
list_files_global.Assign(list_files.Items);
End;
并使用编辑的 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;
到目前为止,一切正常,但问题是当我尝试 edit/rename/delete 过滤列表中的项目时,例如我更改了一个项目:
list_files.Items[i] := '-- Changed Item --';
列表将被编辑,但当我关闭过滤器时,原始列表将返回并且所有更改都将丢失。 所以我想知道有什么正确的方法可以解决这个问题吗?诸如单独隐藏项目或更改项目可见性等之类的东西......因此我可以更改过滤算法并摆脱所有这些制作额外列表。 我在互联网上搜索了一整天,并查看了 Delphi 的帮助文件,但没有找到任何有用的信息。
VCL 列表框的项目 List Box 在 API 中没有任何可见性 属性。不显示项目的唯一选择是将其删除。
但是您可以在虚拟模式下使用控件,其中根本没有任何项目。您决定保留哪些数据,显示哪些数据。那是 LBS_NODATA
window style in the API. In VCL, set the style
property to 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;
这里发生的是列表框需要一个项目来显示它的索引。我们循环遍历主列表,计算可见项目以找出与该索引匹配的项目,并提供其文本。
这是我经常做的事情,但使用列表视图而不是列表框。不过,基本原则是相同的。
我倾向于将单个项目存储为对象,它们是 Delphi 中的引用类型。我将它们全部保存在一个主要的未过滤列表中,该列表拥有对象,同时我维护一个过滤列表(不拥有对象)用于显示目的。与@Sertac 一样,我将它与虚拟列表视图结合起来。
要了解这在实践中是如何工作的,请创建一个新的 VCL 应用程序并在主窗体上放置一个列表视图 (lvDisplay
) 和一个编辑控件 (eFilter
):
请注意,我已将三列添加到列表视图控件:"Name"、"Age" 和 "Colour"。我也将其设为虚拟 (OwnerData = True
)。
现在为单个数据项定义 class:
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;
结果:
请注意,每只狗总是只有一个 in-memory 对象,因此如果您重命名一只狗,更改将反映在列表视图中,无论是否过滤。 (但别忘了让它失效!)