Question

I have a list of records that I want to summarise into a TListView

The structure of the record is as follows

MyRecord = record
   SourceTable: string;
   SourceField: string;
   TargetTable: string;
   TargetField: string;
end;

In the record there may be multiple instances of SourceTable / TargetTable, with single instances of Source/Target Field

I would like to create a TListView in vsReport style that summarises each SourceTable\TargetTable pair.

Ideally I would like to do the following:

procedure SetTables;
var
   mp: MyPointer;
   LI: TListItem;
begin
   LI := LI.Create(nil);
   LI.Caption := ap^.SourceTable;
   LI.SubItems.Add(ap^.TargetTable);
   LI.Checked := not ap^.Updated;
   if lvMigration.Items.IndexOf(LI) = -1 then
       lvMigration.Items.AddItem(LI);
end;

i.e. Create a standalone TListItem, check that it doesn't already exist, then add it to my TListView. However it breaks at the assignment of the LI.Caption - essentially there is nothing to assign to. I suspect at least part of the problem is the (nil)

The normal creation of a TListItem would be to use LI := lvMigration.Items.Add; but this doesn't help my use case. I can't seem to find any documentation where the above is done.

Was it helpful?

Solution

Instead of:

LI := LI.Create(nil);

You meant to write

LI := TListItem.Create(nil);

This is the oldest Delphi mistake in the books and I'm sure you've made it before (we all have), and I'm sure you recognise it when you see it.

The rest of your code will not work though. You cannot do anything with a TListItem instance without supplying an Owner. For example, look at the implementation of TListItem.SetCaption:

procedure TListItem.SetCaption(const Value: string);
begin
  if Value <> Caption then
  begin
    FCaption := Value;
    if not Owner.Owner.OwnerData then
    .... 
  end;
end;

With your code, Owner is nil and so this code will just lead to an access violation. In fact you should not be instantiating TListItem ever. That's done by the container classes.

Even if without that issue, though, you would find that IndexOf does not do what you want. You want it to perform a search on the value of the item. But it performs a search on the reference.

What you will need to do is iterate through each item in the list and check its Caption and sub-items (or whatever identifies the item) against the prospective new value.

If you felt in the mood for a more drastic change, one that would make life easier in the long run, and improve performance if the list gets large, then switching to using the list view in virtual mode would help. You'd maintain a list of your items in a TList<TMyItem> or similar. And then you'd populate on demand. If you did that, then it would be much easier to detect duplicates since you'd be working with a plain container rather than a GUI control. Fundamentally you are making life hard for yourself by using a GUI control as a container class.

OTHER TIPS

I would suggest you sort your main list of records into a second list that has the duplicates filtered out, and then use the second list to populate the ListView. I would also suggest using the ListView in virtual mode (set its OwnerData property to true and then use its OnData... events). That will drastically improve the performance of your ListView, and you can then access/manipulate your records in memory and not be slowed down by the UI.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top