В последней статье, касающейся App Tethering в Delphi рассмотрены, в основном, теоретические моменты работы с этой технологией и приведены небольшие примеры, которые раскрывают отдельные вопросы использования App Tethering. Недавно появилась возможность применить App Tethering Delphi на практике.
Суть решаемой проблемы следующая: имеется ряд точек на местности, для которых необходимо определить их GPS-координаты и передать полученные данные в приложение под Windows для дальнейшей обработки. При этом приложение должно быть максимально простым в использовании.
Вот здесь-то App Tethering, на мой взгляд подходит как нельзя кстати.
Предварительный алгоритм работы программы можно описать следующим образом:
- Считывает с датчика положения текущие координаты устройства (здесь нам пригодятся навыки работы с TLocationSensor)
- По требованию пользователя сохраняем координаты точки в базу данных (здесь нам пригодятся навыки работы с SQLite в Delphi)
- Показываем пользователю список сохраненных в базе точек
- Пользователь выбирает необходимые ему точки и, используя App Tethering, передает их в Windows-приложение.
Рассмотрим, как реализуется этот простенький алгоритм в Delphi 10.1 Berlin.
Макет приложения Android
Так как наше приложение должно быть максимально простым, то я решил сделать вот такой макет приложения:
В панели сверху три кнопки: слева — кнопка для добавления текущих координат в базу данных, справа — кнопки удаления выбранных в списке точек из базы данных и отправка выбранных записей в Windows-приложение.
Под кнопками расположены два TLayout‘а c компонентами TLabel в которых будут отображаться текущие координаты устройства.
Ниже — список сохраненных в базе данных точек (TListBox).
В качестве библиотеки для работы с базой данных SQLite (а именно её мы и будем использовать в Android) я выбрал LiteDAC три компонента которой вы так же можете увидеть на скриншоте формы — это TLiteConnection, TLiteSQL и TLiteQuery.
Также на форме расположен компонент TLocationSensor для считывания координат местоположения устройства и, конечно, компоненты для работы с App Tethering.
Необходимые разрешения для приложения Android
Чтобы наше приложение могло считывать сведения о текущем местоположении с датчиков и пересылать данные по Сети нам необходимо установить для приложения соответствующие разрешения.
Для того, чтобы установить для Android-приложения необходимые разрешения в Delphi необходимо зайти в настройки проекта:
Project —> Options —> User Permissions
и выбрать настройки: Access Fine Location и Internet
Чтение координат местоположения. Работа с TLocationSensor в Delphi
Для того, чтобы получить текущие координаты я воспользовался событием компонента TLocationSensor OnLocationChanged:
procedure TForm3.LocationSensor1LocationChanged(Sender: TObject; const OldLocation, NewLocation: TLocationCoord2D); begin Label3.Text := GPSToString(NewLocation.Latitude) + 'С'; Label4.Text := GPSToString(NewLocation.Longitude) + 'В'; end;
Функция GPSToString() — вспомогательная функция, которая переводит значение координаты из градусов с десятичными долями в формат ГГ.ММ.SS:
function GPSToString(Degree: double): string; const cGrad = 176; cMin = 39; cSecond = 34; var Grad: integer; Min: integer; Second: double; begin Grad := Trunc(Degree); Min := Trunc((Degree - Grad) * 60); Second := ((Degree - Grad) * 60 - Min) * 60; Result := IntToStr(Grad) + chr(cGrad) + IntToStr(Min) + chr(cMin) + StringReplace(FloatToStrF(Second, ffFixed, 5, 2), ',', '.', [rfReplaceAll]) + chr(cSecond); end;
Получив текущие координаты можно записывать их в базу данных.
База данных SQLite в Android
Для компонента TLiteConnection я установил свойство TLiteConnection.Options.ForceCreateDatabase в значение True и написал следующие обработчики:
перед подключением к БД:
procedure TForm3.LiteConnection1BeforeConnect(Sender: TObject); begin LiteConnection1.Database := TPath.Combine(TPath.GetDocumentsPath, 'pdv_coords.sqlite'); end;
После подключения:
procedure TForm3.LiteConnection1AfterConnect(Sender: TObject); begin LiteConnection1.ExecSQL('CREATE TABLE IF NOT EXISTS [data] (' + ' [id] INTEGER PRIMARY KEY AUTOINCREMENT,' + ' [latitude] DOUBLE,' + ' [longtitude] DOUBLE,' + ' [caption] TEXT);'); RefreshList; end;
Здесь метод RefreshList — вспомогательный и используется для обновления списка сохраненных в БД точек:
procedure TForm3.RefreshList; begin ListBox1.Items.Clear; LiteQuery1.Close; LiteQuery1.SQL.Text := 'SELECT * FROM data'; LiteQuery1.Open; LiteQuery1.First; while not LiteQuery1.Eof do begin PointToList(LiteQuery1.FieldByName('caption').AsString, LiteQuery1.FieldByName('id').AsInteger, LiteQuery1.FieldByName('latitude').AsFloat, LiteQuery1.FieldByName('longtitude').AsFloat); LiteQuery1.Next; end; end;
В свою очередь PointToList() добавляет одну точку в список:
procedure TForm3.PointToList(const Caption: string; ID: integer; Lat, Long: double); var LI: TListBoxItem; begin LI := ListBox1.ListItems[ListBox1.Items.Add(Caption)]; LI.ItemData.Accessory := TListBoxItemData.TAccessory.aDetail; LI.ItemData.Detail := GPSToString(Lat) + 'C ' + GPSToString(Long) + 'В'; LI.Tag:=ID; LI.TagString := Caption + '##' + Lat.ToString + '##' + Long.ToString; end;
Чтобы не создавать лишние объекты и не «дёргать» лишний раз базу данных я решил сразу в элементе списка хранить всю необходимую для дальнейшей работы информацию, а именно:
- в свойстве Tag хранится ID записи из базы данных для того, чтобы можно было удалить её при необходимости
- в свойстве TagString хранится уже сформированная строка для отправки в Windows-приложение с помощью App Tethering.
Так как предполагается, что за раз в списке будет накапливаться не более 50-100 точек, то, думаю, что на потреблении памяти и скорости работы приложения такой подход к хранению данных сильно не скажется.
Соответственно, если человек жмет кнопку «Сохранить» (на верхней панели слева), то срабатывает следующий обработчик OnClick кнопки:
procedure TForm3.Button1Click(Sender: TObject); var PlacemarkCaption: string; begin TDialogservice.InputQuery('Введите текст метки', ['Метка'], [''], procedure(const AResult: TModalResult; const AValues: array of string) begin case AResult of mrOk: begin PlacemarkCaption := AValues[0]; LiteSQL1.SQL.Text := 'INSERT INTO data (latitude, longtitude, caption) VALUES (:latitude, :longtitude, :caption)'; LiteSQL1.ParamByName('latitude').AsFloat := LocationSensor1.Sensor.Latitude; LiteSQL1.ParamByName('longtitude').AsFloat := LocationSensor1.Sensor.Longitude; LiteSQL1.ParamByName('caption').AsString := PlacemarkCaption; LiteSQL1.Execute; PointToList(PlacemarkCaption, LiteSQL1.LastInsertId, Latitude, Longtitude); end; mrCancel: PlacemarkCaption := EmptyStr; end; end ); end;
Про работу с диалогами в FMX и, в частности, в Android я рассказывал в статье «Работа с диалогами InputQuery, InputBox в Delphi 10.1 Berlin» — не забудьте подключить в uses модуль FMX.DialogService.
Если в списке выбраны точки и пользователь нажимает кнопку удалить — срабатывает следующий обработчик:
procedure TForm3.Button2Click(Sender: TObject); var I: integer; Deleted: TArray; begin LiteSQL1.SQL.Text := 'DELETE FROM data WHERE id=:id'; for I := 0 to Pred(ListBox1.Items.Count) do if ListBox1.ListItems[I].IsChecked then Deleted := Deleted + [ListBox1.ListItems[I].Tag]; if Length(Deleted)=0 then Exit; LiteSQL1.Params.ValueCount := Length(Deleted); for I := 0 to High(Deleted) do LiteSQL1.Params[0][I].AsInteger := Deleted[I]; LiteSQL1.Execute(Length(Deleted)); RefreshList; end;
Здесь я пользуюсь пакетными операциями в LiteDAC о которых рассказывал в этой заметке. После того, как сформирован список всех точек, которые необходимо отправить в Windows можно приступать к работе с App Tethering.
Отправляем список координат, используя App Tethering
Приемник данных
Для теста я написал простенькое VCL-приложение суть которого — получить строку и отобразить её в Memo. Внешний вид приложения весьма и весьма аскетичен :)
У компонента TTetheringManager установлены следующие свойства:
- Name = tmVCLManager
- Text = tmVCLManager;
- AllowedAdapters = Network
У компонента TTetheringAppProfile установлены следующие свойства:
- Name = tapVCLProfile
- Text = tapVCLProfile
- Manager = tmVCLManager
- Group = pdv_coords.
Исходный код приёмника данных так же прост и лаконичен, как и интерфейс. Здесь есть всего один обработчик события OnResourceReceived компонента TTetheringAppProfile:
procedure TForm4.tapVCLProfileResourceReceived(const Sender: TObject; const AResource: TRemoteResource); begin if AResource.ResType = TRemoteResourceType.Data then Memo1.Lines.Add(AResource.Value.AsString); end;
Больше в приложении-приемнике ничего нет.
Отправитель данных. Android-приложение
На форме Android-приложения расположены также TTetheringManager и TTetheringAppProfile. Для связывания приложений я решил использовать автоматическое подключение, поэтому установил у компонента TTetheringAppProfile свойство Group точно таким же, как и в VCL, то есть:
- Group = pdv_coords
Чтобы отправить сведения о выбранных в списке точек, для кнопки «Отправить» был определен следующий обработчик OnClick:
procedure TForm3.Button3Click(Sender: TObject); begin if PInfo.ProfileIdentifier=EmptyStr then tamAndroidManager.AutoConnect() else SendPoints; end;
PInfo содержит информацию по профилю в VCL-приложении с которым связывается приложение Android:
type TForm3 = class(TForm) //....// private PInfo: TTetheringProfileInfo; //....// procedure SendPoints; public { Public declarations } end;
Метод SendPoints отправляет информацию о выбранных точках:
procedure TForm3.SendPoints; var I: integer; begin if PInfo.ProfileIdentifier=EmptyStr then Exit; for I := 0 to Pred(ListBox1.Items.Count) do if ListBox1.ListItems[I].IsChecked then tapAndroidProfile.SendString(PInfo, 'point', ListBox1.ListItems[I].TagString) end;
Соответственно, раз мы воспользовались автоматическим подключением к удаленному профилю, то необходимо также обработать событие у менеджера:
procedure TForm3.tamAndroidManagerEndAutoConnect(Sender: TObject); var I, J: integer; begin for I := 0 to Pred(tamAndroidManager.RemoteProfiles.Count) do begin if tamAndroidManager.RemoteProfiles[I].ProfileText = 'tapVCLProfile' then PInfo := tamAndroidManager.RemoteProfiles[I]; end; SendPoints; end;
В этом обработчике мы ищем профиль VCL-приложения и отправляем ему данные о выбранных точках.
Теперь можно запустить оба приложения и проверить их работу.
работа Android-приложения:
После того, как в списке выбраны точки и нажата кнопка «Отправить» в VCL-приложении увидим следующее:
Книжная полка
Описание Подробно рассматривается библиотека FM, позволяющая создавать полнофункциональное программное обеспечение для операционных систем Windows и OS X, а также для смартфонов и планшетных компьютеров, работающих под управлением Android и iOS
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Добрый день! Как происходит «борьба» с GPS-координатами полученными не от датчика GPS? Имеется ввиду, что от компонента TLocationSensor иногда присылает координаты отличные от текущего положения
Здесь, как я понимаю, все зависит от того, какие провайдеры местоположения настроены. В Андроид координаты можно получить с GPS-датчика или определить приблизительно по вышкам сотовой связи — это, опять же насколько я понимаю, Андроид определяет сам. И вот здесь могут координаты отличаться от тех, что присылает GPS-датчик. Сам я с такими погрешностями никак не борюсь, т.к. применительно к моей задачи погрешность даже в 15 метров не критическая
Там могут быть и км… Спасибо за ответ!