Pull to refresh

Знакомство с FireUI

Reading time7 min
Views15K
Недавно мы опубликовали серию статей, посвящённых разработке приложений в 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. Теперь речь идёт не о «единой кодовой базе» и нескольких приложениях для разных платформ, а фактически о едином приложении, которое собирается для каждой из платформ.

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

Update 1:
Запущенное приложение на двух девайсах
Tags:
Hubs:
Total votes 12: ↑8 and ↓4+4
Comments6

Articles

Information

Website
www.embarcadero.com
Registered
Employees
Unknown
Location
США