Проверяйте наличие дубликатов TListItem перед добавлением в Tlistview

У меня есть список records, который я хочу обобщить в TListView

Структура записи следующая

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

В записи может быть несколько экземпляров SourceTable/TargetTable с одним экземпляром Source/Target Field.

Я хотел бы создать TListView в стиле vsReport, который суммирует каждую пару SourceTable\TargetTable.

В идеале я хотел бы сделать следующее:

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;

т. е. Создайте автономный TListItem, убедитесь, что он еще не существует, а затем добавьте его в мой TListView. Однако он ломается при назначении LI.Caption - по сути, назначать нечего. Я подозреваю, что по крайней мере часть проблемы заключается в (nil)

Обычным созданием TListItem было бы использование LI := lvMigration.Items.Add;, но это не помогает в моем случае использования. Кажется, я не могу найти никакой документации, где это делается.


person Dan Kelly    schedule 30.10.2013    source источник


Ответы (2)


Вместо:

LI := LI.Create(nil);

Вы хотели написать

LI := TListItem.Create(nil);

Это самая старая ошибка Delphi в книгах, и я уверен, что вы делали ее раньше (мы все делали), и я уверен, что вы узнаете ее, когда увидите.

Однако остальная часть вашего кода не будет работать. Вы ничего не можете сделать с экземпляром TListItem, не предоставив Owner. Например, посмотрите на реализацию TListItem.SetCaption:

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

С вашим кодом Owner равно nil, поэтому этот код просто приведет к нарушению прав доступа. На самом деле вам никогда не следует создавать экземпляр TListItem. Это делается классами контейнеров.

Даже если без этой проблемы, вы обнаружите, что IndexOf не делает то, что вы хотите. Вы хотите, чтобы он выполнял поиск по значению элемента. Но он выполняет поиск по ссылке.

Что вам нужно будет сделать, так это перебрать каждый элемент в списке и проверить его заголовок и подэлементы (или что-то еще, что идентифицирует элемент) по предполагаемому новому значению.

Если вы настроены на более радикальное изменение, которое облегчит жизнь в долгосрочной перспективе и улучшит производительность, если список станет большим, то переход на использование представления списка в виртуальном режиме может помочь. Вы бы сохранили список своих предметов в TList<TMyItem> или подобном. И тогда вы будете заполнять по требованию. Если вы сделаете это, то будет намного проще обнаруживать дубликаты, поскольку вы будете работать с простым контейнером, а не с элементом управления с графическим интерфейсом. По сути, вы усложняете себе жизнь, используя элемент управления GUI в качестве класса-контейнера.

person David Heffernan    schedule 30.10.2013
comment
Упс. На самом деле с правкой, на которую вы указали, она ломается на LI.Caption и не достигает части IndexOf, поэтому мне не удалось проверить это самостоятельно. Отредактирую вопрос. - person Dan Kelly; 30.10.2013
comment
Это не имеет значения. Вам не нужно редактировать вопрос. Пожалуйста, не делай этого. Весь ваш подход не может работать. Вам нужно будет сделать это так, как я описал в моем последнем абзаце. - person David Heffernan; 30.10.2013
comment
Достаточно справедливо на откате. И достаточно справедливо, что подход не будет работать, но вы не отвечаете на основной вопрос - почему установка значения LI.Caption не удалась (несмотря на опечатку в вопросе)? - person Dan Kelly; 30.10.2013
comment
@DanKelly Я не понимал, что это был твой основной вопрос. Я расширим ответ. Дай мне минуту. - person David Heffernan; 30.10.2013
comment
Примерно так я и думал (и пытался намекнуть). Из реализации TLisView.Items.AddItem я надеялся, что есть способ создать виртуальный TListItem, но подозреваю, что это полезно только для перемещения TListItem из одного TListView в другой. - person Dan Kelly; 30.10.2013

Я бы посоветовал вам отсортировать основной список записей во второй список, в котором отфильтрованы дубликаты, а затем использовать второй список для заполнения ListView. Я бы также предложил использовать ListView в виртуальном режиме (установите для свойства OwnerData значение true, а затем используйте его события OnData...). Это значительно улучшит производительность вашего ListView, и вы сможете получить доступ к своим записям в памяти и управлять ими, не замедляясь из-за пользовательского интерфейса.

person Remy Lebeau    schedule 30.10.2013