Pull to refresh

Qt + QML на простом примере

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

Это будет минималистичное приложение, cмысл его простой — при нажатии на любую клавишу на клавиатуре на экране будет появляться случайное изображение и будет проигрываться, опять же, случайный звуковой эффект.

Пользовательский интерфейс будет полностью реализован на QML, программная часть на Qt.

Для тех, у кого Qt еще не установлен, заходим на страницу загрузки qt.nokia.com/downloads и в разделе "Qt SDK: Complete Development Environment" скачиваем бинарники для своей платформы. И, собственно, устанавливаем.

Запускаем Qt Creator, выбираем пункт меню Файл -> Новый файл или проект. В открывшемся окне Проект Qt C++ -> Gui приложение Qt, далее кнопка Выбрать.

Qt

В новом окне вводим название проекта, указываем путь к проекту, жмем Далее.

В следующем окне снимаем галочку Создать форму, она нам не пригодиться, Далее. И в последнем просто нажимаем кнопку Завершить. Все, каркас нашего приложения создан.

Qt

Первым делом добавим модуль qt-declarative к нашему проекту, для этого, в файле проекта (4Toddler.pro), к строке

QT += core gui

добавим declarative
QT += core gui declarative

Далее изменим базовый класс для нашего главного окна, заменим QMainWindow на QDeclarativeView и включим заголовочный файл QDeclarativeView
#include <QDeclarativeView>

class MainWindow : public QDeclarativeView
{
  ...
}


От реализации конструктора отрежем, ставшую ненужной, инициализацию базового класса QMainWindow(parent).

Если сейчас собрать и запустить проект то мы увидим пустое окно. Так и должно быть, т.к. мы еще не создали и не инициализировали Qml интерфейс.

Добавим в проект новый QML файл, для этого щелкаем правой клавишей по проекту

image

Добавить новый..., далее выбираем раздел Qt, шаблон Файл Qt QML. Даем ему имя main, затем Далее и Завершить.

image

Мастер создал нам файл, содержащий один элемент Rectangle, он и будет являться основным элементом для нашего пользовательского интерфейса. Добавим несколько новых свойств и зададим их значение

Rectangle
{
  // Идентификатор, по нему будет происходить
  // обращение к свойствам этого элемента
  id: canvas;

  // Цвет фона, черный
  color: "black"

  // Изменять размер под размеры
  // родительского элемента
  anchors.fill: parent

  // Будет получать фокус ввода
  focus: true
}


Пока ничего особенного, просто черный фон. Добавим код загрузки QML файла, чтобы посмотреть, что у нас получилось. Для этого добавим нашему окну новый метод
void MainWindow::Init()
{
  // Путь к папке, содержащей QML файлы
  QString contentPath;

#ifdef QT_DEBUG
  // В отладочной версии это абсолютный путь к папке проекта
  contentPath = "D:/MyProjects/QT/4Toddler";
#else
  // В релизе это путь к папке, в которой расположено приложение
  contentPath = QApplication::applicationDirPath();
#endif

  setFocusPolicy(Qt::StrongFocus);
  // Изменять размеры QML объекта под размеры окна
  // Возможно делать и наоборот,
  // передавая QDeclarativeView::SizeViewToRootObject
  setResizeMode(QDeclarativeView::SizeRootObjectToView);

  // Загрузить QML файл
  setSource(QUrl::fromLocalFile(contentPath + "/main.qml"));
}

Теперь заменим в файле main.cpp строку
int main(int argc, char *argv[])
{
  ...
  w.show();
  ...
}

на
int main(int argc, char *argv[])
{
  ...
  w.showFullScreen();
  ...
}

Окно будет разворачиваться на весь экран. Прежде, чем запускать приложение, давайте добавим кнопку, с помощью которой можно будет закрыть окно. Забегая вперед скажу, кнопок в окне будет две, для того, чтобы не писать несоклько раз один и тот же код добавим к проекту новый QML файл, и назовем его WindowButton.

Элемент WindowButton мы будем использовать повторно, изменяя лишь определенные свойства у каждого экземпляра. Кнопки у нас будут выполнены в виде иконок, каждой из них мы будем задавать путь к файлу иконки и изменять обработчик нажатия левой клавишей мыши. Ниже приведен готовый код элемента с комментариями
Image
{
  // Идентификатор элемента
  id: button

  // Область, обрабатывающая "мышиные" сообщения
  MouseArea
  {
    // Действует в пределах всего
    // элемента Image
    anchors.fill: parent

    id: mouseArea

    // При нажатии вызвать метод callback
    onClicked: callback()
  }
}

Добавим пару кнопок к нашему окну
// Элемент позволяющий
// распологать элементы горизонтально
Row
{
  // Правый край элемента выравнивается
  // по правому краю родительского элемента
  anchors.right: parent.right;
  // Отступ справа, 4 пикселя
  anchors.rightMargin: 4;
  // Верхний край эелемента выравнивается
  // по верхнему краю родительского элемента
  anchors.top: parent.top;
  // Отступ сверху, 4 пикселя
  anchors.topMargin: 4;

  // Отступ между элементами
  spacing: 4

  WindowButton
  {
    // Кнопка возова диалога "О программе"
    id: about;

    // Путь к файлу с изображением
    // в данном случае иконка лежит в той же папке,
    // что и QML файл
    source: "about.png";

    // Метод, который будет вызываться
    // при нажатии на кнопку левой клавишей мыши
    // onClicked: callback()
    function callback()
    {
    }
  }

  WindowButton
  {
    // Кнопка закрытия окна
    id: exit;

    source: "exit.png";

    function callback()
    {
    }
  }
}


Чтобы то, что мы сделали заработало нам осталось реализовать оба метода callback для каждой кнопки. Для закрытия окна мы вызовем метод Quit, который реализуем в классе окна. Для этого в объявление класс добавим
Q_INVOKABLE void Quit();

Затем реализуем этот метод
void MainWindow::Quit()
{
  QApplication::quit();
}

Осталось сделать этот метод видимым из QML. В метод Init добавим одну единствунную строку, которая сделает экземпляр нашего окна видимым в QML
rootContext()->setContextProperty("window", this);

Обращаться к этому объекту мы сможем по имени — window, имя это произвольное. Добавим реализацию для кнопки закрытия окна
function callback()
{
  window.Quit();
}

Обратите внимание, что вызывать можно только те методы, которые объявлены как Q_INVOKABLE, т.е. от же метод Init, главного окна вызвать не удастся.

Готово, запускаем, видим черный экран, все, что сейчас мы можем сделать это закрыть окно, нажав на кнопку exit. Нажали и видим, что состояние кнопки при наведении курсора и при нажатии никак не меняется, выглядит как «неживая». Оживим ее, добавив состояния:
Image
{
  ...
  states:[
    State
    {
      // Произвольное название
      name: "hovered";
      // Указание на то, когда элемент переходит в это состояние
      // в данном случае когда нажата левая кнопка мыши
      when: mouseArea.pressed;
      // Какие свойства будут изменяться в этом состоянии
      // в данном случае это будет прозрачность
      PropertyChanges { target: button; opacity: 1;}
    },
    State
    {
      name: "normal"
      // В это состояние элемент будет переходить
      // когда левая кнопка мыши не нажата
      when: mouseArea.pressed == false;
      PropertyChanges { target: button; opacity: 0.7; }
    }
   ]
}

Элемент может переходить в определенное состояние как автоматически при выполнении условия, указанного в when, так и вручную, путем изменения свойства state.

Запустили, нажали, прозрачность изменяется, уже лучше, но не хватает плавности. Добавим следующий код:
Image
{
  ...
  Behavior on opacity
  {
    // Анимация с шагом в 100 миллисекунд
    // Раз в 100 миллисекунд прозрачность будет изменяться
    // на 0,1
    NumberAnimation { duration: 100 }
  }
}

Behavior очень полезный элемент для создания анимаций, позволяющий указать то как будет меняться указанное свойство, в данном случае прозрачность кнопки.

Запускаем и смотрим, совсем другое дело, плавный переход от полупрозрачного к непрозрачному состоянию.


Окно о программе будет реализовано полностью на QML. Это будет модальное окно, которое будет появляться при нажатии кнопки about. При щелчке левой клавишей мыши в любом месте окна оно будет исчезать. Добавим новый QML файл About.qml в проект.



Я приведу сразу весь код этого окна с пояснениями
// Главный элемент для диалогового окна
Rectangle
{
  id: about

  // Функция для отображения окна
  // изменяет прозрачность главного элемента
  function show()
  {
    about.opacity = 1;
  }

  // Функция для закрытия окна
  function hide()
  {
    about.opacity = 0;
  }
  
  // Прозрачный задний фон
  color: "transparent"
  // Полностью прозрачен по умолчанию
  opacity: 0
  
  // Ширина и высота устанавливаются равными
  // ширине и высоте родительского элемента
  // в данном случае это элемент с id: canvas
  width: parent.width
  height: parent.height

  // Видимым элемент будет считаться если выполняется условие
  // opacity > 0
  visible: opacity > 0

  // Дочерний элемент, создающий полупрозрачный фон
  Rectangle
  {
    anchors.fill: parent

    opacity: 0.5

    color: "gray"
  }

  // Дочерний элемент создающий который является диалогом
  // "О программе..."
  Rectangle
  {
    id: dialog
    
    // Ширина и высота являются фиксированными
    width: 360
    height: 230

    // Координаты верхнего левого угла вычисляются
    // исходя из размеров самого диалога и родителя
    // так, чтобы окно располагалось в центре
    x: parent.width / 2 - dialog.width / 2;
    y: parent.height / 2 - dialog.height / 2;
    // Задаем z индекс таким, чтобы он был
    // больше z тех элементов, которые должны остаться
    // за диалоговым окном
    z: 10

    border.color: "gray"

    Text
    {
      text: "4 Toddler"

      font.bold: true

      font.pixelSize: 22
      
      // Выравнивание элемента по центру
      anchors.horizontalCenter: parent.horizontalCenter
      anchors.verticalCenter: parent.verticalCenter
    }
  }

  Behavior on opacity
  {
    NumberAnimation { duration: 100 }
  }

  MouseArea
  {
    // Элемент полностью заполняет родительский элемент
    anchors.fill: parent;
    
    // При клике в любом месте прячем окно
    onClicked: hide();
  }
}

Для начала хотелось бы обратить внимание на свойство
width: parent.width

Это не просто присвоение ширины, если в процессе отображения будет меняться ширина родительского элемента, то и ширина дочернего будет перерасчитана. Не знаю как вас, а меня в процессе «ковыряния» QML эта особенность приятно удивила. Так же интересна следующая строка:

visible: opacity > 0

Свойство может быть не только задано, но и вычислено.

Осталось добавить диалог и код для его отображения при нажатии кнопки about. В файл Main.qml добавим код, в конце элемента canvas

Rectangle
{
    id: canvas
  ..
  About
  {
    id: aboutDlg
  }
}

Для того, чтобы окно отображалось добавим строку

aboutDlg.show();

в функцию callback кнопки about

WindowButton
{
  id: about;
  ...
  function callback()
  {
    aboutDlg.show();
  }
}

Теперь добавим, собственно основной функционал. Начнем с отображения случайной картинки при нажатии любой клавиши. Картинка будет являться эелементом Image, определим этот элемент в отдельном файле. Добавим в проект файл Block.qml
Image
{
    id: block;

  // Новое свойство объекта, необходимое
  // для изменения состояния при удалении объекта
    property bool remove: false
  // При добавлении объекта
    property bool show: false
  
    opacity: 0;
    fillMode: Image.Stretch;

  states: [
    State
    {
      // Состояние, в которое переходит объект
      // тогда, когда нам нужно его удалить
      name: "remove"; when: remove == true;
      PropertyChanges { target: block; opacity: 0 }
      StateChangeScript { script: block.destroy(1000); }
    },
    State
    {
      // Состояние, в которое переходит объект
      // тогда, когда нам нужно его отобразить
      name: "show"; when: show == true;
      PropertyChanges { target: block; opacity: 1 }
    }
  ]
  
    Behavior on opacity { NumberAnimation { duration: 300 } }
}

При нажатии любой клавиши на клавиатуре будет отображаться блок с произвольной картинкой. Добавим в проект новый файл main.js. В нем, мы определим обработчик нажатия клавиши на клавиатуре.
// Шаблон для создания новых элементов
var component = Qt.createComponent("block.qml");

// Максимальное количество элементов
var maxBlocksCount = 10;

// Массив, в котором будут храниться все эелементы
var blocksArray    = new Array();

// Функция обработчик нажатия клавиши
function handleKey()
{
  // Координата x - случайно число от 0 до ширины окна
  var x = Math.floor(Math.random() * canvas.width);
  // Координата y - случайно число от 0 до ширины окна
  var y = Math.floor(Math.random() * canvas.height);

  // Вызов функции, которая создаст новый элемент
  // с указанными координатами
  createNewBlock(x, y);
}

// Создание нового элемента
function createNewBlock(x, y)
{
  if(component.status != Component.Ready)
  {
    return false;
  }

  // Удалить лишние элементы
  if(blocksArray.length > maxBlocksCount)
  {
    removeAllBlocks();
  }

  var newBlock = component.createObject(canvas);

  if(newBlock == null)
  {
    return false;
  }

  // Путь к файлу иконки доступен через свойство главного
  // окна randomIcon
  var iconFile = window.randomIcon;

  newBlock.source = ("Icons/" + iconFile);

  newBlock.x = x;
  newBlock.y = y;

  // Переводим элемент в состояние show
  newBlock.show = true;

  blocksArray.push(newBlock);

  // Проигрываем случайный звуковой эффект
  window.PlaySound();

  return true;
}

// Удаление всех добавленных элементов
function removeAllBlocks()
{
  for(var i = 0; i < blocksArray.length; ++i)
  {
    blocksArray[i].remove = true;
  }

  while(blocksArray.length != 0)
  {
    blocksArray.pop();
  }
}

Как видно из кода нам еще следует реализовать свойство randomIcon и функицию PlaySound главного окна.

Добавим свойство в объявление класса MainWindow

Q_PROPERTY(QString randomIcon READ RandomIcon)

И объявление функции

QString RandomIcon();

Затем реализацию:

QString MainWindow::RandomIcon()
{
  QStringList iconFilesList;
  QString searchPath = m_ContentPath + "/Icons/";

  QDir directory = QDir(searchPath);
  QStringList filters;
  filters << "*.png";
  directory.setNameFilters(filters);
  // Получаем список файлов с расширением png
  iconFilesList = directory.entryList(QDir::AllEntries);

  // Получаем случайный индекс элемента
  int fileIdx = qrand() % iconFilesList.count();
  
  // Возвращаем название файла
  return iconFilesList.at(fileIdx);
}

Теперь добавим в заголовочный файл функцию для проигрывания звукового эффекта
Q_INVOKABLE void PlaySound();

и реализацию
void MainWindow::PlaySound()
{
  QStringList soundFilesList;
  QDir directory = QDir(m_ContentPath + "/Sounds/");
  QStringList filters;
  filters << "*.wav";
  directory.setNameFilters(filters);

  // Получаем список файлов с расширением wav
  soundFilesList = directory.entryList(QDir::AllEntries);
  // Получаем случайный индекс элемента
  int fileIdx = qrand() % soundFilesList.count();

  // Получаем название файла
  QString soundFile = m_ContentPath + "/Sounds/" + soundFilesList.at(fileIdx);
  // Проигрываем файл
  QSound::play(soundFile);
}

Почти все, осталось добавить обработчик нажатия клавиш в наш корневой элемент вызов фунции создания нового элемента. В начален файла main.qml сделаем видимым наш скрипт в файле main.qml

import Qt 4.7
import "main.js" as Main


и сам обработчик внутри элемента canvas

Rectangle
{
    id: canvas
  ...
  Keys.onPressed: { if(event.isAutoRepeat == false) { Main.handleKey(); } }

На этом все — можем запускать и любоваться.



Как я и обещал программа является простой, но, на мой взгляд, это достаточно интересная «игровая» площадка для тех, кто только начинает изучать QML. Нет предела совершенству, кто знает, может кто-нибудь разовьет ее во что-то более стоящее.

Архив с проектом можно скачать здесь
Tags: qml qt
Hubs: Qt
Total votes 127: ↑120 and ↓7 +113
Comments 14
Comments Comments 14

Popular right now

Top of the last 24 hours