При работе с компонентом TListView в Delphi разработчики часто сталкиваются с проблемами при отображении изображений, особенно когда элементы списка содержат как текст, так и картинки. Рассмотрим решение этой проблемы на основе реального кейса с форума.
Проблема
Исходный код пытался отображать в TListView элементы с текстом и изображениями, загружаемыми из базы данных. Основные проблемы:
Все элементы списка показывали одно и то же (последнее загруженное) изображение
Возникали ошибки при освобождении памяти
Неправильная работа при удалении элементов
Анализ проблемы
Основная ошибка заключалась в использовании одного объекта TBitmap для всех элементов списка. В исходном коде:
b: TBitmap; // создается при OnCreate
...
ListItemData.ThePicture := TBitmap(b); // все элементы ссылаются на один объект
Это приводило к тому, что все элементы списка показывали последнее загруженное изображение, так как они ссылались на один и тот же объект в памяти.
Решение
Правильный подход - создавать отдельный объект TBitmap для каждого элемента списка:
type
PListItemData = ^TListItemData;
TListItemData = record
theString: string;
ThePicture: TBitmap;
end;
procedure TMyForm.RunQueryAndFillListView;
var
ClipItem: TListItem;
ListItemData: PListItemData;
BlobField: TBlobField;
Stream: TStream;
begin
lvClip.Items.BeginUpdate;
try
while not FDQuery2.Eof do
begin
ClipItem := lvClip.Items.Insert(0);
New(ListItemData);
try
ListItemData.theString := s.Text;
ListItemData.ThePicture := nil; // инициализация
if ContainsText(s.Text, 'Picture') then
begin
BlobField := FDQuery2.FieldByName('Image') as TBlobField;
Stream := FDQuery2.CreateBlobStream(BlobField, bmRead);
try
ListItemData.ThePicture := TBitmap.Create;
ListItemData.ThePicture.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
ClipItem.Data := ListItemData;
except
ListItemData.ThePicture.Free;
Dispose(ListItemData);
raise; // повторно вызываем исключение
end;
FDQuery2.Next;
end;
finally
lvClip.Items.EndUpdate;
end;
end;
Обработка удаления элементов
Для корректного освобождения памяти необходимо обрабатывать событие OnDeletion:
procedure TMyForm.lvClipDeletion(Sender: TObject; Item: TListItem);
var
ListItemData: PListItemData;
begin
ListItemData := PListItemData(Item.Data);
if ListItemData <> nil then
begin
ListItemData.ThePicture.Free; // безопасно, даже если nil
Dispose(ListItemData);
end;
end;
Альтернативное решение
Если возникают проблемы с обработчиком OnDeletion, можно использовать альтернативный подход с классом вместо записи:
type
TListItemData = class
public
theString: string;
ThePicture: TBitmap;
constructor Create;
destructor Destroy; override;
end;
constructor TListItemData.Create;
begin
inherited;
ThePicture := nil;
end;
destructor TListItemData.Destroy;
begin
ThePicture.Free;
inherited;
end;
// Заполнение ListView
procedure TMyForm.FillListView;
var
ClipItem: TListItem;
ListItemData: TListItemData;
begin
ListItemData := TListItemData.Create;
try
// заполнение данных
ClipItem.Data := ListItemData;
except
ListItemData.Free;
raise;
end;
end;
// Обработчик удаления
procedure TMyForm.lvClipDeletion(Sender: TObject; Item: TListItem);
begin
TListItemData(Item.Data).Free;
end;
Проблемы и их решение
Ошибка при освобождении памяти: Убедитесь, что ThePicture инициализирован как nil перед созданием. Проверка на nil перед вызовом Free не обязательна, так как Free сам проверяет на nil.
Проверка на пустое изображение: Вместо проверки ThePicture.Empty (которая может вызвать ошибку, если объект не создан), используйте:
if (ListItemData.ThePicture <> nil) and (not ListItemData.ThePicture.Empty) then
// работаем с изображением
Освобождение памяти при закрытии формы: Если вы используете OnDeletion, дополнительное освобождение в OnClose не требуется. Если же решите освобождать в OnClose, делайте это аккуратно:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
var
i: Integer;
begin
for i := 0 to lvClip.Items.Count - 1 do
if lvClip.Items[i].Data <> nil then
TListItemData(lvClip.Items[i].Data).Free;
end;
Заключение
Правильное управление памятью при работе с TListView и пользовательскими данными требует:
Создания отдельных объектов для каждого элемента
Корректного освобождения памяти
Использования либо OnDeletion, либо OnClose для освобождения ресурсов (но не обоих сразу)
Предложенные решения помогут избежать проблем с отображением изображений и утечками памяти в ваших приложениях на Delphi.
Решение проблемы некорректного отображения изображений в Delphi TListView путем создания отдельных объектов TBitmap для каждого элемента и правильного управления памятью.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.