Recent

Author Topic: How to extract data of TListItem?  (Read 5508 times)

guest60499

  • Guest
How to extract data of TListItem?
« on: February 01, 2017, 01:25:27 am »
I have a TListView with associated items. I give AddItem a string and an instance of a class I'm interesting in watching. I need to retrieve that. However, operating on any of the members of the Items array causes a segfault.

Code: Pascal  [Select][+][-]
  1. function TMainForm.IndexOfClient(Client: TTCPClient): Integer;
  2. var
  3.   I: Integer;
  4. begin
  5.   Result := -1;
  6.  
  7.   for I := 0 to ConnectionListView.Items.Count do
  8.   begin
  9.     if ConnectionListView.Items[I].Data = nil then
  10.       WriteLn('Nil.');
  11.   end;
  12. end;  

If I run similar code outside of the GUI it is possible to access the Caption field and successfully print it, but any access to the Data field crashes the thread handling the connection but not the GUI. What is going on? Do I need to keep a separate key and value list?

kapibara

  • Hero Member
  • *****
  • Posts: 532
Re: How to extract data of TListItem?
« Reply #1 on: February 01, 2017, 02:34:35 am »
You need a cast to get the data from a TListItem.Data:

Code: Pascal  [Select][+][-]
  1. TMyObject = class
  2.   Value: string;
  3. end;
  4.  
  5. TMyObject(ListView.Items[ListView.ItemIndex].Data).Value;

Code: Pascal  [Select][+][-]
  1. procedure SetAndGetListItemData;
  2. var
  3.   myobject: TMyObject;
  4.   listitem: TListItem;
  5. begin
  6.   myobject:= TMyObject.Create;
  7.   myobject.Value:='I am an object';
  8.  
  9.   listitem:= ListView.Items.Add;
  10.   listitem.Caption:='Item1';
  11.   listitem.Data:=myobject;
  12.   listitem.SubItems.Add(myobject.Value);
  13. end;
  14.  
« Last Edit: February 01, 2017, 02:38:11 am by kapibara »
Lazarus trunk / fpc 3.0.4 / Debian 10 - 64 bit

derek.john.evans

  • Guest
Re: How to extract data of TListItem?
« Reply #2 on: February 01, 2017, 02:58:46 am »
What is going on? Do I need to keep a separate key and value list?

Some thoughts:

Ideally a multi-threaded library shouldn't know about any GUI. This allows the GUI to be implemented independently.

You should have a thread safe list which the GUI or a thread can access via locking.

The thread safe list should provide OnInsert/OnDelete events so a GUI can mirror the current threads.

You can merge the thread list/GUI into one, but the rules still apply. The list must be locked to access, and a listitem must be added _after_ a data object is created.

serbod

  • Full Member
  • ***
  • Posts: 137
Re: How to extract data of TListItem?
« Reply #3 on: February 01, 2017, 10:08:45 am »
Use TListView in virtual (owner data) mode:

1. Prepare some non-visual list or collection (TMyObjectList, for example)

2. Set ListView.OwnerData proprty to True

3. Set ListView.OnData event to ListViewData from code below

4. Place TTimer with 500 ms interval, create OnTimer event and add UpdateMyObjectList() to it.

Visual list will reflect content of non-visual list, that can be VERY large (millions items). Timer need only for synchronize lists sizes and invaludate visible items, so they will reflect changes in realtime.

Code: Pascal  [Select][+][-]
  1. procedure TMainForm.ListViewData(Sender: TObject; Item: TListItem);
  2. var
  3.   n: Integer;
  4. begin
  5.   if Assigned(FMyObjectList) and Assigned(Item) then
  6.   begin
  7.     n := Item.Index;
  8.     // get object from non-visual list by visual list item index
  9.     if (n >=0 ) and (n < FMyObjectList.Count) then
  10.     begin
  11.       // update list item from object
  12.       UpdateMyObjectListItem(Item, FMyObjectList.Items[n]);
  13.     end;
  14.   end;
  15. end;
  16.  
  17. procedure TMainForm.UpdateMyObjectListItem(ListItem: TListItem;
  18.   Obj: TMyObject);
  19. begin
  20.   // fill list item from object
  21.   ListItem.ImageIndex := Obj.ImageIndex;
  22.   ListItem.Caption := Obj.Name;
  23.  
  24.   ListItem.SubItems.Clear();
  25.   ListItem.SubItems.Add(Obj.Column1Text);
  26.   ListItem.SubItems.Add(Obj.Column2Text);
  27.   // ...
  28. end;
  29.  
  30. procedure TMainForm.UpdateMyObjectList();
  31. begin
  32.   if Assigned(FMyObjectList) then
  33.   begin
  34.     // update visual list
  35.     if ListView.Items.Count <> FMyObjectList.Count then
  36.       ListView.Items.Count := FMyObjectList.Count;
  37.   end
  38.   else
  39.   begin
  40.     ListView.Items.Count := 0;
  41.   end;
  42.   ListView.Invalidate();
  43. end;
  44.  
  45.  

Fungus

  • Sr. Member
  • ****
  • Posts: 352
Re: How to extract data of TListItem?
« Reply #4 on: February 01, 2017, 12:30:44 pm »
You have a bug in your code which probably causes your SEGFAULT (access violation). The value of TListView.Items.Count is beyond end of list, and the last item is located at *.Count - 1. To get the index of the item which data property holds the client, consider this:

Code: Pascal  [Select][+][-]
  1. function TMainForm.IndexOfClient(Client: TTCPClient): Integer;
  2. begin
  3.   For Result:= 0 To ConnectionListView.Items.Count - 1 Do //Count - 1 = last item
  4.     If ConnectionListView.Items[Result].Data = Client Then Exit;
  5.   Result:= -1;
  6. end;

guest60499

  • Guest
Re: How to extract data of TListItem?
« Reply #5 on: February 01, 2017, 04:43:07 pm »
You have a bug in your code which probably causes your SEGFAULT (access violation). The value of TListView.Items.Count is beyond end of list, and the last item is located at *.Count - 1. To get the index of the item which data property holds the client, consider this:

Code: Pascal  [Select][+][-]
  1. function TMainForm.IndexOfClient(Client: TTCPClient): Integer;
  2. begin
  3.   For Result:= 0 To ConnectionListView.Items.Count - 1 Do //Count - 1 = last item
  4.     If ConnectionListView.Items[Result].Data = Client Then Exit;
  5.   Result:= -1;
  6. end;

Thank you for making me feel extremely stupid. It's been a while since I've programmed in Pascal.

To address the other suggestions (probably useful as I still have another question):

1) I received SIGSEGV even when casting.
2) The thread isn't doing anything with the ListView, it's merely a member of it.
3) In case I needed synchronization I considered using a timer as you've described but I consider the update delay unacceptable.

I've looked more closely at the methods to select an item from a ListView but none of them seem to do what I want. I realized that the ListView probably copies the object for storage. I will need to keep my own record of what is in the list, and the suggestion to use OwnerData seems helpful.

The attempt:

Code: Pascal  [Select][+][-]
  1. function TMainForm.IndexOfClient(Client: TTCPClient): Integer;
  2. var
  3.   I: Integer;
  4.   ListClient: TTCPClient;
  5. begin
  6.   Result := -1;
  7.  
  8.   for I := 0 to ConnectionListView.Items.Count - 1 do
  9.   begin
  10.     ListClient := PTCPClient(ConnectionListView.Items[I].Data)^;
  11.     WriteLn(ListClient.Host, ':', ListClient.Port);
  12.   end;
  13. end;

Fails and prints garbage (no string, very large port). With OwnerData set to true I am unsure of how to manage the ListView - if I need to delete something, do I need to clear it and then re-add every item? If OwnerData is true the loop doesn't run at all.

EDIT: Appears I'll just need to implement the methods referenced in the documentation. Seems rather circuitous - I might just use a separate list and minimally manage the ListView.

Fungus

  • Sr. Member
  • ****
  • Posts: 352
Re: How to extract data of TListItem?
« Reply #6 on: February 01, 2017, 05:57:12 pm »
How is the TCP client assigned to the listitem? Have you tried this:

Code: Pascal  [Select][+][-]
  1. Procedure SetClientToItem(Item: TListItem; Client: TTCPClient); Inline;
  2. Begin
  3.   Item.Data:= Pointer(Client);
  4. End;
  5.  
  6. Function GetClientFromItem(Item: TListItem): TTCPClient; Inline;
  7. Begin
  8.   Result:= TTCPClient(Item.Data);
  9. End;

Using an ekstra type (PTCPClient) is not nescessary since an instance of TTCPClient is already a pointer.

guest60499

  • Guest
Re: How to extract data of TListItem?
« Reply #7 on: February 01, 2017, 10:32:50 pm »
How is the TCP client assigned to the listitem? Have you tried this:

Code: Pascal  [Select][+][-]
  1. Procedure SetClientToItem(Item: TListItem; Client: TTCPClient); Inline;
  2. Begin
  3.   Item.Data:= Pointer(Client);
  4. End;
  5.  
  6. Function GetClientFromItem(Item: TListItem): TTCPClient; Inline;
  7. Begin
  8.   Result:= TTCPClient(Item.Data);
  9. End;

Using an ekstra type (PTCPClient) is not nescessary since an instance of TTCPClient is already a pointer.

Here is how the client is added:

Code: Pascal  [Select][+][-]
  1. ListView.AddItem(Caption: String; AObject: TObject);

Fungus

  • Sr. Member
  • ****
  • Posts: 352
Re: How to extract data of TListItem?
« Reply #8 on: February 02, 2017, 12:22:01 pm »
Here is how the client is added:

Code: Pascal  [Select][+][-]
  1. ListView.AddItem(Caption: String; AObject: TObject);

Does not help much.. Look at this:

Code: Pascal  [Select][+][-]
  1. //Create and get, the right way:
  2. ListView.AddItem('SomeItem', Client);
  3. Client:= TTCPClient(ListView.Items[..].Data);
  4.  
  5. //Create and get, the other way:
  6. ListView.AddItem('SomeItem', @Client);
  7. Client:= PTCPClient(ListView.Items[..].Data)^;
  8.  
  9. //Optimally you should create items like this:
  10. With ListView.Items.Add Do Begin
  11.   Caption:= 'SomeItem';
  12.   Data:= Pointer(Client);
  13.   //Set more properties for item
  14. End;
  15.  
  16. //And get as the first example above:
  17. Client:= TTCPClient(ListView.Items[..].Data);

EDIT: I cannot find any documentation for TListView.AddItem, even though it exists. How the object argument is attached, I do not know, and I cannot find the source for it. So stick with the "optimal" example above.
« Last Edit: February 02, 2017, 12:38:28 pm by Fungus »

 

TinyPortal © 2005-2018