Delphi.int.ru — Портал программистов

Вход Регистрация | Забыли пароль?

События

Сегодня:
Вопросы0    Ответы0    Мини-форумы0


Последние:
Вопрос26.08, 21:10 / #6673
Ответ02.08, 00:42 / #6619
Новости30 апреля 2012


Сейчас онлайн:
На сайте — 19
На IRC-канале — 3

Ссылки

Автор: Вадим К
Для связи: 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. Я сделал это на своём компьютере, смотрим график:

График 1

На этом графике видна линейная зависимость. Но при дальнейшем увеличении количества символов она превратится в экспоненциальную зависимость. Почему? Рассмотрим, что происходит при выполнении такой простой строки как 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;

Здесь память выделяется один раз, а потом заполняется вся строка. Казалось бы, малость, а посмотрите на графики теперь:

График 2

По моим выкладкам, скорость возросла в 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

Следующая статья: Скачиваем файлы из интернета »

Рейтинг статьи: 5.00 Голосов: 7 Ваша оценка:

Зарегистрируйтесь/авторизируйтесь,
чтобы оценивать статьи.


Статьи, похожие по тематике

 

Для вставки ссылки на данную статью на другом сайте используйте следующий HTML-код:

Ссылка для форумов (BBCode):

Быстрая вставка ссылки на статью в сообщениях на сайте:
{{a:38}} (буква a — латинская) — только адрес статьи (URL);
{{статья:38}} — полноценная HTML-ссылка на статью (текст ссылки — название статьи).

Поделитесь ссылкой в социальных сетях:


Комментарии читателей к данной статье

mirt.steelwater
Репутация: +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.
Gooddy
Репутация: +21

Gooddy (19 января 2008, 19:14):

Отличная статья. Не извлёк много полезного но сэкономил время на "Научном тыке"

Оставлять комментарии к статьям могут только зарегистрированные пользователи.