То, что мы узнали в предыдущей части урока, позволяет работать с файлами по адресу, жёстко записанному в тексте программы. Мы же хотим иметь возможность открывать любые файлы и работать с файлами по нашему выбору. Естественно, Delphi предоставляет нам такую возможность. Рассмотрим компоненты, позволяющие в работающей программе осуществлять выбор файлов. Delphi диалоги выбора файла позволяют указать програме, с каким файлом мы хотим работать.

На вкладке палитры компонентов Dialogs находятся компонент Delphi OpenDialog и компонент Delphi SaveDialog. Все Delphi диалоги, находящиеся на этой вкладке, в том числе и Delphi диалоги выбора файла, невизуальные, т.е. при переносе их на Форму в работающей программе их не видно, они видны только на этапе конструирования. Компонент Delphi OpenDialog позволяет открыть в нашей программе стандартное Windows-окно диалога открытия файла, компонент Delphi SaveDialog — окно диалога сохранения.


Delphi диалоги выбора файла сами по себе ничего не делают, а только предоставляют настройки, сделанные пользователем при выборе файла. Самый важный метод Delphi диалоговExecute. Он срабатывает в момент нажатия кнопки "открыть" или "сохранить" в окне выбора файла. Для примера давайте введём в программу возможность выбора файла для загрузки в редактор Memo, и сохранения после редактирования.

Итак, кидаем на Форму оба Delphi диалога, текстовый редактор Memo, и три кнопки Button. В свойство Caption одной из них записываем "Открыть. ", другой — "Сохранить", третьей — "Сохранить как. "


В обработчике OnClick кнопки "Открыть. " пишем:

if OpenDialog1.Execute then
Memo1.Lines.LoadFromFile(OpenDialog1.FileName);

В результате выбора файла свойство FileName компонента OpenDialog получает значение полного адреса выбранного файла, который мы и вставляем в функцию загрузки файла компонента Memo.
Всё это хорошо, но только в данном случае, когда записанное выражение записывается в одну строку. Если программа использует несколько раз выражение OpenDialog1.FileName, то писать руками устанешь. В Delphi для такого случая есть так называемый "оператор присоединения" with. Он используется для любых объектов, имеющих длинный "хвост" из свойств, которые приходится записывать многократно. Вот как он записывается:

Свойства Объекта внутри логических скобок begin/end можно записывать непосредственно. Допускается перечислять через запятую несколько объектов. Естественно, в случае, когда внутри скобок находится один оператор, они необязательны. Перепишем фрагмент загрузки файла с использованием оператора присоединения:

with OpenDialog1, Memo1 do
if Execute then
Lines.LoadFromFile(FileName);

Запись получается более компактной.
Так как свойства компонентов OpenDialog и SaveDialog одинаковы, сохранение текста выглядит абсолютно аналогично. Создаём обработчик нажатия кнопки "Сохранить как. " и пишем:

with SaveDialog1, Memo1 do
if Execute then
begin
Lines.SaveToFile(FileName);
OpenDialog1.FileName:=FileName; // Чтобы исправленный текст не затёр источник
end;

Наконец, для кнопки "Сохранить" пишем:

Memo1.Lines.SaveToFile(OpenDialog1.FileName); // Сохраняем туда, откуда считали

(В предыдущей строчке была ошибка. Как справедливо заметил в комментариях Oraculum — OpenDialog1.FileNam e нужно писать без кавычек.)

При работе этих фрагментов можно заметить, что выбирать приходится из всех файлов в нужной директории. Удобнее видеть только, например, текстовые файлы, или другой тип файлов по нашему выбору. Для этого используются фильтры, свойство Filter в наших компонентах. Настраивается оно в Инспекторе Объектов. При выборе его можно перейти в редактор фильтров:

В колонке FilterName записываем имена фильтров, в колонке Filter — список масок файлов, разделённых точкой с запятой. Маска файла в данном случае выглядит как

Звёздочка означает, что выбираются файлы с любыми именами, подходящие по расширению.
Свойство Delphi диалогов Title позволяет записать в заголовок нужную нам фразу. Если оставить его пустым, то в заголовке будут стандартные "открыть" или "сохранить"
Свойство InitialDir позволяет в момент открытия оказаться в нужной нам директории. Оно доступно как на этапе "конструирования", так и программно.

Теперь, пользуясь всем пройденным материалом, можно решить задачку на тему: как определить размер файла

1. Истина где-то рядом

Пока вёл свои изыскания с XML неоднократно наталкивался на практику применения RTTI в делфи. Но так и не прижилось… Разведения гетеров и сетеров для published свойств сильно засоряло код, хотя свои плюсы несомненно есть в этом подходе. Но смысл для не очень сложного проекта, с простой системой классов разводить весь этот огород? В дальнейшем я наткнулся на коллекции и в принципе они хорошо справляются с отведёнными для них задачами. Но… Не хватает универсальности использования и монотонность одних и тех же действий в разных проектах при работе с ними меня лично напрягало. Всё это в итоге сподвигло меня на написания своего велосипеда…
Итак, что я хотел или сама идея: есть некий сферический класс в вакууме, который имеет некоторые свойства и, соответственно, значения этих свойств:

Так же было бы здорово. Если бы был класс, который хранил множество «сферических» классов:

А уже вообще счастью не было бы предела, если бы создать такой класс, который мог бы сохранять класс-хранитель в файл, да ещё в разных форматах… Вот такая получилась сказка.

2. Укрощение, воплощение, мечты и реальность

В данном случае мне надо было сохранить довольно сложную структуру «Шкаф-купе», в котором произвольное количество секций, в каждой секции — полки, ящики и прочие радости быта домохозяек. Тут конечно пример создания руками, в настоящем проекте, это всё делается автоматом и на лету.

3. Объективная реальность

Собственно предложенное решение не претендует на шедевр, но согласитесь создание приложений существенно ускорится. Да, я знаю что TstringList не надо было использовать и RTTI не есть “светлый путь познания Дзен”, но всё же я этим пользуюсь по той простой причине, что быстро и главное я оперирую объектами напрямую и использую один и тот же код в разных проектах.

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.

Это «Песочница» — раздел, в который попадают дебютные посты пользователей, желающих стать полноправными участниками сообщества.

Если у вас есть приглашение, отправьте его автору понравившейся публикации — тогда её смогут прочитать и обсудить все остальные пользователи Хабра.

Чтобы исключить предвзятость при оценке, все публикации анонимны, псевдонимы показываются случайным образом.

У меня довольно часто возникает ситуация, когда в проекте необходимо организовать сохранение каких либо данных (объектов, типов, записей) в файл. Существует несколько методов решения этого вопроса. Я лично рассматривал возможность сохранения в XML, сериализацию, сохранение блока памяти и последовательное сохранение. Я называю их так.
Существенное отличие сохранения в XML-подобные форматы заключается в том, что сохраненные файлы являются открытыми и могут меняться как пользователями, так и сторонним программным обеспечением, конечно при наличии соответствующей документации. В каких то случаях это плюс, а в каких то минус. Зависит от конкретной задачи.
Мной будет рассмотрен метод сохранения в нетипизированный файл. Плюсы данного метода относительная простота реализации и возможность сохранения любой информации (вплоть до аудио и видео файлов). Без минусов тоже не обошлось — строгое соблюдение прямой записи и считывания, необходимость переписывать процедуры сохранения/чтения при изменении объекта (например добавление новых полей), а также проблемы с обратной совместимостью при изменении версии сохраняемых файлов. Хотя большая часть проблем может быть решена в логике самого приложения.
Данный метод рассматривается именно как достаточно простой как для понимания, так и для реализации.

Ваш объект, тип, или запись должна быть разбита на простые составляющие и впоследствии записаны в файл. Как это делается. Например строковую переменную можно записать в файл следующим образом:

procedure WriteToFile(var F: file; const str: string); var Len: LongInt; begin Len := Length(str); BlockWrite(F, Len, SizeOf(Len)); if Len > 0 then begin BlockWrite(F, Str[1], Length(Str) * SizeOf(Str[1])); end; end;

Как видите для сохранения типа String необходимо сначала записать в файл длину строки, а потом последовательно саму строку. Обратите внимание, что переменная F типа file должна быть открыта, с установленным указателем. Как это сделать я покажу дальше. Кстати существуют и другие способы записи строк как в память так и в файл. Навскидку могу вспомнить строки фиксированной длины (в Delphi это ShortString по моему) этот тип строк имеет фиксированную длину, а следовательно не требует записи своей длины, т.к. занимает в памяти всегда один и тот же объем. Так же в Windows широко применяются Null-terminated string, строки которые заканчиваются нулевым кодом и то же не требуют указания своей длины.
Рассказываю все это потому, что вы должны четко понимать как в памяти хранятся так называемые простые типы (строки, целые числа и т.п.). В конечном итоге все составные и сложные объекты состоят из этих «кирпичиков».
Приведу еще пример сохранения числа типа Integer, так как тут есть небольшой нюанс.

procedure WriteToFile(var F: file; const Int: Integer); var LInt: LongInt; begin LInt := Int; BlockWrite(F, LInt, SizeOf(LInt)); end;

Как видите при сохранении целого числа мы приводим его сначала к типу LongInt. Это делается по причине того, что тип Integer может изменяться в зависимости от версии Delphi, а также насколько я помню от конфигурации операционной системы. Точнее не сам тип, а способ его представления в памяти компьютера. В общем LongInt это фиксированный целый тип со знаком, который не будет изменяться в будущих выпусках Delphi.

Теперь если вы захотите сохранить в файл динамический массив целых чисел, можно воспользоваться следующей процедурой:

procedure WriteToFile(var F: file; const IntegerArray: TIntArray); var i: Integer; Len: LongInt; begin Len := Length(IntegerArray); BlockWrite(F, Len, SizeOf(Len)); if Len > 0 then begin for i := Low(IntegerArray) to High(IntegerArray) do begin WriteToFile(F, IntegerArray[i]); end; end; end;

Ниже показано как можно сохранить в ваш файл изображение в формате JPEG. Важно понимать, что даже если в файл будет сохранено одно только изображение, редактором оно уже не откроется, нужно будет извлекать его из файла таким же образом. Именно поэтому данный способ сохранения подходит только для составных типов. Например когда вы хотите сохранить картинку вместе с какой то информацией:

procedure WriteToFile(var F: file; const Image: TJPEGImage); var Len: LongInt; ImageStream: TBytesStream; begin ImageStream := TBytesStream.Create; Image.SaveToStream(ImageStream); Len := Length(ImageStream.Bytes); BlockWrite(F, Len, SizeOf(Len)); BlockWrite(F, ImageStream.Bytes[0], Len); ImageStream.Free; end;

И в конце, как обещал общая процедура сохранения в файл например массива целых чисел и изображения в формате Jpeg:

procedure Save(const FileName: string; const IntegerArray: TIntArray; const Image: TJPEGImage); var F: file; begin Result := False; AssignFile(F, FileName); Rewrite(F, 1); try WriteToFile(F, IntegerArray ); WriteToFile(F, Image ); finally CloseFile(F); end; end;

В процедуру передается имя файла, динамический массив целых чисел и изображение в формате Jpeg. Как видите все просто. Главное помните, что чтение всех данных должно происходить строго в том же порядке, что и запись. Для удобства метод WriteToFile я делаю перегруженным, и в зависимости от типа переданного аргумента, компилятор сам выбирает метод сохранения в файл. Пример достаточно примитивен, но отражает суть процесса в целом. Например можно было создать запись состоящую из массива целых чисел и картинки, реализовать WriteToFile для нее и использовать в дальнейшем. Примерно вот так:

// создаем запись вида массив плюс изображение TIntegerImage = Record IntegerArray: TIntArray; Image: TJPEGImage end; // пишем метод для записи TIntegerImage в файл, используя ранее описанные методы WriteToFile // для массива целых чисел и изображения Jpeg procedure WriteToFile(var F: file; const IntegerImage: TIntegerImage); begin WriteToFile(F, IntegerImage.IntegerArray ); WriteToFile(F, IntegerImage.Image ); end; // общая процедура сохранения в файл типа TIntegerImage procedure Save(const FileName: string; const IntegerImage: TIntegerImage); var F: file; begin Result := False; AssignFile(F, FileName); Rewrite(F, 1); try WriteToFile(F, IntegerImage ); finally CloseFile(F); end; end;

Возможно эта информация будет кому нибудь полезна.