简短版本:是否可以单独控制或修改LisBox项目?例如,将其Visible 属性分别设置为False。在搜索时,我在Fire Monkey中找到了一个TListBoxItem类,但我不想使用Fire Monkey,而又希望在VCL中使用它。
详细版本:我尝试使用两个TStringList和一个Edit过滤ListBox,一个StringList是全局的,以保留原始列表(list_files_global
),另一个StringList是帮助过滤过程的(list_files_filter
),而我的主要文件列表是ListBox(list_files
)。我在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帮助文件,没有发现有用的信息。
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;
此处发生的是,列表框要求显示一个项目以提供其索引。我们遍历主列表,对可见项进行计数,以找出与该索引匹配的项,并提供其文本。
我经常这样做,但是使用列表视图而不是列表框。但是,基本原理是相同的。
我倾向于将单个项目存储为对象,这是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; 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.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.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;
结果:
请注意,每只狗始终只有一个内存中对象,因此,如果重命名狗,则更改将反映在列表视图中(是否过滤)。(但不要忘记使它无效!)