Switching from ListView to VirtualStringTree
Asked Answered
D

5

9

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!

Decennium answered 3/1, 2011 at 17:38 Comment(0)
M
4
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;
Macule answered 3/1, 2011 at 20:46 Comment(4)
Though your code could do with some try finally's and I hate the with statement, +1 for actually answering the question.Otha
I dug up this code, it was from one of my first projects I created when the VirtualStringTree component finally "clicked" and I understood it. Far from perfect, but I understand what the poster is going thru. There is a lot to learn from the component, and I am far from an expert on it... oh and I try to stay away from WITH statements too but I tried to use his code as a templateMacule
Yeah, I know, I went through the same process. Did dig up an example, but it was way too embedded in a framework to post here.Otha
This answered my question, but may I ask, how exactly does that "record" work? I havent understood what the "^" does, either, as I cant seem to find an article explaining it. A crash course on the code specified would be nice aswell, like "Data^."? :) Thanks :)Decennium
P
11

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.

Percentile answered 3/1, 2011 at 17:53 Comment(14)
+1. I agree. Most often there is no need to abandon the native controls. The Microsoft Windows operating system is a very mature product.Glaring
I also agree that it is best to use the native controls if speed is NOT an issue, otherwise VirtualStringTree is your best mate (:Eastern
@dorin isn't a virtual list view as fast as you need?Percentile
@david frankly I haven't used list view in virtual mode ever, I prefer VirtualStringTree because it's very flexible, however I generally prefer using standard controls over third party for future compatibility, but I rarely ignore that over productivity...Eastern
@Dorin I've just written my first ever virtual list view! It displays 10 million rows effortlessly, which frankly is exactly as I would expect. Anyway, I'm sure VirtualStringTree is good but I don't think there is a performance issue with TListView in virtual mode.Percentile
@david the question is: will it blend? -- jokin', 10 mlo. is nice, but it will actually draw only the visible items actually... VST has more features that can be used effortless than LV -- I rest my case stating that VST is the king if you need nice features like colors, drag'n'drop, etc. out of the box with little or none effort.Eastern
@Dorin I'm not arguing with that, but you mentioned speed as the factor in favour of VST, and I quote, "if speed is NOT an issue". Given that the OP appears already to be using list view, it seems plausible that virtual list view might be acceptable. Anyway, I'm not against your answer, it's good too, and the OP can make his own choice as he sees fit.Percentile
As far as I have read, using it in Virtual Mode is kinda hard, when it comes to managing the list (adding, editing, deleting)?Decennium
@jeff not really in my view. It's similar to virtual string tree.Percentile
@David - I looked at Wouters comment about the Listview in Virtual mode, and he states that I wont use the Add procedure anymore, well how do I add items then?Decennium
@Decennium Typically you tell the list view how many items there are and then handle an event called OnData. In OnData you are passed a TListItem and you set it's Caption property and add to its SubItems property. I'm thinking of a list view in report mode which is what I presume you have.Percentile
@David Yeah, but I am already getting familiar with the VST now, it has so many options! By the way, what did you mean with that comment on my other question, the VirtualStringTree Drawing question?Decennium
@Decennium You've only asked one question, this one. Or perhaps you meant the question you asked as Jeff Hansen (#4658415). I was poking just a little fun in your direction because you seem to be having trouble sticking to just a single SO user account.Percentile
@David Yeah, the problem was that I didnt register when asking this question, and when I was at school, I asked the Drawing question, and when I got home I registered this one.Decennium
E
6

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!

Eastern answered 3/1, 2011 at 18:34 Comment(1)
Youtube link is deadMarybellemarybeth
P
5

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.
Prettify answered 4/1, 2011 at 2:6 Comment(1)
Instead of ListView1.Update, Listview1.Repaint worked for me in Delphi 10.3.3. Thank you very much for this helpful answer.Marybellemarybeth
M
4
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;
Macule answered 3/1, 2011 at 20:46 Comment(4)
Though your code could do with some try finally's and I hate the with statement, +1 for actually answering the question.Otha
I dug up this code, it was from one of my first projects I created when the VirtualStringTree component finally "clicked" and I understood it. Far from perfect, but I understand what the poster is going thru. There is a lot to learn from the component, and I am far from an expert on it... oh and I try to stay away from WITH statements too but I tried to use his code as a templateMacule
Yeah, I know, I went through the same process. Did dig up an example, but it was way too embedded in a framework to post here.Otha
This answered my question, but may I ask, how exactly does that "record" work? I havent understood what the "^" does, either, as I cant seem to find an article explaining it. A crash course on the code specified would be nice aswell, like "Data^."? :) Thanks :)Decennium
I
1

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

Izzy answered 3/1, 2011 at 20:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.