Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
QML Qt / Qml / 1_Qt+QML.doc
Скачиваний:
90
Добавлен:
28.03.2016
Размер:
636.93 Кб
Скачать

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

Учшие приёмы Qt Quick: Компоненты

Блог компании Microsoft Lumia

QML предоставляет удобный способ разбиения кода под названием «Компоненты». Самым простым способом создания компонента, который можно будет в последствии использовать многократно, является добавление нового файла в рабочую директорию главного QML-файла. Example.qml:

import QtQuick 1.0

Rectangle {

}

main.qml:

import QtQuick 1.0

Example {

}

Также, компоненты можно упаковывать как модули (Qt Components являются таким модулем) и публиковать в виде плагинов. Этот пост посвящён использованию компонентов для написания чистого и легко поддерживаемого QML-кода.

Создание новых компонентов

Первый пример показал простоту создания дополнительных компонентов, так что не бойтесь их использовать. Не делайте написание кода, пригодного для многократного использования, своей первоочередной целью. Стремитесь к инкапсулированию деталей реализации и уменьшению связности (decoupling) компонентов. Компоненты должны быть небольшими. Следуя этим правилам вы автоматически придёте к коду, который в последствии можно будет использовать многократно. Давайте посмотрим на этот пример простых аналоговых часов: скачать пример/посмотреть онлайнmain.qml:

import QtQuick 1.0

// Покажем текущее время в аналоговых часах.

Rectangle {

id: root

width: 320

height: 320

property variant now: new Date()

Timer {

id: clockUpdater

interval: 1000 // обновляем часы каждую секунду

running: true

repeat: true

onTriggered: {

root.now = new Date()

}

}

Clock {

id: clock

anchors.centerIn: parent

hours: root.now.getHours()

minutes: root.now.getMinutes()

seconds: root.now.getSeconds()

}

}

Clock.qml:

import QtQuick 1.0

// Аналоговые часы, способные отображать часы, минуты и секунды.

Rectangle {

id: root

width: 262 // минимальная ширина

height: 262 // минимальная высота

// public:

property int hours: 0

property int minutes: 0

property int seconds: 0

// private:

Item {

id: impl

Image {

id: face

source: "images/face.png"

Image {

id: shorthand

source: "images/shorthand.png"

smooth: true

rotation: root.hours * 30

}

Image {

id: longhand

source: "images/longhand.png"

smooth: true

rotation: root.minutes * 6

}

Image {

id: thinhand

source: "images/thinhand.png"

smooth: true

rotation: root.seconds * 6

}

Image {

id: center

source: "images/knob.png"

}

}

}

}

Этот код содержит компонент Clock, который при запуске выглядит так, как показано на скриншоте снизу. Несмотря на то, что он используется в приложении единожды, был смысл выделить его из основного файла. Во-первых, это делает простой и понятной оставшуюся в файле main.qml логику: таймер, обновляющий часы, минуты и секунды компонента Clock — это всё, что разработчику необходимо видеть в main.qml, если он захочет добавить ему функциональности. Во-вторых, наш компонент Clock может не беспокоиться за собственное расположение в окне. Предположим, есть элемент Row, использующий наш компонент Clock N-раз. Если бы в корневом элементе компонента Clock был код 'anchors.fill: parent', мы бы не могли использовать его экземпляры в элементе Row: каждый экземпляр Clock занимал бы весь width_row, вместо width_row / N. Именно поэтому QML запрещает использование большинства якорей (anchors) в элементах, помещаемых в элемент Row. Если мы хотим чтобы наш компонент Clock оставался пригодным для многократного использования, мы не должны строить многочисленные предположения относительно его будущего использования. Резюмируя, корневой элемент компонента не должен содержать якоря к своему родителю или использовать жёстко заданные менеджеры размещения (layouts). В то же время компонент Clock задает фиксированные значения своим длине и ширине. С семантической точки зрения, это размеры нашего компонента по умолчанию. Они не ограничивают использование нашего компонента, ведь размер его экземпляров можно изменить при необходимости. Компоненты с нулевыми размерами считаются невидимыми элементами, поэтому установка ненулевых размеров по умолчанию позволяет избежать глупых ошибок. Также существуют другие, менее очевидные достоинства, такие как создание составных (composed) элементов и инкапсуляция деталей реализации. Создание составного элемента (назовём его ComposedElement) из простых элементов ElementA, ElementB и ElementC упрощает добавление новых свойств и действий к элементам. Мы можем добавить новые элементы ElementD и ElementE к нашему ComposedElement без необходимости изменения ElementA, ElementB или ElementC. Наши простые элементы изолированы друг от друга и, поэтому, не могут просто так сломаться, если один из них вдруг поменяется. Компоненты могут быть разделены на публичную и приватную части, так же как это делается в классах C++ и Java. Публичный API компонента есть сумма всех его свойств и методов, определённых в корневом элементе (включая унаследованные им свойства). Это значит, что такие свойства могут быть изменены, а такие методы могут быть вызваны пользователями компонента. Любое свойство или метод, определённые во вложенном элементе (не корневом), могут считаться полностью приватным API. Это позволяет инкапсулировать детали реализации и должно, в итоге, стать обычным делом при создании компонентов разработчиками. Чтобы доказать пользу от инкапсуляции, мы можем удалить внутренний элемент 'impl' из Clock.qml и запустить приложение вновь (например, через "$ qmiviewer main.qml"). Никаких новых ошибок не будет видно, так как публичный API компонента Clock не был изменён. Это значит, что мы можем свободно менять 'impl', зная, что никаких сторонних эффектов от таких изменений для других компонентов не появится. Мы даже можем расширить эту идею и позволить Clock.qml загружать какой-либо элемент 'impl' динамически, в зависимости от ситуации. Это вводит концепцию полиморфизма в QML; реализация подобного механизма остаётся читателю в качестве упражнения. Если у нас есть хорошо спроектированный, минимальный публичный API для каждого компонента, мы можем сосредоточиться на разработке интерфейсов, а не конкретной реализации.

Соседние файлы в папке Qml