#

Ввод, сохранение в текстовый файл и визуализация данных в среде Lazarus (язык pascal)

В данной статье рассмотрим решение основных задач, встречаемых в практике при разработке прикладной программы.
Типовые задачи:
1. Ввести некие данные;
2. Посчитать период времени от начала измерений;
3. Визуализировать их в форме графика;
4. Сохранение в текстовый файл;
5. Загрузка созданного файла и использование этих данных.

Сделано в последней на данный момент версии Lazarus 2.0.8 (в других вервиях возможны незначительное изменения)

Задачей будет ввод одного числового параметра в форму, отображение его на графике в зависимости от текущего времени. Сохранение происходит при закрытии программы, а загрузка — при открытии.

Вначале необходимо создать пустой проект и настроить его в меню Проект -> Параметры проекта
Сохранить проект в отдельной папке: Проект -> Сохранить проект как…
На форму поместить следующие элементы:
1. TLabeledEdit (из вкладки Additional)
2. TButton (из вкладки Standart)
3. TChаrt (из вкладки Chart)

Должно получиться так: (Специально не изменялись названия имен компонентов и текст внутри, хотя это рекомендуется в настоящем проекте)

Ввод, сохранение в текстовый файл и визуализация данных в среде Lazarus (язык pascal)

Далее нужно создать серию для визуализации: Правой кнопкой мыши по графику -> Редактор диаграммы -> Добавить -> График

Ввод, сохранение в текстовый файл и визуализация данных в среде Lazarus (язык pascal)

Теперь нужно создать обработку событий в исходном коде программы.
Понадобятся три процедуры:
1. Инициализации procedure TForm1.FormShow(Sender: TObject); 
В инспекторе объектов выбрать Form1 -> вкладка События -> OnShow (нажать ) Функция автоматически создается средой 
2. Деинициализация procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
В инспекторе объектов выбрать Form1 -> вкладка События -> OnClose (нажать ) Функция автоматически создается средой 
3. Обработка ввода (нажатия на кнопку) procedure TForm1.Button1Click(Sender: TObject);
На форме совершить двойное нажатие на кнопку Button1
Получаем следующий шаблон в разделе 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; 

Задаем хранилище данных в объекте TStrings, для этого в разделе var дописываем объявление переменной Data:TStringList. 

var
  Form1: TForm1;
  Data:TStringList; // Объект для хранения введенных данных в текстовом виде

Теперь необходимо принять протокол сохранения информации. Например, пусть в базе данных хранится дата и время ввода числа в текстовом виде, а через разделитель «;» — само число с плавающей точкой (но разделитель целой и дробной частей будет запятая).
Т.е. формат такой: ДД.ММ.ГГГГ ЧЧ.ММ.СС;ЧИСЛО

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

Задаем алгоритм инициализации:

// Инициализация
procedure TForm1.FormShow(Sender: TObject);
var i: integer; // Объявление локальной переменной целого типа
    x, y : Double; // Хранение текущих координат графика
    str,           // Хранение текущей строки
    time,          // Хранение текущего времени
    start: string; // Хранение начального времени
begin
    Data := TStringList.Create; // Выделяется память под объект Data
    Chart1LineSeries1.Clear; // Очистка данных в графике (серии)
    if FileExists('data.csv') then // Если файл data.csv с данными существует
    begin                          // ... иначе фала data.csv нет (еще нет), а значит загружать ничего не надо
       Data.LoadFromFile('data.csv'); // Используем метод загрузки из файла объекта TStringList. Он заполнит объект строками из файла.
       // Отображение загруженных данных на графике
       for i:=0 to Data.Count-1 do // Проход в цикле по всем записям. Свойство Data.Count показывает число строк в списке. Т.к. первый элемент 0, то надо вычесть 1.
       begin
           // извлечение (парсинг) полей времени и числа из строки
           str := Data.Strings[i]; // Извлекаем текущую (i-тую) строку
           time := copy(str, 1, pos(';',str)-1); // Копирование символов от начала строки до знака ";" (т.е. вычитается 1)
           { Примечание:
             функция copy(str, start, col) - извлекает col символов из строки str, начиная с символа start
             функция pos(ch, str) - находит номер первого символа ch из строке str
           }
           delete(str,1,pos(';',str)); // удаление извлеченных символов вместе с разделителем ";" из строки str
           { Примечание:
             функция delete(str, start, col) - удаляет col символов из строки str, начиная с символа start
             функция pos(ch, str) - находит номер первого символа ch из строке str
           }
           y := StrToFloatDef(str, -1); // Преобразование в число остатка строки str
           { Примечание:
             Так как в строке str осталось только одно число, то можно не использовать функцию copy.
             Если требуется передать еще несколько полей, то можно вставить такую же конструкцию, как:
             par := StrToFloatDef(copy(str, 1, pos(';',str)-1), -1);
             delete(str,1,pos(';',str));
           }
           if i = 0 then start := time; // Если это первое измерение, то записать время в переменную start
           x:=TimeStampToMSecs(DateTimeToTimeStamp(StrToDateTime(time))) - TimeStampToMSecs(DateTimeToTimeStamp(StrToDateTime(start))); // Расчет разницы текущего времени и начального тек. = тек. - нач.
           { Примечание:
             функция StrToDateTime переводит строку со временем в тип дата/время
             функция DateTimeToTimeStamp переводит переменную типа дата/время в спец. тип метки времени
             функция TimeStampToMSecs переводит переменную типа метка времени в миллисекунды
           }
           x := x /(1000*60); // Перевод из мс в минуты
           Chart1LineSeries1.AddXY(x,y); // Добавить координаты x, y в серию и отобразить их на графике
       end;
    end;
end;                

Задаем алгоритм деинициализации:

// Деинициализация
procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
     Data.SaveToFile('data.csv'); // Сохранение данных в файл data.csv
     FreeAndNil(Data);  // Уничтожение объекта Data из памяти
end;     

        Задаем алгоритм обработки ввода:

// Обработка ввода
procedure TForm1.Button1Click(Sender: TObject);
var
x, y: Double; // Хранение текущих координат графика
time: TDateTime; // Хранение текущего времени
str,           // Хранение текущей строки
start: string; // Хранение начального времени
begin
    y := StrToFloatDef(LabeledEdit1.Text, -1); // Конвертирование текста в поле LabeledEdit1 в число, если там некорректное число, то выдаст -1
    if y = -1 then exit; // "Защита от дурака" Если пользователь ввел не корректное число, то ничего не делать и выйти из функции
    // Расчет периода времени от предыдущего измерения в минутах
    time := Now; // Запись текущего времени из системной переменной Now в переменную time
    start := DateTimeToStr(time); // Присваивание переменной старт текущего времени в текстовом виде. Конвертация происходит функцией DateTimeToStr. Если это не первое измерение, то переменную позже переписать.
    if Data.Count > 0 then // Если были предыдущие измерения, то ...
    begin          // нужно рассчитать разницу по времени
         // извлечение (парсинг) полей времени и числа из строки
         str := Data.Strings[0]; // Извлекаем первую строку
         start := copy(str, 1, pos(';',str)-1); // Копирование части строки со временем s start. Здесь происходит её изменение, в случае не первого измерения.
         { Примечание:
           функция copy(str, start, col) - извлекает col символов из строки st, начиная с символа start
           функция pos(ch, st) - находит номер первого символа ch из строке st
         }
    end;
    Data.Add( DateTimeToStr(time)+';'+FloatToStr(y)); // Запись нового времени и введенного значения в виде строки с разделитетем ";" (согласно принятого протокола)
    x := TimeStampToMSecs(DateTimeToTimeStamp(time)) - TimeStampToMSecs(DateTimeToTimeStamp(StrToDateTime(start))); // Расчет разницы текущего времени и начального тек. = тек. - нач.
    { Примечание:
      функция DateTimeToTimeStamp переводит переменную типа дата/время в спец. тип метки времени
      функция TimeStampToMSecs переводит переменную типа метка времени в миллисекунды
      функция StrToDateTime переводит строку со временем в тип дата/время
    }
    x := x /(1000*60); // Перевод из мс в минуты
    Chart1LineSeries1.AddXY(x,y); // Добавление новых координат в серию и отображение графика
end; 

Проверяем программу:

Ввод, сохранение в текстовый файл и визуализация данных в среде Lazarus (язык pascal)

После закрытия программы в папке появится файл data.csv, содержимое которого можно увидеть в Блокноте:

09.05.2020 12:12:54;1
09.05.2020 12:13:00;2
09.05.2020 12:13:03;3
09.05.2020 12:13:06;4
09.05.2020 12:13:08;5
09.05.2020 12:13:10;5
09.05.2020 12:13:12;1
09.05.2020 12:13:12;1
09.05.2020 12:13:16;1
09.05.2020 12:13:19;31
09.05.2020 12:13:21;3
09.05.2020 12:13:21;3
09.05.2020 12:13:26;2
09.05.2020 12:13:43;0,2

Здесь указывается дата, время с точностью до секунд и через разделитель ; введенное число. Обратите внимание, что целая и дробная часть числа разделяется запятой.
Его можно открыть в обычном текстовом редакторе (типа Блокнот) или в редакторе электронных таблиц (типа Excel).

При открытии программы, график должен опять появиться со введенными значениями!

Все работает! 

В качестве дополнения:
Также может понадобиться удалять записи из базы (и графика).
Для этого можно добавить еще одну кнопку и поле ввода номера измерения , а также воспользоваться следующими функциями:
Data.Delete( i ); // удаляет строку с номером i (начиная от 0)
Chart1LineSeries1.Delete( i ); // удаляет i-тый элемент в серии на графике
StrToIntDef(Edit1.Text,-1); // Взять введенный текст из поля Edit1 и перевести его в целое число. Если это не число — возвратить -1 (для проверки ввода)

Для проверки выхода за диапазон, можно использовать: 
Chart1LineSeries1.Count; // возвратит количество элементов в серии на графике.
Data.Count; // возвратит количество строк-записей

Можно удалять только последнюю запись и тогда поле для ввода не потребуется:
Data.Delete( Data.Count-1 ); 
Chart1LineSeries1.Delete( Chart1LineSeries1.Count-1 ); 

При возникновении вопросов, пишите в комментариях здесь или в сообществе ВК!

(с) Роман Исаков