Recent

Author Topic: Virtual TListView  (Read 34002 times)

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Virtual TListView
« on: January 15, 2011, 08:03:36 pm »
Hello. I am a new Lazarus/FreePascal user who moved from FORTRAN to TurboPascal, to C++, and then to REALbasic for it's cross-platform capabilities. I am very impressed with Lazarus/FreePascal and am attempting to migrate several substantial programs. A basic component I need is a virtual TListView, and would very much appreciate some help. I have spent several days searching Delphi and Lazarus forums, but am still flummoxed.

I am using OS X 10.6.5 Intel with Lazarus 0.9.31, and am using the Qt widget set. I believe I have everything installed properly, although I did have some initial problems, so could have made a mistake.

For the TListView I set OwnerData := true and add two TListColumns then try the following code.

Code: [Select]
var
  dataA: array [0..9] of string =
          ('A0','A1','A2','A3','A4','A5','A6','A7','A8','A9');
  dataB: array [0..9] of string =
          ('B0','B1','B2','B3','B4','B5','B6','B7','B8','B9');
 
  col1: array [0..9] of string;
  col2: array [0..9] of string;

  procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
  begin
    Item.Caption := col1[Item.Index];
    Item.SubItems.Add(col2[Item.Index]);
  end;

  procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
    Selected: Boolean);
  begin
    ShowMessage(IntToStr(Item.Index)); // never happens :/
  end;

  procedure TForm1.Button1Click(Sender: TObject);
  var
    i: integer;
  begin
    for i := 0 to 9 do
    begin
      col1[i] := dataA[i];
      col2[i] := dataB[i];
    end;
    ListView1.Items.Count := 10;
  end;

  procedure TForm1.Button2Click(Sender: TObject);
  var
    i: integer;
  begin
    for i := 0 to 9 do
    begin
      col1[i] := dataB[i];
      col2[i] := dataA[i];
    end;
    ListView1.Items.Count := 10;
  end;

I was unclear if 'Item.SubItems.Add' is the correct way to set the text in column 2, but couldn't figure out any other way to do it. Is there a better way?

Is setting 'Items.Count' the correct way to refresh the list?

The data sets update properly from the 'OnData' event, but the 'OnSelectItem' event never fires. I have tried other events as well ('OnClick', 'OnMouseDown', etc.), but don't get a response.

Can anyone point me in the correct direction?
« Last Edit: January 17, 2011, 06:24:11 pm by Frederick »
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #1 on: January 17, 2011, 06:04:35 pm »
I reinstalled Lazarus using the latest OS X Intel snapshot, and am using the default Carbon set now. Nothing different however. In exploring workarounds I find substituting the following:

Code: [Select]
  //ListView1.Items.Count := 10;
  SetListViewCount(10);


using:

Code: [Select]
procedure TForm1.SetListViewCount(count: integer);
var
   i: integer;
begin
  while ListView1.Items.Count > 0 do
    ListView1.Items.Delete(ListView1.Items.Count-1);
  for i := 1 to count do
     ListView1.Items.Add;
end;       

makes the select event work properly. This seems like an unsatisfactory kludge, as I am adding empty TListItems and defeating the point of a virtual list, but at least it works. An odd behavior is that if the first row is selected or accessed, it doesn't update, but otherwise they all do.

Any pointers on how to do this properly would be greatly appreciated.
« Last Edit: January 17, 2011, 07:32:05 pm by Frederick »
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #2 on: January 17, 2011, 08:08:02 pm »
Changing to:

Code: [Select]
procedure TForm1.SetListViewCount(count: integer);
var
  i: integer;
begin
  while ListView1.Items.Count > 0 do
    ListView1.Items.Delete(ListView1.Items.Count-1);
  ListView1.Items.Count := 0;
  for i := 1 to count do
    ListView1.Items.Add;
end;       

removes the odd behavior for row one. Now this works:

Code: [Select]
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
    Selected: Boolean);
begin
  if selected then
    ShowMessage(
      IntToStr(Item.Index) + ': ' + Item.Caption + Item.Subitems[0]);
end;

I'll use this for now, but still looking for a better solution.
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

Chronos

  • Full Member
  • ***
  • Posts: 241
    • PascalClassLibrary
Re: Virtual TListView
« Reply #3 on: January 17, 2011, 08:32:06 pm »
To force redraw virtual TListView you should call Refresh method.

Code: [Select]
var
  StringList1: TStringList;
  ListView1: TListView; // OwnerData := True

procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
  if Item.Index < StringList1.Count then begin
    Item.Caption := StringList1.Strings[Item.Index];
    Item.SubItems.Add('Column 1 data');
    Item.SubItems.Add('Column 2 data');
  end;
end;

procedure UpdateList;
begin
  ListView1.Items.Count := StringList1.Count;
  ListView1.Refresh;
end;

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #4 on: January 17, 2011, 09:39:32 pm »
Thank you for the reply chronos.

However calling 'ListView1.Refresh' does not seem to change anything. The behavior is identical if I call:

Code: [Select]
ListView1.Items.Count := 10;

or

Code: [Select]
ListView1.Items.Count := 10;
ListView1.Refresh;   

In either case, the odd behavior for the first row occurs, and 'TForm1.ListView1SelectItem' is never triggered.

Oddly, however, if I do:

Code: [Select]
ListView1.Items.Count := 0;
ListView1.Items.Count := 10;

the odd behavior for the first row is fixed. However, the select item event is still not triggered.
« Last Edit: January 17, 2011, 09:49:51 pm by Frederick »
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

Phil

  • Hero Member
  • *****
  • Posts: 2737
Re: Virtual TListView
« Reply #5 on: January 17, 2011, 10:23:54 pm »
Have you tried TOvcVirtualListBox? Maybe that would work better for you.

http://wiki.lazarus.freepascal.org/OrphPort

Thanks.

-Phil

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #6 on: January 17, 2011, 11:07:37 pm »
Phil-

Still new to this, but I got the Orpheus Port installed. The TOvcVirtualListBox seems pretty easy to use, and handles 1000s of rows very nicely. I will have to play around with it for a bit to see if it has everything I need, but it looks promising.

Thanks very much.

-Frederick
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #7 on: January 18, 2011, 12:16:31 am »
Thanks again Phil.

I tried out TOvcVirtualListBox, but will stick with TListView for now. TOvcVirtualListBox is speedy, but has non-standard tabbed columns and appearance, and I was having trouble with the vertical scrollbar and multiple selections.

With my TListView kludge, it takes about ~4 seconds to add 10000 rows, and another ~4 to delete them. Not great, but not a deal breaker.

Code: [Select]
var
  dataSet: integer = 0;
  dataCount: integer = 10000;

procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var
  row: string;
begin
  row := IntToStr(Item.Index);
  case dataSet of
    0: begin
         Item.Caption := 'A' + row;
         Item.Subitems.Add('B' + row);
       end;
    1: begin
         Item.Caption := 'C' + row;
         Item.Subitems.Add('D' + row);
       end;
  end;
end;

procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
  Selected: Boolean);
var
  row, col0, col1: string;
begin
  if selected then
  begin
    row := IntToStr(Item.Index);
    col0 := Item.Caption;
    col1 := Item.Subitems[0];
    ShowMessage('Row ' + row + ': ' + col0 + ', ' + col1);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  dataSet := 0;
  Reset(dataCount);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  dataSet := 1;
  Reset(dataCount);
end;

procedure TForm1.Reset(count: integer);
begin
  //ListView1.Items.Count := 0;
  //ListView1.Items.Count := count;
  SetListViewCount(count);
end;

procedure TForm1.SetListViewCount(count: integer);
var
  i: integer;
begin
  while ListView1.Items.Count > 0 do
    ListView1.Items.Delete(ListView1.Items.Count-1);
  ListView1.Items.Count := 0;
  for i := 1 to count do
    ListView1.Items.Add;
end;   

Still open for suggestions. I'm still new at Lazarus so not sure if I have encountered bug or if I'm just doing something wrong.
« Last Edit: January 18, 2011, 12:36:13 am by Frederick »
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

Chronos

  • Full Member
  • ***
  • Posts: 241
    • PascalClassLibrary
Re: Virtual TListView
« Reply #8 on: January 18, 2011, 07:10:38 am »
Thank you for the reply chronos.

However calling 'ListView1.Refresh' does not seem to change anything. The behavior is identical if I call:

Code: [Select]
ListView1.Items.Count := 10;

or

Code: [Select]
ListView1.Items.Count := 10;
ListView1.Refresh;   

In either case, the odd behavior for the first row occurs, and 'TForm1.ListView1SelectItem' is never triggered.

Oddly, however, if I do:

Code: [Select]
ListView1.Items.Count := 0;
ListView1.Items.Count := 10;

the odd behavior for the first row is fixed. However, the select item event is still not triggered.

I didn't notice that you mentioned "TListView first row display problem". This is bug which nobody probably reported yet. It doesn't work in my last installed Lazarus snapshot neither. I have to make some testing and report bug in bugtracker.

Regerding ListView1.Refresh: In fact this method calls Repaint method which cause repaint of control and so reloading list through OnData event if OwnerData is set. If ListView.Items.Count is set properly according real data count OnData event will be definitely executed.

Assigning to ListView1.Items.Count will probably do refresh only in case if previous and new Count is different. So it is better to call Refresh if you need to reload values even Count is not changed.

I made nice example program to demonstrate use of virtual aspect of TListView. Just download and open attachment. Tested on Lazarus 0.9.31 some recent snapshot.

Chronos

  • Full Member
  • ***
  • Posts: 241
    • PascalClassLibrary
Re: Virtual TListView
« Reply #9 on: January 18, 2011, 09:03:47 am »
Reported bug related to wrong first row refresh on Win32: http://bugs.freepascal.org/view.php?id=18546

Workaround you could use is
Code: [Select]
...
ListView1.Items.Count := RealDataList.Count;
ListView1.Items[-1]; // This change OnData cache last index to -1 so index 0 will be reloaded properly on refresh
ListView1.Refresh;
...

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #10 on: January 18, 2011, 04:18:52 pm »
Thanks chronos.

Your workaround to call "ListView1.Items[-1]" does work. Perhaps calling "ListView1.Items.Count := 0" does the same thing?

A call to "ListView1.Refresh" makes sense and does refresh the list properly when "ListView1.Items.Count" has not changed.

However, "ListView1.Refresh" does not work if "ListView1.Items" contains items added with "ListView1.Items.Add". In that case, visible rows are not refreshed.

My problem is that "TForm1.ListView1SelectItem" is not called unless there are actual items in "ListView1.Items". Once I add the items, "ListView1SelectItem" works, but "ListView1.Refresh" does not. The only way I can figure out how to refresh the list is to delete all items and add them again.

I downloaded your test program and tried it out. It works well, except that "TForm1.ListView1SelectItem" is never called. So I can confirm that your reported bug also applies to OS X, but perhaps the broken "TForm1.ListView1SelectItem" is a new bug only on OS X?


“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

Chronos

  • Full Member
  • ***
  • Posts: 241
    • PascalClassLibrary
Re: Virtual TListView
« Reply #11 on: January 18, 2011, 04:58:50 pm »
It is not good idea to combine classical populated list with virtual list. I use preferably virtual lists as item count in list can grow rapidly in many cases. Even if you want to run your application on old computers you would need to draw on screen only what is necessary.

Problem with ListView1.Items.Count := 0 could be loosing focus on selected item whereas if Count is not changed index of selected item will not exceed total count.

There are many non expected and not tested situations on different platforms on different conditions. You can try to dig deeper to LCL source and try to find cause of trouble. You can easily navigate through code holding CTRL and clicking on functions and switching between class interface and implementation using CTRL+SHIFT plus up down keys. CTRL+H shortcut can navigate back to previous location. Just try to find out more information and then create new bug report. It is always good to check latest snapshot.

Virtual TListView is sufficient for most of common tasks but you should look at TVirtualTreeView component too. It can display virtualized list of items as well.
http://wiki.freepascal.org/VirtualTreeview_Example_for_Lazarus

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #12 on: January 18, 2011, 06:06:59 pm »
Thank you chronos, I appreciate your responses. Yes, I did assume it was a bad idea to mix populated and virtual list methods, but was trying to find a workaround until it gets fixed.

The major bug I am running into (not responding to a select item event) is easy to spot by running your example program on OS X, so I posted a note on your bug report. Maybe I will have to post a separate report as well.

I will also try your tips for digging deeper into the code, thanks. I'm still learning to use the debugger. I did download the latest snapshot yesterday. One of the great things about Lazarus, and FreePascal, is being able to look through the source.

I did try some of the TVirtualTree examples. It is very nice, but in most cases a row-based list is what I need. I'll have another look though.
« Last Edit: January 18, 2011, 06:09:38 pm by Frederick »
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #13 on: January 18, 2011, 06:13:45 pm »
Actually, I have not yet checked out VirtualTreeview. Going to see if I can make it work.
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Virtual TListView
« Reply #14 on: January 18, 2011, 06:36:31 pm »
I'm afraid getting VirtualTreeView running is more than I can deal with at the moment. It needs "lclextensions-0.3.0.zip" installed, which I can't figure out how to do - getting error messages I don't understand. Also it "Needs testing on MacOS".
« Last Edit: January 18, 2011, 06:40:02 pm by Frederick »
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

 

TinyPortal © 2005-2018