TListView在虚拟模式下仅限于100,000,000个项目?

5
将ListView中的Setting Items.Count设置为任何大于100,000,000的数字与将计数设置为0具有相同的结果 - 这是底层Windows控件的限制,还是Delphi特定的?我预期限制应该是约20亿,因为Delphi XE4的文档说限制是(有符号)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_SETITEMCOUNT,但是任何超过100,000,000的LPARAM都会返回错误,并将内部计数设置为0,这使我相信这是底层控件的限制。我无法在任何地方找到相关文档 - 虽然我认为拥有那么多项目并不常见。假设这是控件的限制,应该会提交一个Delphi错误报告,因为TListView在调用失败时没有抛出任何异常 - 它只是默默地中断了一切。

目前,我的解决方法是将listview保持在非虚拟模式下,根据控件的大小(即:VisibleRowCount属性)添加到列表中确切数量的可见项,保持我的数据偏移,并循环遍历列表中的项以基本相同的方式填充列表,就像虚拟模式列表一样,使用自己的滚动条来控制偏移量以实际达到上限约为20亿。

是否有方法解决这种行为?对于处理大量数据和ListView有经验的人能否提供任何见解?


为什么不在ListView模式下使用VirtualTreeView? - Arioch 'The
我尝试过使用VirtualTreeView进行实验,但由于缺乏正确使用它的经验,虽然它非常快速,但从目前为止我所做的事情来看,它为列表中的每个项目分配了一个小结构,因此我在大约4000万个项目的标记周围遇到了内存问题。有没有关于如何像虚拟listview一样使用它的教程链接? - Chrisgbk
看起来Embarcadero的帮助文件又出问题了... - Arioch 'The
2
@Chrisgbk:只是好奇:用户如何在包含10^8个项目的列表视图中导航? - MartynA
1
好的,现在我有点明白了。坦白地说,我认为我会选择完全自定义控件,而不是从列表视图中挤出意义。非常有趣。我喜欢这个问题! - David Heffernan
显示剩余14条评论
2个回答

6

这是我的新解决方案,它可以将虚拟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.

请注意,尽管滚动条仅具有0和100的最小和最大值,但您可以使用滚动条访问每个单独的项目 - 请参见OnScroll处理。 - Chrisgbk

5

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


2
我已将此标记为答案,因为它回答了主要问题。我添加了自己的答案以展示我的解决方法。 - Chrisgbk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接