Чудеса TStringList. Почему надо использовать объекты TStringList везде

Object Pascal в сочетании с ассемблером в современной его форме Delphi 5/6/7 предоставляет неограниченные возможности для полета мысли программиста, и этой статьей мы откроем серию, в которой последовательно будем это демонстрировать.

Начнем мы с простого, крайне полезного, но мало известного, по крайней мере, для начинающих, стандартного класса Дельфи TStringList. Сейчас мы посмотрим, как красиво решаются типичные задачи при помощи этого класса.

Для начала в двух словах, какие такие замечательные свойства есть у объектов данного класса. TStringList - это класс, предназначенный для хранения списка строк и списка объектов с текстовым представлением (прямо как в 1С - это СписокЗначений). Кроме того, этот список может быть отсортирован по алфавиту или при помощи сравнительной функции, написанной программистом. Кроме того, этот список может быть интерпретирован как список значений (Name=string). Кроме того, этот список может быть сохранен в файл или поток, преобразован в непрерывную строку или строку, разделенную запятыми. Физически получаемая строка или поток представляет собой обычный текст, в котором строки разделены символами CR/LF (стандартный Windows Text File), или запятыми (CommaText, Excel). Ну и само собой, есть возможность загружать список строк из файла, потока, строки и строки, разделенной запятыми. Следует особо отметить замечательное свойство упаковки в строку, разделенную запятыми: при обратной распаковке строки всегда восстанавливаются в их исходном виде. Это означает, что допустима многократная вложенность строк, разделенных запятыми, дающая огромный выигрыш при упаковке/распаковке многомерных структурных данных в текстовый формат, что мы и продемонстрируем во второй задаче.

Итак, для начала рассмотрим список строк как список объектов с текстовым представлением, т. к. именно в данном ключе следует использовать список строк в реальных приложениях. Что из себя представляет объект с текстовым представлением? Это может быть, например, список товаров, имеющих помимо наименования еще и дополнительные параметры типа единицы измерения, количества в упаковке и цены. Итак, имеем предопределение типов:


type   
  TEdIzm=class  
    public  
      Name:string;  
      Weight:double;  
    end;  
  TTovar=class  
    public  
      Name:string; //наименование  
      EdIzm:TEdIzm; //ед. изм.  
      CountUp:integer; //кол-во в упаковке  
      PriceOut:currency; //цена продажная  
    end;  
var  
  TovarList:TStringList;  

Набор объектов TTovar - это классический справочник однородных товаров, например, хлебобулочных изделий. Поле Weight в классе TEdIzm требуется для перевода одних единиц в другие. Вернемся к нашим булкам. Допустим, поставщик "Карякинский Хлебозавод" предоставил нам текстовый файл, в котором находится информация о его новой продукции и отпускных ценах в формате текстового файла:


*Начало файла*  
Товар1=[наименование товара]  
ЕдИзм1=[наименование ед.изм.]  
Вес1=[вес единицы измерения]  
КолУп1=[количество в упаковке]  
Цена1=[цена поставщика]  
--  
Товар2=[наименование товара]  
ЕдИзм2=[наименование ед.изм.]  
Вес2=[вес единицы измерения]  
КолУп2=[количество в упаковке]  
Цена2=[цена поставщика]  
--  
*Конец файла*  

Всего в файле содержится, например, 2000 наименований. Наша задача - загрузить данные этого файла в список строк ListBox1:TListBox (лежащий на форме) в отсортированном виде так, чтобы была возможность по двойному щелчку просмотреть параметры каждого товара (для этого к каждой строке будет прикреплен объект типа TTovar).

Если решать эту задачу в лоб (как чтение построчно и разбор текстового файла), а так же напрямую добавлять в ListBox, то это обернется неэффективной работой компьютера, его подтормаживанием (на слабых машинах), и вообще, для дальнейших внесений изменений в программный код это решение не является лучшим. Гораздо эффективнее сделать "финт ушами", а именно, создать TStringList, загрузить в него исходный файл, создать второй TStringList, загрузить в него товары, отсортировать их, и в конце концов, присвоить свойству ListBox.Items:


function LoadTovary(filename:string):TStrings;   
var str:TStringList;  
    tovar:TTovar;  
    edizm:TEdIzm;  
    I:integer;  
begin  
  //загружаем файл с данными  
  str:=TStringList.Create;  
  str.LoadFromFile(filename);  
  //посчитаем, сколько будет товаров  
  result:=TStringList.Create; // TStrings являедся предком TStringList, поэтому данное присвоение корректно  
  result.Capacity:=str.count div 6;  
  for I:=0 to result.capacity do  
  begin  
    tovar:=TTovar.Create;  
    edizm:=TEdIzm.Create;  
    tovar.Name:=str.Values['Товар'+inttostr(i)];  
    edizm.Name:=str.Values['ЕдИзм'+inttostr(i)];  
    edizm.Weight:=strtofloat(str.Values['Вес'+inttostr(i)]);  
    tovar.CountUp:=strtoint(str.Values['КолУп'+inttostr(i)]);  
    tovar.PriceOut:=strtoint(str.Values['Цена'+inttostr(i)]);  
    tovar.EdIzm:=edizm;  
    result.AddObject(tovar.Name,tovar);  
  end;  
  result.Sort;  
end;  
  
...  
ListBox1.Items:=LoadTovary('fromhlzd.txt');  
...  

Заметим, что если написать функцию типа TStringListSortCompare, то можно будет сортировать не только по текстовому представлению, но и по любым другим признакам, например, цене:


function StringListComparePrice(List: TStringList; Index1, Index2: Integer): Integer;  
begin  
  Result := TTovar(List.Objects[Index1]).PriceOut-TTovar(List.Objects[Index2]).PriceOut;  
end;  
  
...  
result.CustomSort(StringListComparePrice);  
...  

Ну и в конце концов, продемонстрируем реакцию на двойное нажатие на получившемся списке товаров. По двойному щелчку мы покажем отпускную цену товара:


procedure TForm1.ListBox1DblClick(Sender: TObject);  
begin  
  showmessage('Цена поставщика '+  
floattostr(TTovar(TListBox(sender).Items.Objects[TListBox(sender).ItemIndex]).PriceOut)  
  );  
end;  

Кстати, не забываем освобождать системные ресурсы объектов перед удалением строк методами ListBox1.Items.Delete()/Clear() или str.Delete()/Clear() (str:TStringList), если это требуется, а так же при закрытии приложения:


procedure TForm1.FormDestroy(Sender: TObject);  
var i:integer;  
begin  
  for i:=0 to ListBox1.Items.Count-1 do  
  with TTovar(ListBox1.Items.Objects[i]) do  
  begin  
    EdIzm.Free;  
    Free;  
  end;  
end;  

Теперь немного усложним задачу. Пусть у нас есть не один файл, а десять - от десяти разных поставщиков. Нам надо в общей сложности загрузить 20000 наименований и затем отсортировать их. Если мы будем именно так и делать, то сортировка такого большого списка объектов займет значительное время, это и составляет задачу. Однако такая задача решается очень просто - достаточно после создания объекта TStringList сразу присвоить его свойству Sorted значение true. После этого вставка новых строк будет осуществляться с помощью быстрого алгоритма, однако потеряется возможность сортировать по параметрам прикрепленных объектов, т.к. в случае Sorted=true сортировка производится автоматически только по текстовому представлению (см. исходники TStringList). При Sorted=true, обработка дубликатов определяется свойством Duplicates. Можно пропускать, разрешать и запрещать дубликаты объектов с одинаковым текстовым представлением. При этом, если дубликаты пропускаются или запрещены, необходимо следить за освобождением ресурсов объектов, не добавленных в список. По умолчанию, свойство Duplicates равно dupIgnore, что означает, что дубликаты пропускаются, поэтому по умолчанию надо следить за освобождением ресурсов.

Стоит отметить, что при сохранении или загрузке списка строк, прикрепленные к строкам объекты не сохраняются и не восстанавливаются в стандартном TStringList, однако можно очень красиво решить эту проблему - написать потомка TStringList с переопределенными процедурами GetTextStr и SetTextStr, в которых придумать и реализовать собственный формат хранения объектов и их текстового представления в виде непрерывного текста.

Итак, мы увидели, что TStringList позволяет решать самые разнообразные задачи от группировки объектов с текстовым представлением до простой обработки списка значений переменных. Теперь посмотрим, как этот класс позволяет решать задачи упаковки/распаковки сложно-структурированных данных в текстовый вид и обратно. Такая задача очень часто возникает в коммуникационных задачах, например, при передаче данных по сети или между программными модулями.

Предположим, что нам надо запустить некую программу obrab.exe с параметром командной строки, в которой необходимо передать выбранный товар из сформированного выше списка. Напишем упаковщик и распаковщик данных. С TStringList эта задача программируется за две секунды:

Упаковщик


procedure runobrab;  
var strtov,stred:TStringList;  
begin  
  strtov:=TStringList.Create;  
  with TTovar(ListBox1.Items.Objects[ListBox1.ItemIndex]) do  
  begin  
    stred:=TStringList.Create;  
    stred.Values['ЕдИзм']:=EdIzm.Name;  
    stred.Values['Вес']:=floattostr(EdIzm.Weight);  
    strtov.Values['Товар']:=Name;  
    strtov.Values['ЕдИзм']:=stred.CommaText;  
    strtov.values['КолУп']:=inttostr(CountUp);  
    strtov.values['Цена']:=floattostr(PriceOut);  
  end;  
  winexec('obrab.exe '+strtov.CommaText,SW_SHOWNORMAL);  
  strtov.Free;  
  stred.Free;  
end;  

Распаковщик


function getparam:TTovar;  
var strtov,stred:TStringList;  
begin  
  strtov:=TStringList.Create;  
  stred:=TStringList.Create;  
  strtov.CommaText:=paramstr(1);  
  stred.CommaText:=strtov.Values['ЕдИзм'];  
  result:=TTovar.Create;  
  with result do  
  begin  
    EdIzm:=TEdIzm.Create;  
    EdIzm.Name:=stred.Values['ЕдИзм'];  
    EdIzm.Weight:=strtofloat(stred.Values['Вес']);  
    Name:=strtov.Values['Товар'];  
    CountUp:=strtoint(strtov.values['КолУп']);  
    PriceOut:=strtofloat(strtov.values['Цена']);  
  end;  
  stred.Free;  
  strtov.Free;  
end;  

Продолжение следует…

Автор: Цованян Роман
Теги:
TStringList
Добавлено: 07 Августа 2018 08:24:30 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...