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

    Недавно мы опубликовали серию статей, посвящённых разработке приложений в 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:
    Запущенное приложение на двух девайсах
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 6

      +3
      А можно привести скриншоты того, как выглядит апп не только на Windows?
        0
        Прикрепил фото запущенного приложения на двух Android-девайсах.
          0
          Спасибо!

          Оффтопик. А компонент StyleBook не учитывает гайдлайны платформы? Афаик, радиобаттонов для андроида нет :)
      +1
      От функции GetAnswerID замельтешило в глазах) Наверное, это просто прототип-заготовка
        0
        А когда на Android x86 будет?

        Only users with full accounts can post comments. Log in, please.