Ввод, сохранение в бинарный файл и визуализация данных в среде Lazarus (язык pascal)
В данной статье рассмотрим решение основных задач, встречаемых в практике при разработке прикладной программы. Для начала нужно ознакомиться с предыдущей статьей, связанной с сохранением данных в текстовый файл. Он удобен для совместимости с другими программами, но занимает слишком много места. Работа с бинарными файлами отличается скоростью обработки, малым размером файла, но требует знания четкой структуры этого файла. Есть отличия и в программных методах работы. Хранение оперативных данных будет осуществляться в динамическом массиве.
Типовые задачи:
1. Ввести некие данные;
2. Визуализировать их в форме графика;
3. Сохранение в бинарный файл;
4. Загрузка созданного файла и использование этих данных;
5. Удаление некорректного значения.
Сделано в последней на данный момент версии Lazarus 2.0.8 (в других вервиях возможны незначительное изменения)
Задачей будет ввод одного числового параметра в форму, отображение его на графике в зависимости от текущего времени. Сохранение происходит при закрытии программы, а загрузка — при открытии.
Вначале необходимо создать пустой проект и настроить его в меню Проект -> Параметры проекта.
Сохранить проект в отдельной папке: Проект -> Сохранить проект как…
На форму поместить следующие элементы:
1. TLabeledEdit (из вкладки Addiitional)
2. TButton (из вкладки Standart) — для ввода (Button1) и удаления (Button2) записи.
3. TChsrt (из вкладки Chart)
Должно получиться так: (Специально не изменялись названия имен компонентов, хотя это рекомендуется в настоящем проекте)
Далее нужно создать серию для визуализации: Правой кнопкой мыши по графику -> Редактор диаграммы -> Добавить -> График
Теперь нужно создать обработку событий в исходном коде программы.
Понадобятся четыре процедуры:
1. Инициализации procedure TForm1.FormShow(Sender: TObject);
В инспекторе объектов выбрать Form1 -> вкладка События -> OnShow (нажать …) Функция автоматически создается средой
2. Деинициализация procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
В инспекторе объектов выбрать Form1 -> вкладка События -> OnClose (нажать …) Функция автоматически создается средой
3. Обработка ввода (нажатия на кнопку Button1) procedure TForm1.Button1Click(Sender: TObject);
На форме совершить двойное нажатие на кнопку Button1
4. Обработка удаления (нажатия на кнопку Button2) procedure TForm1.Button2Click(Sender: TObject);
На форме совершить двойное нажатие на кнопку Button2
Получаем следующий шаблон в разделе implementation
implementation {$R *.lfm} { TForm1 } // Инициализация procedure TForm1.FormShow(Sender: TObject); begin end; // Деинициализация procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin end; // Обработка ввода procedure TForm1.Button1Click(Sender: TObject); begin end; // Удаление последнего измерения procedure TForm1.Button2Click(Sender: TObject); begin end;
Для объявления структуры файла лучше всего обозначить её в виде записи. Для этого объявляется новый тип данных в разделе type кода:
type // запись в базе данных TData = record time:TDateTime; // Поле1 времени Param:Double; // Поле2 параметр // Можно добавлять любое количество параметров с определенным размером end;
Задаем хранилище данных в динамическом массиве нового типа TData, для этого в разделе var дописываем объявление переменной Data:TStringList.
var Form1: TForm1; DB:array of TData; // Динамический массив записей базы данных
Программа будет преобразовывать дату и время в метку времени, затем в миллисекунды, а затем в минуты. Из каждого нового измерения будет вычитаться первое измерение. Таким образом, отсчет времени будет от нуля минут.
Задаем алгоритм инициализации:
// Инициализация procedure TForm1.FormShow(Sender: TObject); var i: integer; // Объявление локальной переменной целого типа rec: TData; // Хранение текущей извлеченной записи из файла start: TDateTime; // Хранение начального времени fData: File of TData; // переменная доступа к файлу определенного типа TData begin Chart1LineSeries1.Clear; // Очистка данных в графике (серии) // Загрузка данных из файла if FileExists('data.dat') then // Если файл data.dat с данными существует begin // ... иначе фала data.dat нет (еще нет), а значит загружать ничего не надо AssignFile(fData,'data.dat'); // Указание имени файла для файловой переменной Reset(fData); // Подключение к файлу для чтения while not Eof(fData) do // Цикл пока не достигнут конец файла begin Read(fData, rec); // Чтение из файла записи в переменную rec (указатель перемещается на следующую) SetLength(DB, Length(DB)+1); // Увеличить размер массива DB на одну запись. { Примечание: Length() - показывает количество записей в массиве } DB[HIGH(DB)].time := rec.time; // Присваивание считанного времени в запись массива DB[HIGH(DB)].Param := rec.Param; // Присваивание считанного параметра в запись массива { Примечание: HIGH() - выдает максимальный индекс в массиве, т.е. Length()-1} // Отображение загруженных данных на графике if Length(DB) = 1 then start := time; // Если это первое измерение, то записать время в переменную start rec.time:=TimeStampToMSecs(DateTimeToTimeStamp(rec.time)) - TimeStampToMSecs(DateTimeToTimeStamp(start)); // Расчет разницы текущего времени и начального тек. = тек. - нач. { Примечание: функция DateTimeToTimeStamp переводит переменную типа дата/время в спец. тип метки времени функция TimeStampToMSecs переводит переменную типа метка времени в миллисекунды } rec.time := rec.time /(1000*60); // Перевод из мс в минуты Chart1LineSeries1.AddXY(rec.time,rec.Param); // Добавить координаты времени и параметра в серию, отобразить их на графике end; CloseFile(fData); // Закрыть (освободить) файл end; end;
Задаем алгоритм деинициализации:
// Деинициализация procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction); var i:Integer; // Объявление локальной переменной целого типа fData: File of TData; // переменная доступа файл определенного типа TData begin // Сохранение данных в файл data.dat AssignFile(fData,'data.dat'); // Указание имени файла для файловой переменной Rewrite(fData); // Подключение к файлу для записи (если был файл, то он будет перезаписан) if Length(DB)>0 then // Если есть записи в базе, то ... begin for i:=0 to HIGH(DB) do // Перебрать все элементы массива DB begin Write(fData, DB[i]); // ... и записать их в файл end; end; CloseFile(fData); // Закрыть (освободить) файл DB:=nil; // Уничтожение объекта DB из памяти end;
Задаем алгоритм обработки ввода:
// Обработка ввода procedure TForm1.Button1Click(Sender: TObject); var x, y: Double; // Хранение текущих координат графика time: TDateTime; // Хранение текущего времени start: TDateTime; // Хранение начального времени begin y := StrToFloatDef(LabeledEdit1.Text, -1); // Конвертирование текста в поле LabeledEdit1 в число, если там некорректное число, то выдаст -1 if y = -1 then exit; // "Защита от дурака" Если пользователь ввел не корректное число, то ничего не делать и выйти из функции. (считается, что числа будут больше 0) // Расчет периода времени от предыдущего измерения в минутах time := Now; // Запись текущего времени из системной переменной Now в переменную time start := time; // Присваивание переменной старт текущего времени. Если это не первое измерение, то переменную позже переписать. if Length(DB) > 0 then // Если были предыдущие измерения, то ... begin start := DB[0].time; // Копирование времени первого измерения в start. Здесь происходит её изменение, в случае не первого измерения. end; // добавление новой записи SetLength(DB, Length(DB)+1); // выделение памяти DB[HIGH(DB)].Param:=y; // Запись параметра DB[HIGH(DB)].time:=time; // Запись текущего времени // Отображение измерения x := TimeStampToMSecs(DateTimeToTimeStamp(time)) - TimeStampToMSecs(DateTimeToTimeStamp(start)); // Расчет разницы текущего времени и начального тек. = тек. - нач. { Примечание: функция DateTimeToTimeStamp переводит переменную типа дата/время в спец. тип метки времени функция TimeStampToMSecs переводит переменную типа метка времени в миллисекунды } x := x /(1000*60); // Перевод из мс в минуты Chart1LineSeries1.AddXY(x,y); // Добавление новых координат в серию и отображение графика end;
Задаем алгоритм удаления записи:
// Удаление последнего измерения procedure TForm1.Button2Click(Sender: TObject); begin if Length(DB)=0 then exit; // Если в базе нет записей, то завершить функцию SetLength(DB, Length(DB)-1); // Установка размера массива на одну запись меньше (удаление последней) Chart1LineSeries1.Delete(Chart1LineSeries1.Count-1); //Удалить последнюю точку на графике по индексу {Примечание: Chart1LineSeries1.Count - возвращает количество точек на графике} end;
Проверяем программу:
После закрытия программы в папке появится файл data.dat, содержимое которого нельзя будет разобрать в текстовых редакторах.
При открытии программы, график должен опять появиться со введенными значениями!
Все работает!
При возникновении вопросов, пишите в комментариях здесь или в сообществе ВК!
(с) Роман Исаков