TListView在虚拟模式下限制为100000000个项目



将ListView中的Items.Count设置为超过100000000的任何数字的结果与将Count设置为0的结果相同-这是底层windows控件的限制,还是Delphi特定的限制?我预计限制是大约20亿,因为DelphiXE4的文件说限制是一个(有符号)DWORD(即:2^31-1)的大小。

简单示例:

unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls;
type
TForm1 = class(TForm)
ListView1: TListView;
procedure ListView1Data(Sender: TObject; Item: TListItem);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// Assumes ListView1.OwnerData := True;
ListView1.Items.Count := 100000001; // Works if 100000000 is used instead
end;
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
Item.Caption := Item.Index.ToString();
end;
end.

我做了一些尝试,将LVM_SETEMCOUNT直接发送到底层控件,但对于任何超过100000000的LPARAM,它都会返回一个错误,并将内部计数设置为0,这让我相信这是底层控件的限制。我在任何地方都找不到这种记录——尽管我认为拥有那么多物品并不常见。假设这是控制的限制,可能应该提交一份Delphi错误报告,因为当调用失败时,TListView不会抛出异常——它只是无声地中断一切。

目前,我正在解决这个问题,方法是使列表视图处于虚拟模式之外,根据添加到列表中的控件(即VisibleRowCount属性)的大小准确地保持可见的项目数,在我的数据中保持偏移量,并在列表中的项目上循环,以与虚拟模式列表基本相同的方式填充列表,使用我自己的滚动条来控制偏移量,使其达到20亿左右的极限。

有办法绕过这种行为吗?任何有过处理大量数据和ListView经验的人都有什么见解吗?

这是我的新解决方法,它将虚拟ListView可以显示的最大项目数增加到9223372036854775807(2^63-1),以防对任何人有用。把它想象成一个虚拟的ListView。通过一些工作,可以将其扩展到所有视图,而不仅仅是列表或详细视图。

unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, System.Math;
type
TForm1 = class(TForm)
ListView1: TListView;
ScrollBar1: TScrollBar;
procedure ListView1Data(Sender: TObject; Item: TListItem);
procedure ListView1Resize(Sender: TObject);
procedure ScrollBar1Change(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ScrollBar1Scroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
private
{ Private declarations }
Offset: Int64;
ItemCount: Int64;
VisibleItems: Integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
// Assumptions:
// ListView1.OwnerData := True
// ListView1.ViewStyle := vsReport with columns set up OR vsList
// ScrollBar1.Min := 0;
// 
// The position of the scrollbar represents equally spaced points along the data
// You can increase this to any number the scrollbar supports
// By default, that means 101 points (0-100), from offset 0 to (ItemCount - VisibleItems + 1)
const
LISTVIEW_VIRTUALITEMS_MAX = 100000000;
procedure TForm1.FormCreate(Sender: TObject);
begin
ItemCount := High(Int64); // For testing
// Make sure the listview shows enough items
ListView1.Items.Count := Min(ItemCount, LISTVIEW_VIRTUALITEMS_MAX);
end;
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var
Index: Int64;
begin
// Item.Index now represents an offset from an offset, adding them together
// gives the true index
Index := Offset + Item.Index;
Item.Caption := Index.ToString; // Testing
end;
procedure TForm1.ListView1Resize(Sender: TObject);
begin
VisibleItems := ListView1.VisibleRowCount;
if VisibleItems = 0 then VisibleItems := 1;
ListView1.Items.Count := VisibleItems;
end;
procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
ListView1.Refresh;
end;
procedure TForm1.ScrollBar1Scroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
var
MaxOffset: Int64;
begin
// Int64 support for scrollbar, etc
MaxOffset := ItemCount - VisibleItems + 1;
case ScrollCode of
TScrollCode.scLineUp: begin
if Offset > 0 then
Offset := Offset - 1;
end;
TScrollCode.scLineDown: begin
if Offset < MaxOffset then
Offset := Offset + 1;
end;
scPageUp: begin
if Offset > VisibleItems then
Offset := Offset - VisibleItems
else
Offset := 0;
end;
scPageDown: begin
if (MaxOffset - Offset) > VisibleItems then
Offset := Offset + VisibleItems
else
Offset := MaxOffset;
end;
scPosition, scTrack: begin
Offset := Trunc((ScrollPos / Scrollbar1.Max) * MaxOffset);
Exit;
end;
scTop: begin
Offset := 0;
Exit;
end;
scBottom: begin
Offset := MaxOffset;
Exit;
end;
scEndScroll: begin
end;
end;
ScrollPos := Trunc((Offset / ItemCount) * ScrollBar1.Max);
ListView1.Refresh;
end;
end.

这确实是底层控件的限制。您需要更改UI设计以避免限制,或者找到不同的控件。话虽如此,我怀疑是否有许多控件可以有效地显示1亿个项目。

最新更新