
Об удобстве использования связки Qt и QML уже говорилось не раз, поэтому не буду дальше распространяться о плюсах, минусах, а приведу, шаг за шагом, пример простого Qt приложения.
Это будет минималистичное приложение, cмысл его простой — при нажатии на любую клавишу на клавиатуре на экране будет появляться случайное изображение и будет проигрываться, опять же, случайный звуковой эффект.
Пользовательский интерфейс будет полностью реализован на QML, программная часть на Qt.
Для тех, у кого Qt еще не установлен, заходим на страницу загрузки qt.nokia.com/downloads и в разделе "Qt SDK: Complete Development Environment" скачиваем бинарники для своей платформы. И, собственно, устанавливаем.
Запускаем Qt Creator, выбираем пункт меню Файл -> Новый файл или проект. В открывшемся окне Проект Qt C++ -> Gui приложение 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 файл, для этого щелкаем правой клавишей по проекту

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

Мастер создал нам файл, содержащий один элемент 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. Нет предела совершенству, кто знает, может кто-нибудь разовьет ее во что-то более стоящее.
Архив с проектом можно скачать здесь