Recent

Author Topic: TListView Problems with delete(Solved)  (Read 1720 times)

lucamar

  • Hero Member
  • *****
  • Posts: 2397
Re: TListView Problems with delete
« Reply #15 on: April 06, 2019, 12:36:34 am »
SearchResult :=  ListView1.items.indexof('SearchString');

I can;t get the indexof to work either.

The parameter of TListItems.IndexOf() is a TListItem but you're passing it a string. Try instead with TListItems.FindCaption(). For example (untested!):

Code: Pascal  [Select]
  1. AListItem := ListView1.Items.FindCaption(0, 'SearchString', True, True, True);
  2. if Assigned(AListItem) then
  3.   SearchResult := ListView1.items.IndexOf(AListItem)
  4. else
  5.   SearchResult := -1;
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.4/2.0.6  - FPC 3.0.4 on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

wp

  • Hero Member
  • *****
  • Posts: 6718
Re: TListView Problems with delete
« Reply #16 on: April 06, 2019, 01:19:33 am »
There are two issues in your code of the first post which lead to the crash when deleting list item #3:

First of all, you want to practice working with TListView but you begin at the more difficult end, the virtual mode. In "normal mode" (I don't know if this is the correct mode), you "Add" items to the Items collection of the ListView, i.e. the list items exist within the listview, and the listview can delete them by calling ListView.Items.Delete(index) like you do. In this case also you do not set the Count of the Listview's items because Count grows with every item added.

In virtual mode, on the other hand, the data displayed in the listview are not stored within the listview itself but somewhere else, in your case in an array FData of TDataItem records. Because the listview does not know anything about the data you first tell it how many records are to be displayed - this is where you set ListView.Items.Count to the number of records in the file.Then you must write an event handler for OnData which populates a dummy TListItem provided with the data from your own structure. This listitem is processed by the ListView as if it were an item of "normal" mode although it is not stored within the ListView. You cannot delete this "virtual" item by calling ListView.Items.Delete(index) because it never was allocated this way. Instead, you must delete the item in your own data structure, the FData array, and decrement the ListView.Items.Count:

Code: Pascal  [Select]
  1. // Delete an item from the FData array
  2. procedure TForm1.DeleteDataIndex(AIndex: Integer);
  3. var
  4.   i, n: Integer;
  5. begin
  6.   if (AIndex >= 0) and (AIndex < Length(FData)) then begin
  7.      n := Length(FData);
  8.     for i := AIndex to High(FData) - 1 do
  9.       FData[i] := FData[i+1];
  10.     SetLength(FData, n-1);    // <---
  11.   end;
  12. end;
  13.  
  14. // Delete an item from the FData array and update the listview
  15. procedure TForm1.btnDeleteClick(Sender: TObject);
  16. begin
  17.   DeleteDataIndex(3);
  18.   ListView1.Items.Count := ListView1.Items.COunt - 1;
  19.   ListView1.Invalidate;
  20. end;

Having fixed this, the program still crashes, now in the line marked above by "<---". This is due to an allocation error: You have a routine RcdCount(Const aPath : string) which counts how many records are contained in the data file. Then you use the result, TOTRCD, to set the length of the FData array to TOTRCD-1. But later when you read the data you read all records and write to unallocated memory for the last record because you did not Setlength(FData, TOTRCD). After removing the -1 here, the deletion works correctly.

Finally an idea for an improvement: you read the data file twice, the first time for counting the items so that you can set the length of the array, and the second time for getting the data into the array. This is not necessary: call SetLength during reading. Depending on the size of the file, I'd recommend to SetLength to some given block size and count the items during reading. When the counter indicates that the block is full call SetLength again and increase the array by another block size - internally the array is copied to a new location, nothing is lost. At the end use the counter value to SetLength to the correct number or records. The following code does this, do not call RcdCount at all:

Code: Pascal  [Select]
  1. procedure TForm1.FormShow(Sender: TObject);
  2. var
  3.   t: TDateTime;
  4. begin
  5.   t := Now;
  6.   if LoadDatafromFile(C_FileName) then begin
  7.     t := Now - t;
  8.     ListView1.Items.Count := Length(FData);
  9.     ListView1.Invalidate;
  10.     Statusbar1.SimpleText := Format('%.0n items read in %s s', [
  11.     Length(FData)*1.0, FormatDateTime('s.zzz', t)]);
  12.   end else
  13.     StatusBar1.SimpleText := 'Error';
  14.     DefaultColumWidth;
  15. end;
  16.  
  17. function TForm1.LoadDataFromFile(const AFileName: String): Boolean;
  18. const
  19.   BLOCK_SIZE = 1000;  // prepare block for 1000 records
  20. var
  21.   DataFile : TextFile;
  22.   Line     : String;
  23.   Counter  : Integer = 0;
  24.   Bit1     : String;
  25.   p        : Integer;
  26.   Empty    : Boolean;
  27. begin
  28.   Result := false;
  29.   if not FileExists(AFileName) then begin MessageDlg(Format('File "%s" not found.', [AFileName]), mtError, [mbOK], 0); exit; end;
  30.  
  31.   SetLength(FData, 0);
  32.   AssignFile(DataFile, AFileName);
  33.   try
  34.     Reset(Datafile);
  35.     try
  36.       while not EoF(DataFile) do begin
  37.         ReadLn(DataFile, Line);
  38.  
  39.         // When the counter is 0, 1000, 2000, ... the preallocated array is full
  40.         // Extend its length for another 1000 records.
  41.         if Counter mod BLOCK_SIZE = 0 then
  42.           SetLength(FData, Length(FData) + BLOCK_SIZE);
  43.  
  44.         FData[Counter].LIDX := Counter;
  45.         Line := Trim(Line);
  46.  
  47. //        Bit1 := Copy2Space(Line);          {ICAO}
  48.         Bit1 := ExtractWord(1,Line,['|']);
  49.         FData[Counter].ICAO := Bit1;
  50.  
  51.         Bit1 := ExtractWord(5,Line,['|']);
  52.         Empty := IsEmptyStr(Bit1, [' ']);
  53.         if Empty then FData[Counter].City := 'Nil'
  54.         else FData[Counter].City := Bit1;
  55.  
  56.         Bit1 := ExtractWord(6,Line,['|']);
  57.         Empty := IsEmptyStr(Bit1, [' ']);
  58.         if Empty then FData[Counter].Country := 'Nil'
  59.         else FData[Counter].Country := Bit1;
  60.  
  61.  
  62.         Bit1 := ExtractWord(7,Line,['|']);
  63.         Empty := IsEmptyStr(Bit1, [' ']);
  64.         if Empty then FData[Counter].Region := 'Nil'
  65.         else FData[Counter].Region := Bit1;
  66.  
  67.         Bit1 := ExtractWord(3,Line,['|']);
  68.         FData[Counter].Lat := Bit1;
  69.  
  70.         Bit1 := ExtractWord(4,Line,['|']);
  71.         FData[Counter].Lon := Bit1;
  72.  
  73.         Bit1 := ExtractWord(2,Line,['|']);
  74.         FData[Counter].HASH := Bit1;
  75.  
  76.         Inc(Counter);
  77.       end;
  78.  
  79.       // Now that all records are read we know their count and we can trim
  80.       // the data array to its final length
  81.       SetLength(FData, Counter);
  82.  
  83.       Result := true;
  84.     except
  85.       SetLength(FData, 0);
  86.       MessageDlg(Format('Error reading file "%s"', [AFileName]), mtError, [mbOK], 0);
  87.     end;
  88.   finally
  89.     CloseFile(DataFile);
  90.   end;
  91. end;
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

JLWest

  • Hero Member
  • *****
  • Posts: 634
Re: TListView Problems with delete
« Reply #17 on: April 06, 2019, 02:41:59 am »
@ wp

I agree with all that you said and understand very little of it.

I'll try and implement what you have provided and see if I can get something to work.

"First of all, you want to practice working with TListView but you begin at the more difficult end, the virtual mode. In "normal mode" ".

I have no knowledge of the 'virtual Mode' or 'normal Mode'.

FPC 3.2.0, Lazarus IDE v2.0.4
 Windows 10 Pro 32-GB
 Intel i7 770K CPU 4.2GHz 32702MB Ram
GeForce GTX 1080 Graphics - 8 Gig
4.1 TB

lucamar

  • Hero Member
  • *****
  • Posts: 2397
Re: TListView Problems with delete
« Reply #18 on: April 06, 2019, 03:34:01 am »
Here is a small example of the ListView in normal mode, as it is just after adding it to a form. It tests:
  • Adding items to the list (from a simple text file);
  • Searching for items in the list;
  • Deleting items from the list.
Four keys are used:
  • Ctrl+Q - Exits the program
  • Ctrl-F - Jumps to the search box
  • Enter (or Tab) - In the search box, does the search;
  • Del - On the list view, deletes the selected item

It's not exactly production quality code but it'll give you an idea of how to work with a list view.

HTH!

ETA
About this:
I have no knowledge of the 'virtual Mode' or 'normal Mode'.

Basic explanation

In "normal" mode, yoou let the list manage the items, using only the ... let's call them "basic" events and the "basic" methods of TListItems, as I did in my example.

In "virtual" mode (i.e. when OwnerData = True) you manage the items yourself, externaly to the list, however you want; but then you can't use ListView.Items, since it's not used. Instead you have to respond to the OnData* events of the list itself to give it the data it needs in each moment. This is quite more complex, but useful if you have to retrieve the data through a relatively time-consuming procedure, for example to list an FTP directory or if the data is in a slow, remote database, etc.

So it's not that you set a "mode", but that you take the decision of leting the list manage the items or you take that responsability for yourself.
« Last Edit: April 06, 2019, 11:08:38 am by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.4/2.0.6  - FPC 3.0.4 on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

JLWest

  • Hero Member
  • *****
  • Posts: 634
Re: TListView Problems with delete
« Reply #19 on: April 06, 2019, 04:05:04 am »
@wp

Have it working.

At first I thought it was deleting the wrong line so I thought I would debug your code. Then I found this line. "DeleteDataIndex(3);"  I'm pretty sure that's why it wasn't deleting the line with focus.

As for reading the file twice and doing the setlength(FData) once. The actual file is pretty small and it takes .11 Sec to do the read.

But I implemented the Counter mod BLOCK_SIZE = 0.

Although it works I'm not sure how the grid or TListView get populated.

I read the Text file ('APorts.txt') into a Dynamic array FData which is an array of records.

There is an event on Listview1 (ListView1Data) ONData. It is never called? I modified the procedure  to look like my columns and text file. And somehow it populates the TlistView. I guess that's how TlistView works.

@ lucamar
I have downloaded your demo and will go thru it tonight. Hopefully I can understand this a little better.after a few hours.

Thanks to all.



FPC 3.2.0, Lazarus IDE v2.0.4
 Windows 10 Pro 32-GB
 Intel i7 770K CPU 4.2GHz 32702MB Ram
GeForce GTX 1080 Graphics - 8 Gig
4.1 TB

JLWest

  • Hero Member
  • *****
  • Posts: 634
Re: TListView Problems with delete
« Reply #20 on: April 06, 2019, 04:56:37 am »
@ lucamar

Kinda understand the ListViewTest code but the ActionList is a mystery.
FPC 3.2.0, Lazarus IDE v2.0.4
 Windows 10 Pro 32-GB
 Intel i7 770K CPU 4.2GHz 32702MB Ram
GeForce GTX 1080 Graphics - 8 Gig
4.1 TB

wp

  • Hero Member
  • *****
  • Posts: 6718
Re: TListView Problems with delete
« Reply #21 on: April 06, 2019, 10:10:47 am »
Although it works I'm not sure how the grid or TListView get populated.

I read the Text file ('APorts.txt') into a Dynamic array FData which is an array of records.

There is an event on Listview1 (ListView1Data) ONData. It is never called?
When a listview is painted in normal mode the Listview iterates through all its items and paints a row for each of them.

When a listview is painted in virtual mode it just increments a counter up to the number of items that you had set initially - and for each counter value it fires the event OnData. The event gets a dummy TListItem as parameter. The current counter value is written to the ListItem.Index. Therefore, you know which item is being requested. Look up in your data array FData for this index and populate the Listitem with the information that you want to be seen. With the ListItem populated this way the ListView can display the information in the same way as it would in normal mode.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

lucamar

  • Hero Member
  • *****
  • Posts: 2397
Re: TListView Problems with delete
« Reply #22 on: April 06, 2019, 11:04:09 am »
@ lucamar

Kinda understand the ListViewTest code but the ActionList is a mystery.

Those are just a couple actions to respond to Ctrl-Q and Ctrl-F. It serves basically to avoid adding a main menu or respond to KeyDown/Up in the form. The Ctrl-Q action is a fairly automatic "File>Exit" equivalent; the Ctrl-F one has its handler in the code.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.4/2.0.6  - FPC 3.0.4 on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

JLWest

  • Hero Member
  • *****
  • Posts: 634
Re: TListView Problems with delete
« Reply #23 on: April 06, 2019, 10:52:05 pm »
@lucamar

In working with the TlistView in normal mode I have the following procedure to add data to the TListview.

My data/text file looks like this:

 '|LELL||4||41.520833333||2.105||Sabadell||Spain||LE| '

Code: Pascal  [Select]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2.   Const First: Integer = 0;
  3.   Var
  4.    AList   : TStringList;
  5.    AString : String;
  6.    Bit1    : String;
  7.    AnItem  : TListItem;
  8.   begin
  9.  
  10.     if FileExists(C_File) then begin
  11.        AList := TStringList.Create;
  12.  
  13.        try
  14.         AList.LoadFromFile(C_File);
  15.  
  16.         for AString in AList do begin
  17.             Bit1 := AString;
  18.             Bit1 := ExtractWord(1, Bit1,['|']);
  19.  
  20.             AnItem := Listview1.Items.Add;
  21.             AnItem.Caption := Bit1;                 < Works fine
  22.  
  23.             Bit1 := ExtractWord(2, Bit1,['|']);
  24. //          AnItem.SubItem := Bit1;               <-- Compiler error
  25. //          AnItem. Items := Bit1;                  <-- Compiler error
  26.       end;
  27.  
  28.       SelectItem(First)
  29.     finally
  30.       AList.Free;
  31.     end;
  32.  
  33.   end else
  34.     ShowMessage('Error: ' + C_File);
  35.  
  36. end;                

Can't figure out how to add the next item/subitem to the TListView;

Need help

Thanks
FPC 3.2.0, Lazarus IDE v2.0.4
 Windows 10 Pro 32-GB
 Intel i7 770K CPU 4.2GHz 32702MB Ram
GeForce GTX 1080 Graphics - 8 Gig
4.1 TB