【问题标题】:TListView in virtual mode limited to 100,000,000 items?虚拟模式下的 TListView 限制为 100,000,000 个项目?
【发布时间】:2014-02-06 11:28:25
【问题描述】:

将 ListView 中的 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 不会引发异常 - 它只是默默地破坏了一切。

目前,我正在通过将列表视图保持在虚拟模式之外来解决这个问题,根据添加到列表中的控件的大小(即:VisibleRowCount 属性)准确地保持可见的项目数,维护一个偏移到我的数据中,并循环遍历列表中的项目以填充列表,其方式与虚拟模式列表基本相同,使用我自己的滚动条来控制偏移,以实际使限制达到约 20 亿。

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

【问题讨论】:

  • 为什么VirtualTreeView不在listview模式下?
  • 我尝试过使用 VirtualTreeView,但缺乏正确使用它的经验 - 虽然它很快,但从我目前所做的来看,它为列表中的每个项目分配了一个小结构,所以我遇到了大约 4000 万个项目标记的内存问题。链接到使用它的教程,以便它像虚拟列表视图一样工作?
  • @Chrisgbk:只是好奇:用户将如何浏览包含 10^8 个项目的列表视图?
  • 好的,这对我来说更有意义。坦率地说,我认为我会为此选择一个完整的自定义控件,而不是从列表视图中挤出意义。很有意思。我喜欢这个问题!

标签: windows delphi listview windows-7 delphi-xe4


【解决方案1】:

这是我的新解决方法,它将虚拟 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 处理。
【解决方案2】:

这确实出现了底层控件的限制。您将需要更改您的 UI 设计以避免限制,或者找到不同的控件。也就是说,我怀疑有多少控件可以有效地显示 1 亿个项目。

【讨论】:

  • 我已将此标记为答案,因为它回答了主要问题。我添加了自己的答案来展示我的解决方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-18
  • 2014-10-17
相关资源
最近更新 更多