Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/neo4j/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Delphi TVirtualStringTree-重置非可视节点和内存消耗_Delphi_C++builder_Vcl_Virtualtreeview - Fatal编程技术网

Delphi TVirtualStringTree-重置非可视节点和内存消耗

Delphi TVirtualStringTree-重置非可视节点和内存消耗,delphi,c++builder,vcl,virtualtreeview,Delphi,C++builder,Vcl,Virtualtreeview,我有一个应用程序,可以从二进制日志文件加载记录,并在虚拟TListView中显示它们。一个文件中可能有数百万条记录,用户可以对显示进行过滤,因此我不会一次加载内存中的所有记录,并且ListView项索引与文件记录偏移量不是1对1的关系(例如,列表项1可能是文件记录100)。我使用ListView的OnDataHint事件加载ListView实际感兴趣的项目的记录。当用户滚动时,OnDataHint指定的范围发生变化,允许我释放不在新范围内的记录,并根据需要分配新记录 这工作正常,速度可以接受,内

我有一个应用程序,可以从二进制日志文件加载记录,并在虚拟TListView中显示它们。一个文件中可能有数百万条记录,用户可以对显示进行过滤,因此我不会一次加载内存中的所有记录,并且ListView项索引与文件记录偏移量不是1对1的关系(例如,列表项1可能是文件记录100)。我使用ListView的OnDataHint事件加载ListView实际感兴趣的项目的记录。当用户滚动时,OnDataHint指定的范围发生变化,允许我释放不在新范围内的记录,并根据需要分配新记录

这工作正常,速度可以接受,内存占用非常低

我目前正在评估TVirtualStringTree作为TListView的替代品,主要是因为我想添加扩展/折叠跨多行记录的功能(我可以通过动态增加/减少项目计数来使用TListView,但这并不像使用真正的树那样简单)

在大多数情况下,我已经能够移植TListView逻辑,并使所有内容都能按需要工作。不过,我注意到TVirtualStringTree的虚拟范例大不相同。它没有与TListView相同的OnDataHint功能(我可以使用OnScroll事件来伪造它,这允许我的内存缓冲区逻辑继续工作),并且我可以使用OnInitializeNode事件将节点与分配的记录相关联

但是,一旦树节点被初始化,它就会看到它在树的生存期内保持初始化状态。那对我不好。当用户滚动并从内存中删除记录时,我需要重置这些非可视节点,而不会完全从树中删除它们,也不会丢失它们的展开/折叠状态。当用户将它们卷回视图时,我可以重新分配记录并重新初始化节点。基本上,就虚拟化而言,我想让TVirtualStringTree尽可能像TListView一样工作

我已经看到TVirtualStringTree有一个ResetNode()方法,但每当我尝试使用它时,都会遇到各种错误。我一定是用错了。我还考虑在每个节点中存储一个指向记录缓冲区的数据指针,然后分配和释放内存,相应地更新这些指针。最终效果也不是很好

更糟糕的是,我最大的测试日志文件中有大约500万条记录。如果我一次用那么多节点初始化TVirtualStringTree(当日志显示未过滤时),该树节点的内部开销将占用高达260 MB的内存(尚未分配任何记录)。而使用TListView,加载相同的日志文件和它背后的所有内存逻辑,我只需使用几MB就可以解决问题


有什么想法吗?

您不应该使用ResetNode,因为此方法调用InvalidateNode并再次初始化节点,从而产生与预期相反的效果。
我不知道在不实际删除节点的情况下,是否有可能使VST释放NodeDataSize中指定的内存大小。但为什么不将NodeDataSize设置为指针()的大小并自己管理数据呢?只是一个想法…

如果我理解正确,TVirtualStringTree的内存需求应该是:

nodecount*(SizeOf(TVirtualNode)+YourNodeDataSize+DWORD对齐填充)

为了最小化内存占用,您可以只使用指向内存映射文件偏移量的指针初始化节点。在这种情况下,重置已经初始化的节点似乎没有必要——内存占用应该是nodecount*(44+4+0)——对于500万条记录,大约230MB

我想,使用该树没有更好的方法,但是使用内存映射文件将允许您直接从该文件读取数据,而无需分配更多内存并将数据复制到该文件中


也可以考虑使用树形结构而不是平面视图来呈现数据。这样,您可以根据需要初始化父节点的子节点(当父节点展开时),并在父节点折叠时重置父节点(从而释放其所有子节点)。换句话说,尽量不要在同一级别上有太多的节点。

除非您至少使用了VST的一些标准listbox/listview所没有的优秀功能,否则您可能不应该切换到VST。但是与一个简单的项目列表相比,当然会有很大的内存开销

我看不到使用
TVirtualStringTree
只能够扩展和折叠跨多行的项的真正好处。你写

主要是因为我想添加扩展/折叠跨多行记录的功能(我可以通过动态增加/减少项目计数来使用TListView对其进行篡改,但这并不像使用真正的树那样简单)

但是,您可以轻松实现这一点,而无需更改项目计数。如果将列表框的
样式设置为
lbOwnerDrawVariable
,并执行
OnMeasureItem
事件,则可以根据需要调整高度,以便仅绘制第一行或所有行。手动绘制展开三角形或树视图的小加号应该很容易。Windows API函数或可用于测量和绘制(可选文字包装)文本

编辑:

抱歉,我完全没有注意到您现在使用的是listview,而不是listbox。事实上,在listview中没有办法拥有不同高度的行,因此这是没有选择的。您仍然可以使用顶部带有标准标题控件的列表框,但这可能不支持您现在从listview功能中使用的所有内容,而且它本身可能需要做同样多甚至更多的工作才能获得ri
// Removes all children and their children from memory without changing the vsHasChildren style by default.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Grids, StdCtrls;

type
  TForm1 = class(TForm)
    DrawGrid1: TDrawGrid;
    procedure DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
    procedure DrawGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
    procedure DrawGrid1TopLeftChanged(Sender: TObject);
    procedure DrawGrid1DblClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    procedure AdjustGrid;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// Display a large number of multi-line records that can be expanded or collapsed, using minimal overhead.
// LinesInThisRecord() and RecordContents() are faked; change them to return actual data.

const TOTALRECORDS = 5000000; // arbitrary; a production implementation would probably determine this at run time

// keep track of whether each record is expanded or collapsed
var isExpanded: packed array[1..TOTALRECORDS] of boolean; // initially all FALSE

function LinesInThisRecord(const RecNum: integer): integer;
begin // how many lines (rows) does the record need to display when expanded?
result := (RecNum mod 10) + 1; // make something up, so we don't have to use real data just for this demo
end;

function LinesDisplayedForRecord(const RecNum: integer): integer;
begin // how many lines (rows) of info are we currently displaying for the given record?
if isExpanded[RecNum] then result := LinesInThisRecord(RecNum) // all lines show when expanded
else result := 1; // show only 1 row when collapsed
end;

procedure GridRowToRecordAndLine(const RowNum: integer; var RecNum, LineNum: integer);
var LinesAbove: integer;
begin // for a given row number in the drawgrid, return the record and line numbers that appear in that row
RecNum := Form1.DrawGrid1.TopRow; // for simplicity, TopRow always displays the record with that same number
if RecNum > TOTALRECORDS then RecNum := 0; // avoid overflow
LinesAbove := 0;
while (RecNum > 0) and ((LinesDisplayedForRecord(RecNum) + LinesAbove) < (RowNum - Form1.DrawGrid1.TopRow + 1)) do
  begin // accumulate the tally of lines in expanded or collapsed records until we reach the row of interest
  inc(LinesAbove, LinesDisplayedForRecord(RecNum));
  inc(RecNum); if RecNum > TOTALRECORDS then RecNum := 0; // avoid overflow
  end;
LineNum := RowNum - Form1.DrawGrid1.TopRow + 1 - LinesAbove;
end;

function RecordContents(const RowNum: integer): string;
var RecNum, LineNum: integer;
begin // display the data that goes in the grid row.  for now, fake it
GridRowToRecordAndLine(RowNum, RecNum, LineNum); // convert row number to record and line numbers
if RecNum = 0 then result := '' // out of range
else
  begin
  result := 'Record ' + IntToStr(RecNum);
  if isExpanded[RecNum] then // show line counts too
    result := result + ' line ' + IntToStr(LineNum) + ' of ' + IntToStr(LinesInThisRecord(RecNum));
  end;
end;

procedure TForm1.AdjustGrid;
begin // don't allow scrolling past last record
if DrawGrid1.TopRow > TOTALRECORDS then DrawGrid1.TopRow := TOTALRECORDS;
if RecordContents(DrawGrid1.Selection.Top) = '' then // move selection back on to a valid cell
  DrawGrid1.Selection := TGridRect(Rect(0, TOTALRECORDS, 0, TOTALRECORDS));
DrawGrid1.Refresh;
end;

procedure TForm1.DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var s: string;
begin // time to draw one of the grid cells
if ARow = 0 then s := 'Data' // we're in the top row, get the heading for the column
else s := RecordContents(ARow); // painting a record, get the data for this cell from the appropriate record
// draw the data in the cell
ExtTextOut(DrawGrid1.Canvas.Handle, Rect.Left, Rect.Top, ETO_CLIPPED or ETO_OPAQUE, @Rect, pchar(s), length(s), nil);
end;

procedure TForm1.DrawGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
var RecNum, ignore: integer;
begin
GridRowToRecordAndLine(ARow, RecNum, ignore); // convert selected row number to record number
CanSelect := RecNum <> 0; // don't select unoccupied rows
end;

procedure TForm1.DrawGrid1TopLeftChanged(Sender: TObject);
begin
AdjustGrid; // keep last page looking good
end;

procedure TForm1.DrawGrid1DblClick(Sender: TObject);
var RecNum, ignore, delta: integer;
begin // expand or collapse the currently selected record
GridRowToRecordAndLine(DrawGrid1.Selection.Top, RecNum, ignore); // convert selected row number to record number
isExpanded[RecNum] := not isExpanded[RecNum]; // mark record as expanded or collapsed; subsequent records might change their position in the grid
delta := LinesInThisRecord(RecNum) - 1; // amount we grew or shrank (-1 since record already occupied 1 line)
if isExpanded[RecNum] then // just grew
else delta := -delta; // just shrank
DrawGrid1.RowCount := DrawGrid1.RowCount + delta; // keep rowcount in sync
AdjustGrid; // keep last page looking good
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
Caption := FormatFloat('#,##0 records', TOTALRECORDS);
DrawGrid1.RowCount := TOTALRECORDS + 1; // +1 for column heading
DrawGrid1.ColCount := 1;
DrawGrid1.DefaultColWidth := 300; // arbitrary
DrawGrid1.DefaultRowHeight := 12; // arbitrary
DrawGrid1.Options := DrawGrid1.Options - [goVertLine, goHorzLine, goRangeSelect] + [goDrawFocusSelected, goThumbTracking]; // change some defaults
end;

end.