Виртуальный ListView
Автор: Вадим К
Для связи: midav(a)land.ru
Содержание
Часто на форумах слышен вопрос: «Как в TListView (или TListBox) добавить 1000 строк? Уже на 100-200 начинает подтормаживать!». Ответы обычно такие: «А кто будет просматривать тысячи строк? Ошибка дизайна». Приводятся абстрактные примеры, показывающие, за сколько времени человек сможет просмотреть этот список.
Но есть пример, перед которым все поднимают руки. Это Lingvo. Для тех, кто не в курсе – это электронный словарь и на основном окне у него список слов и TEdit для ввода слова. В этой статье вы узнаете, как сделать такое, и почему стандартный подход тормозит.
Давайте для начала рассмотрим простой пример, который прольёт свет на это. Проанализируем следующий код:
function DupChar(c:char;count:integer):string;
var i:integer;
begin
Result:='';
for i:=1 to count do
Result:=result+c;
end;
Функция предельно проста – она получает на вход символ и количество повторений, а на выходе даёт строку с count символов c. Казалось, всё просто, но попробуйте протестировать эту функцию, когда количество символов достигает 1000-2000. Я сделал это на своём компьютере, смотрим график:
На этом графике видна линейная зависимость. Но при дальнейшем увеличении количества символов она превратится в экспоненциальную зависимость. Почему? Рассмотрим, что происходит при выполнении такой простой строки как s:=s+'*';.
- определяется размер исходной строки s, пусть будет n. Это быстро – пара ассемблерных команд
- определяется размер результирующей строки – это n+1
- выделяется место под новую строку. Очень долгая операция.
- старая строка копируется в новое место. Также не короткая операция.
- дописывается один символ
- удаляется старая строка – быстро, но не так, как хотелось
То есть, когда мы создаём строку на 1000 элементов, мы 1000 раз выделяем и 999 раз удаляем память.
А теперь посмотрите на такой вариант кода.
function DupChar(c:char;count:integer):string;
var
i:integer;
begin
SetLength(Result,count);
for i:=1 to count do
Result[i]:=c;
end;
Здесь память выделяется один раз, а потом заполняется вся строка. Казалось бы, малость, а посмотрите на графики теперь:
По моим выкладкам, скорость возросла в 25-30 раз. Неплохо, да? Кто там говорил, что Delphi делает медленный код?
Я думаю, теперь уже становится яснее, почему TListView, TMemo, TListBox при добавлении большого количества строк начинают подтормаживать? Их работа аналогична.
Но с TListView и другими компонентами есть ещё одна проблема. Мы дублируем данные. То есть у нас где то в массиве есть данные и также они есть в TListView. Это ничего, пока мы не начинаем редактировать или сортировать. И тут начинают изобретаться велосипеды. Лично видел код, где в TListView отображались данные, а также на форме были ещё с десяток невидимых TMemo, в которых хранилась вспомогательная информация. При удалении одной строки из списка передёргивались все компоненты. Сортировка была просто ужасной.
Выходом из сложившейся ситуации является виртуальный режим. В таком режиме TListView способен удерживать до 232 элементов. Согласитесь, это уже нечто. Заинтересовались? Тогда вперёд. Давайте сделаем простой пример. В списке будут числа и их квадраты. Числа будут до 10000.
Итак, открываем Delphi и ставим на форму TListView. Дальше настраиваем такие свойства:
object ListView1: TListView
Left = 152
Top = 168
Width = 250
Height = 150
Columns= <
item
Caption = #1063#1080#1089#1083#1086
Width = 100
end
item
Caption = #1050#1074#1072#1076#1088#1072#1090
Width = 100
end>
OwnerData = True
TabOrder = 2
ViewStyle = vsReport
End
Для тех, кому лень настраивать TListView, могут просто скопировать этот текст и, выбрав свою форму, нажать Ctrl+V. Главным свойством здесь является OwnerData. Именно оно переводит лист в виртуальный режим. Теперь список требует от нас, что бы мы указали, сколько будет элементов. Для этого я поставил кнопку и вписал туда строку
ListView1.Items.Count:=1000;
Теперь лист знает сколько элементов. Когда он захочет нарисовать их, он у нас попросит нужные элементы. Замечу, что «просить» их он может в произвольном порядке. Более того, просить он будет только те, которые нужно отрисовать. Если у вас на экране умещается 20 элементов, а в списке 1000, то попросит он только 20! Остальные будут запрашиваться по мере того, как вы будете прокручивать список. И ещё, он умеет их кэшировать. То есть, некоторые элементы повторно не будут запрашиваться.
Все запросы идут через событие OnData. В нём нас интересует параметр item – это переменная, куда мы должны заполнить данные. А номер элемента мы можем узнать через item.index. Итак, для наших целей нужен такой обработчик.
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
item.Caption:=inttostr(item.Index);
item.SubItems.Add(inttostr(sqr(item.Index)));
end;
Для тех, кто раньше работал с TListView, тут нет ничего необычного. Всё! Запускаем и наслаждаемся. На своём компьютере я выставлял 1000000 (миллион!) и всё работало моментально. Попробуйте в качестве эксперимента повторить классическим способом. Я думаю при 1000-2000 вам уже надоест ждать заполнения.
Об особенностях виртуально режима.
Сначала о недостатке – он пока один – если выставить свойство CheckBox, то они не появляются. Я пока не разобрался с этим, если у вас есть предложения – готов выслушать. Самое интересное, что место под них выделяется.
Допустим, вы храните данные в массиве и обновили их. А TListView не знает об этом. И на экране будут отображаться устаревшие данные. Для этого подскажем TListView, что данные изменены – вызываем метод UpdateItems(n,m). Где n – начальный элемент для обновления, m – конечный. То есть, если нужно обновить только пятый элемент, то пишем UpdateItems(5,5). Но если элемент не отображается на экране в данный момент, то эта строка проигнорируется TListView’ом.
Так как параметр Item имеет свойство ImageIndex, то можно, подцепив ImageList, и картинки добавлять.
Также в виртуальном режиме очень хорошо делать список, элементы которого должны быстро обновляться. То есть, например, для менеджеров закачек, для таблички мониторинга состояния группы объектов. И главное, память экономится.
В приложенном архиве вы найдёте рабочий пример, где есть динамический массив структур.
А дальше?
Если вам интересны подобные темы, пишите свои замечания мне на почту – midav[a]land.ru или на IRC-канал сайта www.delphi.int.ru.
Автор: Вадим К
Статья добавлена: 18 июля 2007
Следующая статья: Скачиваем файлы из интернета »
Зарегистрируйтесь/авторизируйтесь,
чтобы оценивать статьи.
Статьи, похожие по тематике
- Web-страничка внутри приложения
- Бегущая строка
- Введение в Synapse
- Выравнивание компонентов
- Диалог в стиле wizard'а
- Добавляем компонент в стандартный Message Dialog
- Использование компонента TCoolBar
- Компоненты Ribbon в Delphi
- Написание инсталлятора на Delphi
- Обучающий курс. 13. Ввод и вывод
- Обучающий курс. 6. Обзор свойств формы
- Обучающий курс. 7. Обзор палитры компонент - Standard, Additional
- Перемещение TImage по форме во время работы приложения
- Рисуем график функции в Delphi
- Создание интерфейса с использованием PNG-графики
- Создание своего диалога выбора цвета
- Читаем цитаты с bash.org.ru своей программой
Для вставки ссылки на данную статью на другом сайте используйте следующий HTML-код:
Ссылка для форумов (BBCode):
Быстрая вставка ссылки на статью в сообщениях на сайте:
{{a:38}} (буква a — латинская) — только адрес статьи (URL);
{{статья:38}} — полноценная HTML-ссылка на статью (текст ссылки — название статьи).
Поделитесь ссылкой в социальных сетях:
Комментарии читателей к данной статье
Репутация: +2 |
mirt.steelwater (13 декабря 2010, 18:22): отличная статья, спасибо!
|
Репутация: +359 |
Вадим К (13 апреля 2009, 16:29): Легко сказать - Лаги. Я не знаю, какой код в OnData. Может там сложные матрасчёты - тогда конечно будет тормозить.
В целом, без кода ничего не могу сказать. Почта вообще то в конце письма указана. И! если в письме будет "аФФтар" - никакого ответа никто не получит. Если с русским проблема - понимаю на других языках - английском и украинском. |
Репутация: 0 |
Эхо Унитазного Бачка (13 апреля 2009, 12:46): В общем сортировку я написал к виртульному LV. Но вот незадача! при количестве колонок от 6 и более LV тормозит сильнее чем при включенном OwnerData!!! Сортировка выполняется моментально, отрисовка в LV страдает. При прокрутке с помощью бегунка наблюдаются лаги. АФФТОР Статьи откликнись. Давай решим вопрос с виртуальным LV навсегда ;-)
|
Репутация: 0 |
Эхо Унитазного Бачка (2 апреля 2009, 15:00): Замечательная статья, только остается загадкой как организовать сортировку в таком ListView.
|
Репутация: +21 |
Gooddy (19 января 2008, 19:14): Отличная статья. Не извлёк много полезного но сэкономил время на "Научном тыке"
|
Оставлять комментарии к статьям могут только зарегистрированные пользователи.