Link to home
Start Free TrialLog in
Avatar of hall2000
hall2000

asked on

Save a Tlistview

Hello,

I have a Tlistview with 4 columns, I need to save it to a file. and load it when the app loads again.

what I did is create 4 stringlists and save the values of each column in a stringlist, and then save those stringlists to a file. The only problem is that when I have 1000's of items on the listview it gets slow and takes a while to save it.

Is there any better way to do this?

Avatar of Martyn Spencer
Martyn Spencer
Flag of United Kingdom of Great Britain and Northern Ireland image

One little suggestion, which may help, is that if you are referencing a node within the view many times, it will assist if you assign it to a holding variable. I have done something similar to you, but found that when referring to nodes in the treeview, it is very slow with large trees when you refrence by index. So, rather than:

 while i < ListView1.Items.count do begin
   // lots of references to ListView1[i]
 end;

try:

 var
   li: TListItem;

 while i < ListView1.Items.count do begin
   li := ListView1.Items[i];
   // lots of references to li
 end;

When I did this, I found that the manipulation processing speed was increased dramatically. Basically, it avoids the node list from being accessed (in what appears to be sequential order) each time you wish to process a node.

HTH.
In the first para, for treeview, read listview...
Avatar of hall2000
hall2000

ASKER

But it still uses the loop that is making it slow... I am trying to eliminate the loop (if posible). thanks.
I doubt you will avoid the loop, and I doubt that it is the loop that is causing the delay. If you try getting Delphi to iterate through a loop of 100,000, I doubt you will see it take much time at all. I would suggest that it is the accessing of the items in the list view or the writing out to disk.

To illustrate the access issue I mentioned earlier, think about your listview having 10,000 items. Now, if your loop is accessing elements from the listview as in the first example, if you use the listview[i] notation, each time you reference the element, Delphi, behind the scenes is going to search through the treeview for the element you require. So, for element 1, you get 1 node access. For element 2, you get 1 (the first access in your loop) + 2 (the 2 because Delphi has had to scan item 1 again). For element 3, you will get 1 (your first iteration) + 2 (your second iteration) + 3 (delphi). You are therefore iterating through the list by a factorial value of the number of elements. Now, if you reference the item list three or four times in your loop, imagine the work that is going on...

My figures may not be precise, but safe to say that each time you reference the items array using an index, it takes an increasing amount of time for larger lists. My suggestion effectively 'caches' the element you are working on and does improve performance measurably. My findings are based on having spent time looking into what was causing delays in writing a treeview out to disk.

How about pasting the code in your loop so we can take a look?

Have you considered using the list view in virtual mode?

I have found that once you get above ~1000 or so items in the list view, adding, clearing, etc. can be very time consuming. In many cases I have gone from a 10 sec load time down to 10-20 ms.

The nice part is, very little is required to run this way:

1.) The ListView.OwnerData must be set to true, and you must handle the OnData event (I will provide an example), and optionally the OnDataFind event.
 
1.) You must provide the data backing for the list view. In your case, you already create the TStringLists for loading and saving. So why not keep them around as the data backing?

2.) Instead of adding data to the list view (though items), you would add data to your stringlists, and then set the ListView.Item.Count to reflect the number of rows of data that you are backing. (ListView.Item.Count:=StringList.Count)

For example;

var
 DataCols:   Array [0..3] of StringList;

// OnData event for list view control
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin

  // Make sure Item is in range
  if (Item.Index >= DataCols[0].Count) then Exit;

  // Set the list item properties
  Item.Caption:=DataCols[0][Item.Index];
  Item.SubItems.Add(DataCols[1][Item.Index]);
  Item.SubItems.Add(DataCols[2][Item.Index]);
  Item.SubItems.Add(DataCols[3][Item.Index]);

end;

// OnDataFind event for list view control
procedure TForm1.ListViewDataFind(Sender: TObject; Find: TItemFind; const FindString: String; const FindPosition: TPoint; FindData: Pointer;
           StartIndex: Integer; Direction: TSearchDirection; Wrap: Boolean; var Index: Integer);
var
  i:             Integer;
  Found:         Boolean;
begin

  i:=StartIndex;
  if (Find = ifExactString) or (Find = ifPartialString) then
  begin
     repeat
        if (i = DataCols[0].Count-1) then
           if Wrap then i:= 0 else Exit;
        Found:=Pos(UpperCase(FindString), UpperCase(DataCols[0][i])) = 1;
        Inc(i);
    until Found or (i = StartIndex);
    if Found then Index:=i-1;
  end;

end;

-----

If you would like to see a more in-depth example, let me know.

Russell

Hi
 I think this is the code that solve your problem best. So try this one.

//-----------------------------
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    ListView1: TListView;
    LoadFromFile: TButton;
    SaveToFile: TButton;
    procedure FormCreate(Sender: TObject);
    procedure LoadFromFileClick(Sender: TObject);
    procedure SaveToFileClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.LoadFromFileClick(Sender: TObject);
var
  S, S1 : TStringList;
  i, j : Integer;
  LI : TListItem;
begin
S := TStringList.Create;
S1 := TStringList.Create;
S.LoadFromFile('Test2.txt');
For i:=0 to S.Count-1 do
  begin
  S1.CommaText := S.Strings[i];
  LI := ListView1.Items.Add;
  for j:=0 to S1.Count-1 do if j=0 then LI.Caption := S1.Strings[j] else LI.SubItems.Add(S1.Strings[j]);
  S1.Clear;
  end;
S1.Free;
S.Free;
end;

procedure TForm1.SaveToFileClick(Sender: TObject);
var
  S : TStringList;
  i : Integer;
begin
S := TStringList.Create;
for i:=0 to ListView1.Items.Count-1 do
  begin
  S.Add(ListView1.Items.Item[0].Caption + ','+ListView1.Items.Item[0].SubItems.CommaText)
  end;
S.SaveToFile('Test2.txt');
FreeAndNil(S);
end;

end.
//-----------------------------

With Best Regards
Fareed.
ASKER CERTIFIED SOLUTION
Avatar of Fareed Ali Khan
Fareed Ali Khan
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial