Недавно мы опубликовали серию статей, посвящённых разработке приложений в FireMonkey. Тогда мы описали ключевые моменты построения приложения, в том числе создание базы данных, подключение данных с помощью технологии LiveBinding, развёртывание приложения на мобильной платформе. Однако, детально рассматривать ньюансы создания мобильных приложений мы не стали. Во многом это связано с тем, что сам процесс мобильной разработки в Delphi эволюционирует от версии к версии. В частности, в последней XE7 на сегодняшний день версии Delphi, был представлен новый дизайнер форм FireUI Multi-Device Designer. В данной статье с помощью небольшого примера мы рассмотрим, что же из себя представляет FireUI и каким образом с его появлением изменилась методология разработки.
Для этого мы создадим простейшее приложение для тестирования. Пользователю на экран выводится картинка, содержащая вопрос и несколько вариантов ответов. Мы выбрали футбольную тематику (почему бы и нет?).
Можно сказать, что это вариация академической задачи, поэтому в данной статье мы не станем сосредотачиваться на логике приложения. Вместо этого более детально опишем непосредственно процедуру создания приложения.
Наиболее естественным выбором СУБД для мобильных приложений является SQLite. В качестве менеджера БД можно использовать бесплатную версию SQLite Expert или любой другой продукт для работы с SQLite.
База будет содержать единственную таблицу, в которой будет храниться информация о футболитах. SQL-код, с помощью которого создаётся таблица, будет выглядеть примерно так:
CREATE TABLE [Player] (
[Id] INTEGER NOT NULL ON CONFLICT ROLLBACK PRIMARY KEY ON CONFLICT ROLLBACK AUTOINCREMENT,
[PlayerName] CHAR(100) NOT NULL,
[TeamNumber] INTEGER,
[Height] INTEGER,
[Weight] INTEGER,
[Photo] BLOB,
[CountryId] INTEGER);
Собственно, в данной статье мы используем только поля Id, PlayerName и Photo. Остальное – задел на будущее. Заполнить таблицу тестовыми данными мы также сможем с помощью SQLite Expert.
По окончании подготовки базы приступим непосредственно к написанию программы.
В XE7 был несколько изменён подход к созданию FireMonkey-приложений. Теперь между «настольным» и мобильным приложением нет принципиальной разницы. Как следствие, при создании приложения мы выбираем шаблон Multi-Device Application.
Такой проект способен генерировать исполняемые файлы для любой из платформ, поддерживаемых Delphi: Windows, OS X, IOS и Android. Мы сможем выбрать конкретную платформу в ходе создания проекта, а также настроить пользовательский интерфейс для конкретного типа устройств.
Как и в предыдущих версиях мы можем выбрать шаблон главной формы проекта с помощью диалога Select a Multi-Device Application Type.
Для нашего приложения подойдёт шаблон Tabbed.
Прежде всего настроим подключение к БД с помощью FireDAC. Для этого последовательно поместим на форму три компонента – TFDConnection, TFDPhysSQLiteDriverLink и TFDGUIxWaitCursor. Двойной щелчок по компоненту TFDConnection вызывает редактор соединения. Здесь нам следует указать путь к базе данных. Свойству LoginPrompt присвоим значение False.
Для формирования списка вопросов и ответов нам придется сформировать два SQL-запроса. Вызываться эти запросы будут с помощью компонентов TFDQuery.
Первый запрос выведет список из пяти случайных игроков.
SELECT * FROM Player
ORDER BY Random()
Limit 5
Собственно, это и будет список вопросов.
Второй запрос похож на первый с той лишь разницей, что в этот список не будет попадать один из игроков.
SELECT * FROM Player
WHERE Id<> :Id
ORDER BY Random()
Limit 4
Это список ответов, который будет формироваться для каждого из вопросов. Из списка исключен правильный ответ, который будет вставляться вручную.
Давайте внимательно посмотрим на обновлённый дизайнер рабочей области в XE7. Он имеет собственную панель инструментов, расположенную чуть ниже основной. Эта панель содержит две кнопки и два раскрывающихся списка.
Списки, которые были доступны в ХЕ5 / XE6 — были заменены на список стилей и
список представлений. Кнопки для изменения ориентации устройства и для отображения маски устройства.
Первый из списков позволяет выбрать то, как будет выглядеть интерфейс в процессе разработки.
Ко второму списку мы вернёмся чуть позже. А пока разместим на форме необходимые элементы управления и создадим нужный код.
Базовый элемент шаблона TTabControl в нашем приложении будет содержать три вкладки, которые будут переключаться последовательно в процессе теста. Свойству TabPosition присвоим значение None, чтобы пользователь не имел возможности переключать вкладки в произвольном порядке вручную.
На первой вкладке поместим две кнопки. Примерно так, как это показано на рисунке.
Кнопка «Выход» закрывает приложение.
procedure TfMain.btnExitClick(Sender: TObject);
begin
Close;
end;
Кнопка «Новая игра», как несложно догадаться, инициирует процедуру тестирования.
Нажатие кнопки обработаем следующим образом:
procedure TfMain.btnNewGameClick(Sender: TObject);
begin
qPlayer.Close;
qPlayer.Open;
// qPlayer.First;
TrueCount := 0;
TabControl1.TabIndex := 1;
FormAnswers(4);
end;
После нажатия на кнопку активной станет вторая вкладка.
Слева будет выводится изображение игрока, справа на панели pAnswers — список вариантов ответов.
procedure TfMain.FormAnswerArray(Count, qId: integer);
var
aPos, i: integer;
rb: TRadioButton;
BlobStream: TStream;
begin
Randomize;
aPos := Random(Count - 1);
try
BlobStream := qPlayer.CreateBlobStream(qPlayer.FieldByName('Photo'),
TBlobStreamMode.bmRead);
Image1.Bitmap.LoadFromStream(BlobStream);
finally
BlobStream.Free;
end;
qAnswer.Close;
qAnswer.ParamByName('Id').AsInteger := qId;
qAnswer.Open;
i := 0;
while not qAnswer.Eof do
begin
if i > Count then
Break;
try
rb := TRadioButton.Create(pAnswers);
rb.Position.X := 0; // Image1.Position.X +10;
rb.Position.Y := i * 20;
rb.Parent := pAnswers;
pAnswers.InsertComponent(rb);
rb.Size.Width := 250;
finally
end;
if i = aPos then
begin
rb.Text := qPlayerPlayerName.AsString;
rb.Tag := qPlayerId.AsInteger;
end
else
begin
rb.Text := qAnswerPlayerName.AsString;
rb.Tag := qAnswerId.AsInteger;
qAnswer.Next;
end;
Inc(i);
end;
end;
procedure TfMain.ClearRadioButtons;
var
i, j: integer;
begin
for i := pAnswers.ChildrenCount - 1 downto 0 do
begin
if pAnswers.Children[i].ClassNameIs('TRadioButton') then
begin
(pAnswers.Children[i] as TRadioButton).Visible := FAlse;
pAnswers.Children[i].Free;
end;
end;
pAnswers.Repaint;
end;
procedure TfMain.FormAnswers(Count: integer);
begin
ClearRadioButtons;
FormAnswerArray(Count, qPlayerId.AsInteger);
end;
Переменная TrueCount будет содержать количество правильных ответов. Процедура FormAnswers – формирует список ответов.
Как не сложно догадаться из кода, на панель pAnswers добавляются компоненты TRadioButton, свойство Text которых в качестве значения будет иметь фамилию игрока, а свойство Tag – Id. При этом правильный вариант берётся из результов первого запроса (qPlayer), неправильные — из результатов второго (qAnswer). Положение правильного ответа – случайно.
При нажатии кнопки «Далее» вызывается следующий код:
procedure TfMain.btnNextClick(Sender: TObject);
var
answID: integer;
begin
answID := GetAnswerID;
Inc(cnt);
if answID = qPlayerId.AsInteger then
begin
Inc(TrueCount);
ShowMessage('Верно');
end
else
begin
ShowMessage('Не верно');
end;
if qPlayer.RecNo <> 5 then
begin
qPlayer.Next;
FormAnswers(4);
end
else
begin
TabControl1.TabIndex := 2;
end;
// if qPlayer.Eof then
if qPlayer.RecNo = 5 then
begin
lResult.Text := 'Верно ' + IntToStr(TrueCount) + ' из 5';
btnNext.Text := 'Финиш';
end;
end;
Функция GetAnswerID реализована следующим образом.
function TfMain.GetAnswerID: integer;
var
i, j, k: integer;
begin
Result := -1;
for i := tiQuest.ChildrenCount - 1 downto 0 do
begin
if tiQuest.Children[i].ClassNameIs('TTabItemContent') then
begin
for j := tiQuest.Children[i].ChildrenCount - 1 downto 0 do
begin
if tiQuest.Children[i].Children[j].ClassNameIs('TPanel') then
for k := tiQuest.Children[i].Children[j].ChildrenCount - 1 downto 0 do
if tiQuest.Children[i].Children[j].Children[k].ClassNameIs
('TRadioButton') then
if (tiQuest.Children[i].Children[j].Children[k] as TRadioButton).IsChecked
then
begin
Result := (tiQuest.Children[i].Children[j].Children[k]
as TRadioButton).Tag;
Break;
end;
end;
end;
end;
end;
На третьей вкладке просто отображается результат.
Новая методология построения мультиплатформенного приложения в Delphi XE7 сводится к тому, что вначале мы размещаем на форме все необходимые компоненты, используя представления Master View. Затем создаём специальное представление для каждой платформы и типа устройства, для которых мы планируем генерировать проект. И наконец, индивидуально настраиваем каждое из представлений.
Всё, что мы делали до настоящего момента производилось в представлении Master.
Если сейчас мы запустим приложение на выполнение для Windows (целевую платформу, как и ранее, выбираем в Project Manager), то главная форма будет выглядеть примерно так же, как и в дизайнере.
Однако, теперь мы можем выбирать из списка соответствующее представление и настраивать его отдельно. Давайте посмотрим, как это выглядит на практике.
Добавим представление для Windows Desktop, просто выбрав его из списка, и подключим стиль. Для этого используем компонент StyleBook.
Работающее приложение в Windows будет выглядет примерно так.
Так будет выглядеть форма в дизейнере при активном представлении Windows Desktop.
Но для представления Master изменения не произойдут.
Важно понимать, что изменения, производимые в одном из представлений, не отразятся на других. Но если вы меняете представление Master, то такие изменения будут «сквозными». Естественно, что при таком подходе существуют определённые ограничения. В упрощённом виде они сводятся к тому, что во всех представлениях на форме должен быть один и тот же набор компонентов. Вы не сможете удалить компонент из одного представления, оставив его в других. Но вы спокойно можете изменять свойства компонентов независимо в каждом из представлений.
Такой подход вносит определённую гибкость. Давайте посмотрим, как механизм FireUI будет работать при создании мобильного приложения для Android.
Прежде всего, нам необходимо будет осуществить перенос базы на мобильное устройство. Для этого используем Deployment Manager, знакомый нам по предыдущим версиям.
Как и в предыдущих версиях Delphi, нам потребуется обработать событие BeforeConnection компонента FDConnection.
procedure TfMain.FDConnection1BeforeConnect(Sender: TObject);
begin
{$IFDEF ANDROID}
FDConnection1.Params.Values['Database'] := IncludeTrailingPathDelimiter
(System.IOUtils.TPath.GetDocumentsPath) + 'tests.db';
{$ENDIF}
end;
После этого подключим мобильное устройство и создадим соответствующее представление.
Настроим стиль и расположение контролов и запустим приложение на мобильном устройстве. Приложение принимает совершенно иной вид, но, очевидно, что это одно и то же приложение.
Кроме этого, нам нужно изменить обработчик события BeforeConnection компонента FDConnection.
procedure TfMain.FDConnection1BeforeConnect(Sender: TObject);
begin
FDConnection1.Params.Values['Database'] := IncludeTrailingPathDelimiter
(System.IOUtils.TPath.GetDocumentsPath) + 'tests.db';
end;
А для того, чтобы приложение продолжало работать и под Windows, в представлении Windows Desktop просто не будем обрабатывать это событие.
Итак, в общих чертах мы увидели, какие изменения произошли в методике разработки FireMonkey-приложений с появлением FireUI. Теперь речь идёт не о «единой кодовой базе» и нескольких приложениях для разных платформ, а фактически о едином приложении, которое собирается для каждой из платформ.
В следующей статье мы постараемся разобрать «подводные камни», которые могут встречаться в приложениях, поддерживающих разные платформы. Оставайтесь с нами.
Автор: DenisVasilyev