Как в delphi blob записать в файл

Обновлено: 07.07.2024

Сохранение данных в типизированный файл.

Предварительные замечания.

Прежде, чем начать разговор о нетипизиованных файлах, сделаем ещё несколько замечаний по поводу записи в типизированный файл в delphi.

Они будут необходимы нам в дальнейшем при работе с нетипизированными файлами.

Позиционирование указателя типизированного файла.

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

Для обращения к записи по её номеру существует процедура позиционирования:

seek(файловая_переменная, номер_записи) //помним, что отсчет ведутся от 0)

Создадим файл для хранения целых чисел. Запишем в него 5 значений. Проконтролируем записанные значения чтением из файла и отображение их в memo.

Определим размер файла функцией FileSize(ф.п.).

Затем установим указатель файла в четвёртую позицию (то есть номер_записи=3).

Изменим значение записи в этой позиции и вновь проконтролируем результат.

Для решения задачи подготовим форму:

типизированный файл в delphi

Воспользуемся полученной информацией и ещё раз посмотрим, как в delphi записать в файл данные фиксированной длины и какие процедуры и функции для работы с файлами нам для этого понадобятся.

Создадим обработчики событий для первой и второй кнопок:

var vFileInt:File Of Integer; // создаём переменную для ссылки на типизированный файл

procedure TForm1.Button1Click(Sender: TObject);

rewrite(vFileInt); //создаём файл

j:=filePos(vFileInt); //контролируем положение указателя файла

write(vFileInt,j); //формируем значения и записываем их в файл.

j:=filePos(vFileInt); //опять контролируем положение указателя в файле

j:=fileSize(vFileInt); //проверяем размер файла (количество записей)

reset(vFileInt); //переоткрываем файл. При этом указатель файла устанавливается на 0.

while not EOF(vFileInt) do

read(vFileInt,j); //читаем файл и прочитанные значения отображаем в memo.

closeFile(vFileInt);

Форма после нажатия на первую кнопку:

чтение и запись файлов в delphi

procedure TForm1.Button2Click(Sender: TObject);

var j:integer; s:string;

reset(vFileInt);

seek(vFileInt,3); //устанавливаем указатель файла в четвёртую позицию (0,1,2,3)

write(vFileInt,j); //перезаписываем четвёртое значение

j:=filePos(vFileInt); //отображаем положение указателя файла после перезаписи.

reset(vFileInt);

while not EOF(vFileInt) do

read(vFileInt,j); //читаем и отображаем файл

closeFile(vFileInt);

Форма после нажатия на вторую кнопку:

чтение и запись файлов в delphi

Компонент TBitBtn

Продолжим знакомиться с компонентами. На вкладке Additional находится компонент TbitBtn.

Это кнопка с расширенным набором свойств.

Познакомимся с некоторыми из них, которые будут важны в данном занятии.

Если обычная кнопка TButton имеет минимальные возможности по формированию своего внешнего вида, то у TbitBtn таких возможностей гораздо больше.

Для записи в поток подготовим кнопу типа TbitBtn и настроим её так, чтобы она имела в конечном итоге вид:

типизированный файл в delphi

Как видно из рисунка, на кнопке появилась пиктограмма (красный кружок), Название кнопки выведено в трёх строках. Изменён цвет шрифта.

Для отображения названия кнопки в нескольких строках посмотрим, как хранятся строки в компоненте memo.

В окне текст отображается в виде отдельных строк. А в свойстве Memo1.Text он хранится в виде:

типизированный файл в delphi

В Object Inspector нельзя добиться вставки этих служебных символов в строку. Они будут воспроизведены как простой текст.

Чтобы служебные символы сыграли свою роль, надо отдельно сформировать строку с их присутствием и записать в свойство Caption.

Удобнее всего это сделать в обработчике создания формы:

procedure TForm1.FormCreate(Sender: TObject);

begin

end;

Цвет символов можно изменить, раскрыв свойство Font, нажав на «+». В выпадающем списке Color можно выбрать нужный цвет символов.

Как видно на рисунке, для кнопки можно организовать подсказку. Для этого надо заполнить свойство Hint (в нашем случае там написано «подсказка») и разрешить её отображать (выставить свойство ShowHint в true).

И наконец, на кнопке отображена небольшая пиктограмма. Она подготавливается заранее в текстовом редакторе и загружается через свойcтво Glyph).

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

Простые и структурированные типы данных.

Под переменные стандартных типов отводится оговоренное количество байт памяти. Под Integer 4 байта, под Double 8 байт, под Char 1 байт и так далее.

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

Совокупность длины переменной и способа интерпретации последовательности байт можно назвать типом данных.

Указанные типы называются простыми типами данных.

По мере развития вычислительной техники и возрастания её возможностей стали вводиться более сложные, структурированные типы данных.

Чтобы работать с типом данных «массив» надо хранить информацию о том, что мы имеем дело с последовательностью байт, где начинается эта

последовательность, указать общее количество байт и на какие «порции» надо делить эту последовательность (указать тип данных).

Обычный массив характерен тем, что длина всех элементов массива одинакова.

В отличие от него, динамический массив может иметь разную мощность по разным измерениям. Например, двумерный динамический массив может содержать по первому измерению 5 элементов, а по второму — 10.

Но тип всех элементов всё равно остаётся одинаковым. Например, Integer.

Тип данных «запись».

Объявляется запись следующим образом:

type tRec=record J:integer, sh:string[20]; ss:string; d:double; ar:array[1..3] of char; ex1,ex2:extended; end;

Нотация обращения к полям записи следующая:

vRec.d:=10.255; vRec.ex1:=456.89; vRec.ex2:=789.023;

Заметим, что полем может служить другая запись!

type tRec1=record JJ:integer, end;

type tRec=record J:integer, sh:string[20]; ss:string; d:double; ar:array[1..3] of char; ex1,ex2:extended; r1:tRec1 end;

Тогда для формирования значения JJ надо записать:

vRec.r1.JJ:=20;

Не прадо ли, очень похоже:

Form1.Memo1.Text ?

Теперь сделаем «петлю» и ещё раз вернёмся к типу «string».

Ещё раз о типе «string».

В типе «string» первые 12 байт служебные. В них указывается длина строки.

Номер кодовой таблицы символов (в какой язык интерпретировать числа, записанные в байтах, составляющих строку). Счётчик ссылок (он увеличивается на 1, когда выполняется s2:=s1, где s1 и s2 — строковые переменные).

Символы пронумерованы по порядку, начиная с 1. К символу номер N можно обратиться, записав s1[5]. Если, например, в s1 хранится слово «строка», то s1[4] вернёт символ «к».

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

Само обращение к данным происходит по ссылке (по адресу, который записан в переменной, объявленной как тип String).

Но и само описание структуры данных переменной сложного типа можно разместить отдельно от самих данных, оставив пере данными только ссылку на область памяти, где хранится описание (тип) структуры данных.

Записи являются как бы предшественниками такого типа данных, как объект.

Понятие «объект» как особый тип данных.

К полям записи обращаются по нотации «переменная_запись»«.»«имя_поля».

Такая же нотация характерна и для объекта. Вспомним, что компоненты Delphi — это объекты, встроенные в IDE (в среду разработки).

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

Объекты содержат не только данные (которые в объекте называются полями), но и процедуры (и функции).

К этим процедурам можно обращаться с помощью всё той же нотации с «точкой», как и к полям с данными.

Например, вспомним, как мы очищали содержимое компонента Memo (удаляли из него весь текст).

Для этого мы использовали процедуру (в объектах встроенные процедуры называются методами!) Clear. Так как ей не нужны параметры, её можно писать без скобок, то есть вместо Clear() записать просто Clear.

Поэтому мы использовали следующее предложение:

Form1.Memo1.Clear;

Сам объект Memo1 создаётся по шаблону (типу) Tmemo.

Более того. Сами типы являются объектами, поставляемыми со средой IDE. В них содержится метод (процедура) Create, называемый конструктором.

Собственно, он и создаёт экземпляр объекта в программе по собственному образу и подобию. Объект — тип может быть только один в пределах видимости IDE. Но своих копий (экземпляров) он может создать сколько угодно (конечно, каждому экземпляру должно быть дано уникальное имя).

В программе экземпляр объекта оформляется как переменная объектного типа.

Здесь объявлены переменная Form1 три переменных типа Tmemo.

var Form1: Tform1; Memo1:TMemo; Memo2:TMemo; Memo3:TMemo; и так далее.

Tform1, Tmemo и другие встроенные в IDE объекты-типы называются Delphi компонентами. При размещении компонента на форму IDE вызывает конструктор объекта-типа автоматически при размещении компонента на форму.

Замечание. На будущее — типы, по которым создаются объекты, в объектном программировании называются «классами (Class)».

В этой небольшой статье, я хотел бы задеть тему Blob полей. Эти поля необходимы для хранения файлов, но многие программисты (а в частности веб-мастера) используют БД не для хранения самих файлов, а для хранения ссылки на них. С одной стороны такой способ удобен, ведь в таком случае мы можем получить быстрый доступ ко всем файлам. Но с другой стороны у Blob полей есть ряд преимуществ:
1) При удалении записи из БД, удаляется и сам файл.
2) Не надо переименовывать файл (когда все файлы лежат в одной папке, им определённо нужны уникальные имена).
3) Ограничение прав доступа, наложенных с помощью БД действуют и на файлы.
И ещё маленькая тележка небольших достоинств.

Стоит признаться я и сам при написании сайтов все картинки (для статей, новостей), хранил в отдельной папке. Насчёт хранения файлов в самой БД я даже и не заморачивался, пока однажды не понадобилось написать приложение на связке MySQL и Delphi.
Найти оптимальный и лёгкий способ оказалось довольно сложно. Просидев конец рабочего дня и пару часов дома я ни нашёл ничего интересного об объёкте TBlobField. В итоге всё таки получилось создать рабочий кусок кода, и как самое главное, я считаю, довольно компактный.
Итак, первый пример у нас будет с использованием компонента ZEOS.
Как установить ZeosDBO можно прочитать здесь: "Установка Zeos".
Создайте новый проект и добавить на форму кнопку, OpenPictureDialog, ZQuery и ZConnection. Настройте ZConnection и ZQuery, создайте процедуру OnClick для кнопки и добавьте в неё следующие.


А теперь разберём этот небольшой код.
Первая строчка вызывает окно выбора файла, который мы хотим сохранить в БД. Во второй строчке указываем SQL запрос, результатом этого запроса будут записи, в которые далее мы запишем выбранный файл. В данном случае это будет запись пользователя (user) с идентификатором (user_id) равным 15. В третьей и четвёртой строчках мы активируем компонент запросов (ZQuery1) и указываем что сейчас мы будем изменять данные.
Самое интересное начинается в строке под номером 5, здесь мы указываем что поле БД `foto`, не что иное как Blob поле (ZQuery1.FieldByName('foto') as TBlobField) и загружаем в него ранее выбранный файл LoadFromFile(OpenPictureDialog1.FileName).
А последняя строчка указывает что редактирование завершено и данные можно изменить.

Использовать новые компоненты dbExpress удобно. Однако прилагательное «новые» приносит не только радость… Решение возникающих проблем бывает затягивается на долгие часы и дни. На помощь Internet увы надеяться не приходится, т.к. информации по dbExpress там ни так много. Одна из этих проблем – работа с BLOB полями. Использовать нативный SQL для работы с BLOB не всегда возможно, поэтому нужно применять другие, альтернативные способы.

Для работы с BLOB полями в Delphi имеется несколько классов:

  • TBlobStream;
  • TClientBlobStream;
  • TBlobField;
  • TGraphicField;
  • TMemoField;

Также сюда можно отнести функцию TCustomClientDataSet.CreateBlobStream, но она реализована посредством класса TClientBlobStream. Классы TGraphicField и TMemoField являются производными от TBlobField. TBlobStream не подходит для работы с dbExpress, а применяется только при манипулировании данным через BDE.

Таким образом для работы с BLOB-полями через dbExpress остаются два ключевых класса: TBlobField и TClientBlobStream. Следовательно возможно два, принципиально различных, варианта доступа к BLOB-полям: через потоки и через свойства объекта. Как указано в справочной системе при работе с BLOB-полями вообще и dbExpress в частности достаточно удобными оказываются переменные типа String.

Действительно, максимальный размер данных хранимых в переменных данного типа составляет 2 Гб, что равняется максимальному размеру BLOB-поля в MySQL (3.23.47). Строки достаточно удобны для работы с потоками, а также существует достаточно много функций для работы с ними. Проблем при работе с BLOB-полями также существует две: чтение данных и их запись. Рассмотрим каждый из возможных вариантов.

Проблема №1. Чтение данных из BLOB-поля

Для работы с BLOB-полями необходимо присвоить свойству TCustomClientDataSet.FetchOnDemand значение True, а также необходимо внимательно изучить свойство Options параметр poFetchBlobsOnDemand. Данные настройки нужны для того, чтобы получать данные из BLOB-поля в клиентское приложение. Загрузить данные можно используя метод FetchBlobs.

Использование потоков

Использование свойства TDataSet.FieldValues

Использование свойства TBlobField.Value

Проблема №2. Запись данных в BLOB-поле.

Использование потоков

Перед созданием потока необходимо обязательно вызывать метод FetchBlobs для загрузки данных из BLOB-поля, иначе возникает ошибка. При работе с полем используя поток, необходимо следовать правилу:

Одна запись – один поток.

Если необходимо обработать новую запись, то поток необходимо создавать заново. Естественно нужно не забывать своевременно уничтожать созданные потоки.

По непонятным причинам данный код не работает. Точнее данные в Stream передаются, но в базу не записываются. При этом никаких ошибок не выдается (может это bug, а может что-то в данном коде не учтено). В связи с этим, если необходимо использование потоков, следует создать промежуточный поток, затем загрузить в него данные, а потом эти данные перебросить в строковую переменную, которую в дальнейшем занести в базу. Этот способ не является самым оптимальным, но работает безотказно.

Использование свойства TDataSet.FieldValues

Использование свойства TBlobField.Value

Вместо заключения

«И зачем все это!?», – спросит внимательный читатель, – «Ведь в начале статьи написано, что данные BLOB-поля неплохо представляются в виде String. Почему просто не записать SQL запрос UPDATE mytable SET blobf=’s’ WHERE где s – строковая переменная, которая может содержать, какие угодно данные?». Действительно, в определенных ситуациях такое решение пригодно, но предположим, что в данной переменной содержится символ < ? >, тогда сервер посчитает ее концом присваемого значения и возникнет ошибка. При использовании вышеописанных способов такого не происходит. Конечно, для исключения подобных ситуаций можно проводить предварительную обработку переменной для замены «запрещенных» символов на другие, но это дополнительная работа. В любом случае, главное: «ВОЗМОЖНОСТЬ ВЫБОРА. Выбора решения поставленной задачи».

В этой статье я хотел бы рассказать как можно в своих приложениях БД использовать данные в Blob-полях. Этой статьей я хотел бы и закончить наш рассказ, про ADO технологию в Delphi для БД MS Access. Пойдем дальше и будем рассматривать другие БД и не много другие технологии. Так вот использование Blob-полей очень выгодно, нигде не потяряется нужный файл, но от них очень сильно растет размер нашей БД. Но это не беда, так как лучше уж размер БД вырастет, чем размер программы, да и еще с возможностью того, что файлы могут потеряться, это я про то, что если мы файлы будем хранить в какой-нибудь папке с программой.

Ну что приступим. Для начала создайте БД в MS Access и какую-нибудь таблицу, и в таблице обязательно должно быть поле с типом данных — Поле OLE-объекта. В этом поле и будут храниться наши файлы, а в точности картинки. Мы будем записывать и считывать картинки.

Как всегда сделаем подключение к нашей БД. Затем на OnCreate нашей формы активируем наш компонент запросов

Не забудьте на нашу форму «положить» компонент TImage куда будет выводится наши картинки и TOpenDialog.

Для записи нашей картинки на событие кнопки — OnClick напишем следующее

Что здесь может быть непонятного, здесь у нас переменная памяти, в которую грузим нашу картинку, а затем при создании записываем ее в БД, указав идентификатор bmWrite, и необходимо сделать преобразование типов TBlobField, то есть мы указываем, что это точно поле с нашими двоичными данными. Запись вроде бы у меня прошла без ошибок, и если открыть нашу БД, то можно увидеть, что в поле, где сохраняли мы картинку написана такое — Двоичные данные. Это означает, что там что-то есть. Далее нам необходимо считать информацию из БД. Для этого на другую кнопку события OnClick пишем

Здесь у меня и TBitmap и TJpegImage, потому что я не знаю какой формат запишет в БД пользователь картинки. Вот для этого необходимо в БД сделать новое поле, где будет храниться наш формат данных, и при записи картинки записывать в это поле расширение нашей картинки, а при считывании проверять формат и в зависимости от него создавать ту или иную переменную. В данном случае я пробывал и с той, и с той у меня все получилось. Если будете использовать TJpegImage, то необходимо в Uses подключить модуль jpeg - для работы с jpg-изображениями. Также можно увидеть здесь, что я ставлю указатель на первую запись в БД с помощью First. Да просто мы записываем и в первой записи будет что-нибудь в любом случае, поэтому я ничего не стал придумывать, кидать грид на форму и получать картинки с выбранной строки, я получаю все время с первой строки, а уже усовершенствовать в ваших силах. Здесь также присутствует переменная памяти, в которую мы считываем данные, указав идентификатор bmRead, что означает, что мы считываем данные. Затем эту переменную загружаем в переменную изображения, ну а затем и в TImage. Как видите ничего сложного нету.

Этой статьей, я хотел бы подвести итоги с работой с Ado технологией в Delphi в БД MS Access. Далее мы рассмотрим как работать с Ado-технологией использую например MS Excel в качестве нашей БД.

Как записать картинки в БД другим способом мы еще рассмотрим, но тут технология ADO уже не причем. Таким способом можно записывать в любые БД картинки, даже не записывать, а формально хранить их там.

Мой проект Delphi 10.4.2 хранит содержимое файла .WAV в поле большого двоичного объекта базы данных SQLite3 с помощью этого кода:

Какой код используется после успешного запроса SELECT * для сохранения содержимого этого поля обратно в файл?

1 ответ

Для этого вы можете использовать SaveToFile TBlobField . Очевидно, это проще всего, если вы настроите постоянные TFields, включая TBlobField в соответствующем поле, в Delphi IDE.

Обновление . Из вашего комментария я понял, что у вас возникли проблемы с соотнесением того, что я сказал, с вашим кодом, поэтому я постараюсь объяснить это как можно яснее.

Когда вы открываете FDQuery с помощью Sql-запроса, если вы не предпринимаете шаги для того, чтобы FDQuery вел себя иначе, он создаст одно поле-потомок TField для каждого столбца в наборе результатов Sql. FireDAC использует метаданные с сервера, чтобы определить, какого потомка TField (например, TIntegerField, TStringField, TBlobField) он создает для каждого столбца Sql. FDQuery освободит поля сразу после того, как вы вызовете .Close () в FDQuery.

Альтернативный способ работы - создание «постоянных» полей, которые продолжают существовать, даже когда FDQuery закрыт. Основная причина для этого заключается в том, чтобы вы знали во время разработки, какой тип TField-потомка используется для каждого столбца набора результатов, и устанавливали определенные поведения (например, формат отображения полей). Чтобы настроить постоянные TFields, вы можете сделать следующее:

  • В среде IDE щелкните правой кнопкой мыши FDQuery и нажмите Fields editor. во всплывающем окне.
  • Затем вы увидите всплывающее окно Fields editor .
  • Щелкните его правой кнопкой мыши и выберите Add all fields в контекстном меню.
  • Затем Fields editor заполнится одним полем для каждого столбца в наборе результатов Sql. (На этом этапе вы также можете вручную добавить вычисляемые поля и поля поиска, если хотите).

Как только все это будет сделано, вы обнаружите, что поля отображаются в инспекторе объектов, каждое с именем компонента, которое основано на комбинации имени FDQuery и имени связанного столбца запроса Sql. Надеюсь, что для вашей колонки, содержащей файлы .WAV, тип поля будет MemoField. Если это так, вы дома и сохнете; в противном случае щелкните правой кнопкой мыши Fields editor , запишите имя поля данных WAV, удалите столбец данных WAV и щелкните правой кнопкой мыши, чтобы вручную создать TMemoField и установить для его свойства FieldName только что отмеченное имя.

Затем вы можете использовать SaveToFile в только что созданном MemoField.

Читайте также: