Delphi сохранение настроек в файл
Сейчас, а именно в этой статье, я хотел бы поговорить об ini-файлах, так как в следующей статье хотел бы рассказать как можно сохранить TStringGrid именно в ini-файл. И дальше я буду рассказывать как создавать тесты с помощью ini-файлах, так что нам эта информация понадобится в дальнейшем. Вообще ini-файлы предназначены для сохранения настроек программы, также некоторые любят хранить настройки в реестре (об этом мы тоже поговорим, работать почти одинаково, что и с ini-файлами), но зачем лишним захламлять реестр, так что в этом случае используются ini-файлы (для настроек). Ini-файл (а точнее файл настроек) должен быть (состоит) из следующих разделов
Название раздела заключается в квадратные скобки — [название раздела]. Разделов может быть сколь угодно, это специально предназначено для удобного использования и разделения настроек. Список значений определяется с помощью названия переменных ini-файла. Например
Давайте посмотрим полную структуру ini-файла, на примере настроек нашей программы (какой-нибудь)
Ini-файл у нас создан, давайте теперь подключать его в наш проект, для начала надо в Uses подключить модуль IniFiles. Далее Мы объявим переменную типа TIniFile, а затем создадим ее. На событие формы OnCreate обычно создается ini-файла, например так
ini-файл у нас создан теперь мы можем считывать с него значения и записывать туда, в определенные переменные самого ini-файла (например в переменную Languae, которая описана выше в нашем файле).
Для записи используются следующая функция
- WriteString(‘название раздела’,’название переменной’,’значение’);
Как видите мы указываем наши параметры ini-файла. Записывать можно и целочисленное значение, только вместо WriteString - WriteInteger. А вообще из раскрывающего списка можно увидеть типы записи в файл.
Для чтения настроек используется функция
- ReadString(‘название раздела’,’название переменной’,»);
Как видите все тоже самое, только последний параметр остался — пустые кавычки, это означает, что мы считывает строковое значение, если считать как целочисленное значение, то необходимо будет указать в последнем параметре 0 (нуль) и при этом считать с помощью функции ReadInteger.
Есть форма, в которой заполняются настройки программы.
На этой же форме есть выпадающий список, в котором хранятся разные конфигурации этих настроек. Как сохранить несколько конфигураций и загружать их, если пользователь выберет определённые.
Я знаю о существовании iniFiles. Использовать несколько штук этих файлов? Может быть какой-то класс нужен?
__________________jmp $ ; Happy End!
The Cake Is A Lie. Секции, насколько я понял, делят логически настройки внутри одной конфигурации.
Ваше решение нарушает идеологию. Наверное. Секции, насколько я понял, делят логически настройки внутри одной конфигурации.
2. Если INI-файл хранить в папке с программой, то при переносе папки на другой компьютер настройки сохраняются. (Я еще не написал ни одной программы, которая бы не поместилась на одну дискету
3. Новичку в реестре можно запросто запутаться или (боже упаси), чего-нибудь не то изменить.
Поэтому для хранения параметров настройки программы удобно использовать стандартные INI файлы Windows. Работа с INI файлами ведется при помощи объекта TIniFiles модуля IniFiles. Краткое описание методов объекта TIniFiles дано ниже.
Create('d:\test.INI');
Создать экземпляр объекта и связать его с файлом. Если такого файла нет, то он создается, но только тогда, когда произведете в него запись информации.
WriteBool(const Section, Ident: string; Value: Boolean);
Присвоить элементу с именем Ident раздела Section значение типа boolean
WriteInteger(const Section, Ident: string; Value: Longint);
Присвоить элементу с именем Ident раздела Section значение типа Longint
WriteString(const Section, Ident, Value: string);
Присвоить элементу с именем Ident раздела Section значение типа String
ReadSection (const Section: string; Strings: TStrings);
Прочитать имена всех корректно описанных переменных раздела Section (некорректно описанные опускаются)
ReadSectionValues(const Section: string; Strings: TStrings);
Прочитать имена и значения всех корректно описанных переменных раздела Section. Формат : имя_переменной = значение
EraseSection(const Section: string);
Удалить раздел Section со всем содержимым
ReadBool(const Section, Ident: string; Default: Boolean): Boolean;
Прочитать значение переменной типа Boolean раздела Section с именем Ident, и если его нет, то вместо него подставить значение Default.
ReadInteger(const Section, Ident: string; Default: Longint): Longint;
Прочитать значение переменной типа Longint раздела Section с именем Ident, и если его нет, то вместо него подставить значение Default.
ReadString(const Section, Ident, Default: string): string;
Прочитать значение переменной типа String раздела Section с именем Ident, и если его нет, то вместо него подставить значение Default.
Free;
Закрыть и освободить ресурс. Необходимо вызвать при завершении работы с INI файлом
Property Values[const Name: string]: string;
Доступ к существующему параметру по имени Name
Единственное замечание, INI файлы имеют ограничение на размер файла в 64 Kb.
Иногда требуется сохранить значение какого-либо параметра для того, чтобы использовать его в дальнейшем. В таких случаях я предпочитаю записывать значения в ini файл.
Итак рассмотрим использование ini файлов сразу на практике, по ходу сразу и разберемся. Итак создадим новый проект и добавляем пару кнопок. Первая кнопка, чтобы сохранить данные, а вторая - загрузить. Еще нам потребуется компонент memo - для того, чтобы отобразить результат.
Приступим к написанию кода. Первое, что необходимо сделать - это в разделе uses подключить модуль IniFiles.
Напишем код для клавиши загрузить.
Теперь объясним, что происходит по нажатию кнопки. Первое, что мы делаем - объявляем ряд переменных.
Первый объект IniFile типа TIniFile, этот объект нам и понадобится для работы с ini файлами.
Далее мы объявляем переменные param1, param2, param3 и param4 в них мы будем сохранять считанные значения. Обратите внимание, что переменные разных типов, чтобы обозначить разницу в работе с ними.
IniFile := TIniFile.Create(GetCurrentDir + '\Options.ini'); - далее мы вызываем конструктор класса Tinifale.
Если объяснить по рабоче-крестьянски - то мы создаем объект, который будет работать с файлом Options.ini, который находится в директории приложения GetCurrentDir (если, конечно таковой имеется).
Далее, в блоке try finally с помощью функций считывания мы получаем значение параметров. Например строка Param3:=IniFile.ReadString('Param','Param3','Слово'); - считываем значение Param3 из блока Param, если нет возможности считать - присваиваем переменной значение = 'Слово'. Аналогично считываются все параметры. после выводим то, что получили в memo.
Для наглядности продемонстрирую Вам как выглядит наш ini файл:
[Param]
Param1=1
Param2=2
Param3=Слово
Param4=01.01.2013
В квадратных скобках содержится имя блока, а ниже перечислены все переменные.
После того, как мы считали все значения и вывели их в memo нам необходимо разрушить объект IniFile. Для этого вызовем деструктор класса TInifale - IniFile.Destroy;
Этот код во многом похож на код кнопки загрузить, только мы теперь на считываем, а записываем. Пример записи в ini файл: Inifile.WriteInteger ('Param','Param2',param2); Записываем в блок Param, в переменной Param2 значение param2.
Пока вёл свои изыскания с XML неоднократно наталкивался на практику применения RTTI в делфи. Но так и не прижилось… Разведения гетеров и сетеров для published свойств сильно засоряло код, хотя свои плюсы несомненно есть в этом подходе. Но смысл для не очень сложного проекта, с простой системой классов разводить весь этот огород? В дальнейшем я наткнулся на коллекции и в принципе они хорошо справляются с отведёнными для них задачами. Но… Не хватает универсальности использования и монотонность одних и тех же действий в разных проектах при работе с ними меня лично напрягало. Всё это в итоге сподвигло меня на написания своего велосипеда…
Итак, что я хотел или сама идея: есть некий сферический класс в вакууме, который имеет некоторые свойства и, соответственно, значения этих свойств:
Так же было бы здорово. Если бы был класс, который хранил множество «сферических» классов:
А уже вообще счастью не было бы предела, если бы создать такой класс, который мог бы сохранять класс-хранитель в файл, да ещё в разных форматах… Вот такая получилась сказка.
2. Укрощение, воплощение, мечты и реальность
Собственно, как это всё сделать и заставить работать. Итак, посмотрим на наш сферический класс.
- TClassItem = class (TCollectionItem)
- private
- FName: String ;
- FProperty: TStringList;
- FStorage: TClassItems;
- FPrt:Integer;
- public
- ParentItem: TClassItem;
- constructor Create(Collection: TCollection); override ;
- function GetPropAsHTML: String ;
- function GetAddress(separator: char ): String ;
- destructor Destroy; override ;
- published
- property Name: String read FName write FName;
- property Parent: Integer read FPrt write FPrt;
- property Prop: TStringList read FProperty write FProperty;
- property Storage: TClassItems read FStorage write FStorage;
- end;
Видим, что есть имя, есть родитель, стринглист со парами «свойство-значение» и хранилище себе подобных. Рассмотрим хранилище себе подобных поближе. Это как раз тот «класс-хранитель», что был выше.
- TClassItems = class (TCollection)
- Private
- ParentItem:TClassItem;
- function GetItem(Index: Integer): TClassItem;
- procedure SetItem(Index: Integer; Value: TClassItem);
- public
- function AddOrGet(Name: String ): TClassItem;
- function ItemExists(Name: String ):Boolean;
- property Items[Index: Integer]: TClassItem read GetItem write SetItem;
- end;
Думаю тут всё более-менее понятно. Далее посмотрим на реализацию класса, который обеспечивал бы хранение в файлах.
- TSaveClass = class (TComponent)
- private
- FStorage: TClassItems;
- public
- constructor Create(AOwner: TComponent); override ;
- destructor Destroy; override ;
- Procedure SaveToFile(FileName: String );
- Procedure LoadFromFile(FileName: String );
- procedure LoadFromTextFile(FileName: String );
- procedure SaveToTextFile(FileName: String );
- Procedure SaveToCompressFile(FileName: String );
- Procedure LoadFromCompressFile(FileName: String );
- Function GetItemFromAddress(Adr: String ;separator: char ):TClassItem;
- published
- property Storage: TClassItems read FStorage write FStorage;
- end;
Не сложно заметить, что работает с тремя типами файлов — бинарные, текстовые и (на десерт) со сжатием. Ну и конечно пример работы со всем этим счастьем:
- procedure TForm1.Button1Click(Sender: TObject);
- var
- Shkaf,Sections,Section,Polka:TClassItem;
- begin
- sc:=TSaveClass.Create(nil);
- // Создание
- Shkaf:=sc.Storage.AddOrGet( 'Shkaf' );
- // Задание значений свойств
- Shkaf.Prop.Values[ 'W' ]:= '3000' ;
- Shkaf.Prop.Values[ 'H' ]:= '1800' ;
- Shkaf.Prop.Values[ 'D' ]:= '300' ;
- // Создание дочернего объекта
- Sections:= Shkaf.Storage.AddOrGet( 'Sections' );
- Section:= Sections.Storage.AddOrGet( 'Section1' );
- Section.Prop.Values[ 'width' ]:= '152' ;
- Polka:=Section.Storage.AddOrGet( 'Polka1' );
- Polka.Prop.Values[ 'Position' ]:= '450' ;
- Polka:=Section.Storage.AddOrGet( 'Polka2' );
- Polka.Prop.Values[ 'Position' ]:= '0' ;
- Section:= Sections.Storage.AddOrGet( 'Section2' );
- Section.Prop.Values[ 'width' ]:= '300' ;
- // Сохраняем в текстовый файл
- sc.SaveToTextFile( 'sc.txt' );
- // Сохраняем в бинарный файл
- sc.SaveToFile( 'sc.bin' );
- // Сохраняем в Zip-файл
- sc.SaveToCompressFile( 'sc.z' );
- //Уничтожаем объект
- sc.Free;
- //Создаём заново
- sc:=TSaveClass.Create(Form1);
- //Загружаем из Zip-файла
- sc.LoadFromCompressFile( 'sc.z' );
- //Получаем объект
- Shkaf:=sc.Storage.AddOrGet( 'Shkaf' );
- //Отображаем его в дереве вместе со всеми потомками
- ToTreeViev(Shkaf,Nil);
- end;
В данном случае мне надо было сохранить довольно сложную структуру «Шкаф-купе», в котором произвольное количество секций, в каждой секции — полки, ящики и прочие радости быта домохозяек. Тут конечно пример создания руками, в настоящем проекте, это всё делается автоматом и на лету.
3. Объективная реальность
Собственно предложенное решение не претендует на шедевр, но согласитесь создание приложений существенно ускорится. Да, я знаю что TstringList не надо было использовать и RTTI не есть “светлый путь познания Дзен”, но всё же я этим пользуюсь по той простой причине, что быстро и главное я оперирую объектами напрямую и использую один и тот же код в разных проектах.
Читайте также: