重置非
我有一个应用程序从二进制日志文件加载记录,并将它们显示在虚拟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(当日志显示未经过滤时),树的节点内部开销将占用高达260MB的内存(尚未分配任何记录)。 而使用TListView,加载相同的日志文件及其背后的所有内存逻辑,我可以轻松使用几MB。
有任何想法吗?
你可能不应该切换到VST,除非你至少使用了标准列表框/列表视图没有的VST的一些很好的功能。 但是,与平面项目列表相比,当然存在较大的内存开销。
我没有看到使用TVirtualStringTree
仅能够展开和折叠多行项目的实际好处。 你写
主要是因为我想添加扩展/折叠跨越多行的记录的能力(我可以通过动态增加/减少项目计数来使其与TListView密切相关,但这不像使用真正的树那样直截了当)。
但您可以轻松实施,而无需更改项目数量。 如果将列表框的Style
设置为lbOwnerDrawVariable
并实现OnMeasureItem
事件,则可以根据需要调整高度以仅绘制第一行或所有行。 手动绘制扩展器三角形或树形视图的小加号应该很容易。 可以使用Windows API函数DrawText()
或DrawTextEx()
来测量和绘制(可以是文字包装的)文本。
编辑:
对不起,我完全错过了你现在使用listview而不是列表框的事实。 事实上,没有办法在列表视图中拥有不同高度的行,所以这是不行的。 您仍然可以在顶部使用带有标准标题控件的列表框,但是这可能不支持您现在使用的所有列表视图功能中的所有内容,并且它本身可能比动态显示和隐藏列表视图行更适合甚至更多工作。模拟崩溃和扩大。
如果我理解正确, TVirtualStringTree
的内存要求应该是:
nodecount *(SizeOf(TVirtualNode)+ YourNodeDataSize + DWORD-align-padding)
为了最大限度地减少内存占用量,您可以使用指向偏移内存映射文件的指针初始化节点。 在这种情况下,重置已经初始化的节点似乎不是必需的 - 对于500万条记录,内存占用空间应该是nodecount *(44 + 4 + 0),大约为230 MB。
恕我直言,你不能得到任何更好的树,但使用内存映射文件将允许您直接从文件读取数据,而不分配更多的内存并将数据复制到它。
您也可以考虑使用树结构而不是平面视图来呈现数据。 这样,您可以根据需要初始化父节点的子节点(当父节点展开时),并在父节点折叠(因此释放其所有子节点)时重置父节点。 换句话说,尽量不要在同一级别有太多的节点。
为了满足您的要求,“扩大/折叠跨越多行的记录”,我只需使用拉格。 要检查出来,将一个drawgrid拖到一个表单上,然后插入下面的Delphi 6代码。 您可以折叠并展开5,000,000多行记录(或任何您想要的数量),而且基本没有开销。 这是一种简单的技术,不需要太多的代码,而且工作起来非常好。
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.
链接地址: http://www.djcxy.com/p/901.html
上一篇: resetting non
下一篇: Ignoring "Content is not allowed in trailing section" SAXException