Switching from ListView to VirtualStringTree
I am trying to build my projects with a VirtualStringTree rather than a Listview, because of the vast speed difference. The thing is, even after looking thru the demo's, I just can't figure out exactly how I would use it as a ListView. Like, adding, deleting, and basically just working with ListView items is so easy, but when I look at the VT, it gets almost too complicated.
All I am looking for, is a VT that looks like a ListView, with subitems etc.
Here are some routines using the ListView, that I would like to use with VT (This is just a pseudo example:
procedure Add;
begin
with ListView.Items.Add do
Begin
Caption := EditCaption.Text;
SubItems.Add(EditSubItem.Text开发者_开发技巧):
End;
end;
Procedure ReadItem(I : Integer);
begin
ShowMessage(ListView.Items[I].Caption);
ShowMessage(ListView.Items[I].SubItems[0]);
end;
Of course, also the Delete function, but since thats like 1 line, I didnt bother :P
Could anyone maybe translate the above examples into using a ListView style VT?
Thanks!
Why don't you use a list view in virtual mode? That will look and feel right and perform great.
The Delphi TListView control is a wrapper around the Windows list view component. In its default mode of operation copies of the list data are transferred from your app to the Windows control and this is slow.
The alternative to this is known as a virtual list view in Windows terminology. Your app doesn't pass the data to the Windows control. Instead, when the control needs to display data it asks your app for just the data that is needed.
The Delphi TListView control exposes virtual list views by use of the OwnerData property. You'll have to re-write your list view code somewhat but it's not too hard.
I also offer a link to another question here that covered similar ground. Rather oddly, the accepted answer for that question talked about list boxes even though the question was about list view controls.
with VirtualStringTree it's a bit more complex than the simple TListView, however here's a very simple tutorial that I've created a little while back on how to use VirtualStringTree http://www.youtube.com/watch?v=o6FpUJhEeoY I hope it helps, cheers!
Just use your normal TListView, but use it in virtual mode.
It's really simple:
- Set the
OwnerData
property totrue
- Implement the
OnData
event handler.
Sample implementation that shows a simple list of 3 rows:
Type TMyItem=record
Item:String;
SubItem:String;
end;
var Items:Array of TMyItem;
// set up some in-memory dataset.. choose your own layout
SetLength(Items,3);
Items[0].Item := 'foo1';
Items[0].SubItem := 'bar1';
Items[1].Item := 'foo2';
Items[1].SubItem := 'bar2';
Items[2].Item := 'foo3';
Items[2].SubItem := 'bar3';
// tell ListView1 how many items there are
ListView1.Items.Count := Length(Items);
procedure TfrmMain.ListView1Data(Sender: TObject; Item: TListItem);
begin
Item.Caption := IntToStr(Item.Index);
Item.SubItems.Add( MyArray[Item.Index] );
Item.SubItems.Add( UpperCase(MyArray[Item.Index]) );
end;
// Updating a value:
Items[1].Item := 'bzzz';
ListView1.Update;
That's all!
Some things to keep in mind:
- You don't call ListView1.Items.Add() anymore.
- You need to keep your own list of data somewhere in memory, or come up with the data in real-time, so you cannot 'store' data in the listview any longer.
- You need to set the items.count property, or you won't see anything.
- Call ListView1.Update() if something changes.
procedure Add;
Var
Data: PLogData;
XNode: PVirtualNode;
begin
with vst do
Begin
XNode := AddChild(nil);
ValidateNode(XNode, False);
Data := GetNodeData(Xnode);
Data^.Name:= EditCaption.Text;
Data^.Msg := EditSubItem.Text;
End;
end;
Procedure ReadItem(I : Integer);
var
Data: PLogData;
begin
if not Assigned(vst.FocusedNode) then Exit;
Data := vst.GetNodeData(vst.FocusedNode);
ShowMessage(Data^.Name);
ShowMessage(Data^.Msg);
end;
Basically that is what you need to do, but the VirtualStringTree has/needs alot of other things working together to fully understand it. And once you "get it" the VST is easy and powerful. The following webpage will help you: http://wiki.freepascal.org/VirtualTreeview_Example_for_Lazarus
and below I will add more code I use for a simple VST Log display. I keep all the code in datamodule, just use the procedure Log to display information and change your FormMain.vstLog to yours...
unit udmVstLog;
interface
uses
SysUtils, Windows, Forms, Classes, Graphics,
VirtualTrees, ActnList, Dialogs, ExtDlgs;
type
PLogData = ^TLogData;
TLogData = record
IsErr : Boolean;
Name: String;
Msg : String;
end;
type
TdmVstLog = class(TDataModule)
actlst1: TActionList;
actClear: TAction;
actSave: TAction;
actCopyLine2Mem: TAction;
sdlgLog: TSaveTextFileDialog;
procedure DataModuleCreate(Sender: TObject);
procedure actClearExecute(Sender: TObject);
procedure actSaveExecute(Sender: TObject);
procedure actCopyLine2MemExecute(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
procedure VSTPaintText(Sender: TBaseVirtualTree;
const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
TextType: TVSTTextType);
end;
procedure Log(aIsErr: Boolean; AName, AMsg: string); overload;
procedure Log(AName, AMsg: string); overload;
procedure Log(AMsg: string); overload;
var
dmVstLog: TdmVstLog;
implementation
uses uFormMain, ClipBrd;
{$R *.dfm}
procedure Log(aIsErr: Boolean; AName, AMsg: string);
Var
Data: PLogData;
XNode: PVirtualNode;
begin
XNode:=FormMain.vstLog.AddChild(nil);
FormMain.vstLog.ValidateNode(XNode, False);
Data := FormMain.vstLog.GetNodeData(Xnode);
Data^.IsErr := aIsErr;
if aIsErr then
Data^.Name:= DateTimeToStr(Now) + ' ERROR ' + AName
else
Data^.Name:= DateTimeToStr(Now) + ' INFO ' + AName;
Data^.Msg:= AMsg;
end;
procedure Log(AName, AMsg: string);
begin
Log(False,AName,AMsg);
end;
procedure Log(AMsg: string);
begin
Log(False,'',AMsg);
end;
// VirtualStringTree Events defined here
procedure TdmVstLog.VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
Data: PLogData;
begin
Data:=Sender.GetNodeData(Node);
Finalize(Data^);
end;
procedure TdmVstLog.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var
Data: PLogData;
begin
Data := Sender.GetNodeData(Node);
case Column of
0: CellText := Data^.Name + ' - '+ Data^.Msg;
end;
end;
procedure TdmVstLog.VSTPaintText(Sender: TBaseVirtualTree;
const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
TextType: TVSTTextType);
Var
Data: PLogData;
begin
Data := Sender.GetNodeData(Node);
if Data^.IsErr then
TargetCanvas.Font.Color:=clRed;
end;
//PopUpMenu Actions defined here!
procedure TdmVstLog.actClearExecute(Sender: TObject);
begin
FormMain.vstLog.Clear;
end;
procedure TdmVstLog.actCopyLine2MemExecute(Sender: TObject);
var
Data: PLogData;
begin
if not Assigned(FormMain.vstLog.FocusedNode) then Exit;
Data := FormMain.vstLog.GetNodeData(FormMain.vstLog.FocusedNode);
ClipBoard.AsText := Data^.Name + ' - ' + Data^.Msg;
end;
procedure TdmVstLog.actSaveExecute(Sender: TObject);
Var
XNode: PVirtualNode;
Data: PLogData;
ts: TStringList;
begin
If FormMain.vstLog.GetFirst = nil then Exit;
XNode:=nil;
if sdlgLog.Execute then begin
ts:= TStringList.create;
try
Repeat
if XNode = nil then XNode:=FormMain.vstLog.GetFirst Else XNode:=FormMain.vstLog.GetNext(XNode);
Data:=FormMain.vstLog.GetNodeData(XNode);
ts.Add(Data^.Name + ' - '+ Data^.Msg);
Until XNode = FormMain.vstLog.GetLast();
ts.SaveToFile(sdlgLog.FileName);
finally
ts.Free;
end;
end;
end;
// Datamodule Events defined here
procedure TdmVstLog.DataModuleCreate(Sender: TObject);
begin
with FormMain.vstLog do begin
NodeDataSize := SizeOf(TLogData);
OnFreeNode := VSTFreeNode;
OnGetText := VSTGetText;
OnPaintText := VSTPaintText;
end;
end;
end.
...
procedure RemoveSelectedNodes(vst:TVirtualStringTree);
begin
if vst.SelectedCount = 0 then Exit;
vst.BeginUpdate;
vst.DeleteSelectedNodes;
vst.EndUpdate;
end;
procedure RemoveAllNodes(vst:TVirtualStringTree);
begin
vst.BeginUpdate;
vst.Clear;
vst.EndUpdate;
end;
Get the VT Contributions pack and check out some of the descendants of virtual string tree. That are in there. I haven't used them in projects, but they seem to make Virtual String Tree easier to use.
Here's my getting started primer nonetheless:
I've found after using Virtual String Tree quite a bit that the only way you can make the most of it is by implementing the init node/child functions and setting the root node count, much the same as you would a list view with ownerdraw := true.
It's pretty easy to do stuff with VirtualStringTree, you just need to implement the get text function and the node size functions (set it equal to the size of whatever record you'd like to use as the data behind your tree)
I've found it's almost always easier to do
TVirtualTreeNodeRecordData = record
Data : TVirtualTreeNodeData;
end
and create the data object on the init functions. It creates the pointers for you, but you need to free the objects (again, use another delete node callback).
精彩评论