Question

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!

Was it helpful?

Solution

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;

OTHER TIPS

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:

  1. Set the OwnerData property to true
  2. 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:

  1. You don't call ListView1.Items.Add() anymore.
  2. 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.
  3. You need to set the items.count property, or you won't see anything.
  4. Call ListView1.Update() if something changes.

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).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top