Getting Started Programming with QML/uk

From Qt Wiki
Jump to navigation Jump to search
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine.
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.
IMPORTANT: The content of this page is outdated. Reason: Broken links, old API Level, English version available?
If you have checked or updated this page and found the content to be suitable, please remove this notice.


Легкий старт з QML

Ласкаво просимо у світ QML та декларативної мови розробки інтерфейсу користувача (ІК). У цьому "Легкому старті", ми створимо простий текстовий редактор, використовуючи QML. Прочитавши цю статтю, Ви будете готові розробляти власні програми за допомогою QML та Qt C+.

QML як засіб створення інтерфейсу користувача

Нам потрібно розробити простий текстовий редактор, за допомогою якого користувач зможе відкрити текстовий файл, виконати певні маніпуляції з текстом, та зберегти результат. Ця стаття складається з двох частин. Перша частина описує створення макету програми, та опис її поведінки. У другій частині йдеться про роботу з файлами, за допомогою Qt C. Завдяки системі мета-об'єктів Qt, функції С+ доступні елементам QML як властивості об'єктів. Об'єднавши QML і Qt C+, можна ефективно відокремити програмну логіку від логіки інтерфейсу користувача.

http://doc.qt.io/qt-4.8/images/qml-texteditor5_editmenu.png

Для того щоб виконати QML-код достатньо запустити програму qmlviewer (знаходиться у каталозі $QTDIR$\bin), передавши їй в якості аргумента QML-файл. Для того, щоб зрозуміти ту частину програми, яка написана на С, читачу достатньо мати базові знання Qt та процесу компіляції проекту.

Розробка програми складається з наступних кроків:

  1. Опис елементів Button і Menu
  2. Релалізація рядка меню
  3. Створення редактора тексту
  4. Оформлення редактора тексту
  5. Доповнення QML за допомогою С++

Опис елементів Button і Menu

Кнопка: базовий компонент

Розпочнемо створення текстового редактора із побудови кнопки. Функціонально, кнопка складається з області, по якій можна натискати мишою і надпису. Кнопка виконує певну дію, коли користувач натискає її.

У QML, базовою візуальною одиницею є елемент Rectangle (прямокутник). Прямокутник має властивості, які визначають його зовнішній вигляд, розміри та положення.

import Qt 4.7

Rectangle{
 id: simplebutton
 width: 150;
 height: 50;
 color: grey;
 Text {
 id: buttonLabel;
 text: "button label";
 anchors.centerIn: parent;
 }
 }

Розглянемо наведений QML-код детально. Спочатку, команда import Qt 4.7 повідомляє програмі qmlviewer про необхідність підключити QML-елементи, які пізніше будуть використовуватись. Цей рядок є обов'язковим для всіх QML-файлів. Зверніть увагу на те, що потрібно обов'язково вказати версію модулів Qt.

Цей простий прямокутник має унікальний ідентифікатор, simplebutton, який задається властивістю id. Властивості елементу Rectangle задаються переліком у такому вигляді: назва властивості, двокрапка, значення. У прикладі сірий колір (grey) задається властивістю color прямокутника. Аналогічно ми прив'язуємо значення розмірів елемента Rectangle (властивості width та height).

Елемент Text - недоступне для редагування текстове поле. Назвемо цей текстовий елемент buttonLabel. Для того, щоб задати текст, який буде відображатися в елементі Text використаємо властивість text. Текстове поле розміщується всередині елемента Rectangle. Вирівнювання будь-якого елемента QML визначається властивістю anchors. У нашому випадку, текст вирівнюється по центральній вертикалі батьківського елемента, який називається simplebutton. Прив'язки (Anchors) можна кріпити до прив'язок інших елементів, що спрощує створення макету.

Збережемо цей код у файл SimpleButton.qml. Відкривши цей файл у програмі qmlviewer, ми побачимо сірий прямокутник з текстом всередині.

http://doc.qt.io/qt-4.8/images/qml-texteditor1_simplebutton.png

Добавимо кнопці чутливість до миші, за допомогою елемента MouseArea та механізму обробки подій QML. Обробка подій QML дуже схожа до механізму сигналів і слотів Qt: при генерації певного сигналу, виликається прикріплений до нього обробник.

 Rectangle{
 id: simplebutton
 

MouseArea{
 id: buttonMouseArea

// прикріпити всі сторони області до сторін прямокутника
 anchors.fill: parent
 // код в обробнику onClicked обробляє натискання кнопок миші.
 onClicked: console.log("Натиснуто кнопку " + buttonLabel.text)
 }
 }

Ми включили елемент MouseArea у означення кнопки simplebutton. "Область миші" задає інтерактивний прямокутник, чутливий до руху миші та натискань її кнопок. У нашій кнопці, ми прикріпили усі сторони MouseArea до меж батьківського елемента simplebutton. Запис anchors.fill означає доступ до спеціальної властивості fill всередині групи властивостей anchors. У QML використовується система розміщення елементів на основі прив'язок, які можна прикріпити до інших елементів, створюючи покращений інтерфейс користувача.

Елемент MouseArea має багато сигналів, які генеруються при тих чи інших діях мишки над областю. Одним з них є onClicked і він викликається при натисканні на одну з кнопок миші, ліву по замовчуванні. Ми можемо прив'язати певну дію до сигналу onClicked, у нашому прикладі console.log() виводить повідомлення про те, що було натиснуто кнопку. Функція console.log() корисна для налагодження роботи програми.

Коду у файлі SimpleButton.qml достатньо для відображення кнопки на екрані та виводу повідомлення при натисненні на цю кнопку.

 Rectangle {
 id:Button
 

property color buttonColor: "lightblue"
 property color onHoverColor: "gold"
 property color borderColor: "white"

signal buttonClick()
 onButtonClick: {
 console.log("Натиснуто кнопку " + buttonLabel.text)
 }

MouseArea{
 onClicked: buttonClick()
 hoverEnabled: true
 onEntered: parent.border.color = onHoverColor
 onExited: parent.border.color = borderColor
 }

// задання кольору кнопки, виходячи із її стану
 color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
 }

Повністю готова кнопка реалізована у файлі Button.qml. Фрагменти коду у цій статті скорочені (позначено трьома крапками). Опущені частини були наведені вище, або ж не мають відношення до поточної тематики.

Елементи QML можуть мати нестандартні властивості (властивості, визначені користувачем). Такі властивості описуються синтаксичною структурою property type name. У наведеному вище фрагменті описано властивість buttonColor типу color, яка дорівнює lightblue (світло-синій колір). Ця властивість пізніше використовується для обчислення кольору кнопки. Зверніть увагу на те, що є можливість задавати значення властивості за допомогою знака рівності =, крім того властивість одного елемента може бути "прив'язана" до властивості іншого елемента певною залежністю (формулою). Нестандартні властивості дозволяють отримати доступ до внутрішніх елементів ззовні області видимості Rectangle. Перелік базових класів QML містить типи int, string, real, особливий тип variant та деякі інші.

Обробники слотів onEntered, onExited змінюють колір границі кнопки: вона стає жовтою при наведені миші.

За допомогою синтаксичної конструкції signal ми описали сигнал buttonClick() у файлі Button.qml. Для усіх сигналів обробники створюються автоматично, їхні назви починаються з префікса on, тобто описаний нами сигнал буде викликати слот onButtonClick.

Тепер у Вас є базові знання оголошення елементів QML, які можуть отримувати повідомлення миші. Ми створили кнопку, яка складається з прямокутника (елемент Rectangle), текстового поля (елемент Text) та області реагування на події миші (елемент MouseRegion). Ідея вкладення одних елементів у інші буде ще неодноразово використана при розробці текстового редактора.

Створена нами кнопка буде використовуватись як компонент для виконання певних дій. У наступному розділі ми створимо елемент меню, який буде складатись з кількох кнопок.

http://doc.qt.io/qt-4.8/images/qml-texteditor1_button.png

Створення меню

На цьому етапі ми уже вміємо створювати елементи і описувати їх поведінку у єдиному QML-файлі. У цьому розділі ми розглянемо імпорт і повторне використання створених компонентів для побудови інших компонентів.

Програмні меню відображають список, кожен елемент якого виконує певну дію. Меню в QML можна створювати різними способами. Для початку зробимо меню, яке складається з кнопок, які виконауть різні дії при натисканні на них. QML-код меню розмістимо у файлі FileMenu.qml.

 import Qt 4.7 підключаємо основні модулі Qt QML
 import "folderName" підключаємо вміст каталогу folderName
 import "script.js" as Script підключаємо Javascript-файл і даємо йому псевдонім Script

Фрагмент коду, наведений вище показує як використовувати ключове слово import для того, щоб використати Javascript- aбо QML-файли з інших каталогів. Оскільки Button.qml знаходиться у тому ж каталозі, що й FileMenu.qml, нам не потрібно його підключати за допомогою import - можна просто створити компонент Button, написавши Button{}, подібно до того як ми оголошували Rectangle{}. Тобто створення нестандартного компонента нічим не відрізняється від створення базових елементів QML.

 Файл FileMenu.qml:

Row{
 anchors.centerIn: parent
 spacing: parent.width/6

Button{
 id: loadButton
 buttonColor: "lightgrey"
 label: "Відкрити"
 }
 Button{
 buttonColor: "grey"
 id: saveButton
 label: "Зберегти"
 }
 Button{
 id: exitButton
 label: "Вийти"
 buttonColor: "darkgrey"

onButtonClick: Qt.quit()
 }
 }

У файлі FileMenu.qml ми оголосили три кнопки Button всередині елемента Row, котрий розміщує свої дочірні елементи вздовж горизонталі. Опис компонента Button знаходиться у файлі Button.qml, який ми розробили у попередньому розділі; він підключається автоматично, оскільки знаходиться у тому ж каталозі, що й FileMenu.qml. Створюючи компонент, ми можемо задати нові значення його властивостям, котрі перекриють значення, взяті з файлу Button.qml.

Кнопка exitButton завершуватиме виконання програми при натисканні на неї. Зверніть увагу на те, що обробник сигналу onButtonClick, визначений у файлі Button.qml, буде викликаний після обробника onButtonClick, заданого у exitButton.

http://doc.qt.io/qt-4.8/images/qml-texteditor1_filemenu.png

Елемент Row знаходиться всередині Rectangle, контейнера, що дає непряму можливість маніпулювання кнопками меню.

Оголошення меню редагування тексту дуже схоже до основного меню, і складається з наступних кнопок: Копіювати, Вставити та Виділити все.

http://doc.qt.io/qt-4.8/images/qml-texteditor1_editmenu.png

Озброївшись здобутими знаннями імпортування на налаштування попередньо створених компонентів, ми тепер можемо скомбінувати створені меню, створити загальну панель меню і перейти до розгляду структурування даних з використанням QML.

Створення панелі меню

Наш текстовий редактор повинен відображати два різні програмні меню на одній панелі, тобто нам потрібно певним чином структуризувати меню. Для структуризації даних та їх відображення у QML використовуються моделі даних та відображення даних.

Використання Моделей даних та Відображень даних

QML має на озброєнні різні моделі та відображення даних. Наша панель меню буде відображати меню у вигляді списку із заголовком, у якому будуть знаходитись назви меню. Список меню описується всередині елемента VisualItemModel, котрий може містити візуальні елементи та імпортовані компоненти ІК. Інші типи моделей можуть містити невізуальні компоненти, проте повинні делегувати функцію відображення даних іншим елементам. Ми оголосили дві візуальні одиниці всередині menuListModel, FileMenu та EditMenu та налаштували обидва меню для відображення. Отож ми отримали три файли меню: FileMenu.qml містить меню управління файлами, EditMenu.qml містить меню маніпулювання текстом, MenuBar.qml містить опис панелі меню.

 VisualItemModel{
 id: menuListModel
 FileMenu{
 width: menuListView.width
 height: menuBar.height
 color: fileColor
 }
 EditMenu{
 color: editColor
 width: menuListView.width
 height: menuBar.height
 }
 }

Для відображення створеної моделі, використаємо елемент ListView. ListView може відображати невізуальні дані, у такому випадку він повинен делегувати їх відображення іншому елементу. Проте наша модель menuListModel містить візуальні дані, тому делегування нам не знадобиться.

ListView{
 id: menuListView

// наступна команда задає розмір панелі меню рівний розміру батьківського вікна
 anchors.fill:parent
 anchors.bottom: parent.bottom
 width:parent.width
 height: parent.height

// модель, з якої беруться дані для відображення
 model: menuListModel

// контроль переключання меню
 snapMode: ListView.SnapOneItem
 orientation: ListView.Horizontal
 boundsBehavior: Flickable.StopAtBounds
 flickDeceleration: 5000
 highlightFollowsCurrentItem: true
 highlightMoveDuration:240
 highlightRangeMode: ListView.StrictlyEnforceRange
 }

Оскільки ListView наслідує елемент Flickable, список реагує на перетягування мишею та інші жести. Останній фрагмент коду встановлює властивості, унаслідувані від Flickable для того, щоб досягти потрібного нам ефекту "струшування" відображення при його русі. Закрема властивість highlightMoveDuration задає тривалість струшування - більше значення highlightMoveDuration спричинятиме повільніше переключання меню.

Елемент ListView зберігає пункти моделі у вигляді індексованого списку і кожен пункт моделі доступний через свій індекс у порядку оголошення. Для того щоб виділити певний елемент списку достатньо змінити значення властивості ListView.currentIndex. Заголовок меню реалізовує цю можливість: він містить дві кнопки, розміщені вздовж горизонтальної лінії, кожна з яких змінює поточне меню при натисканні. Кнопка fileButton змінює поточне меню на меню управління файлами, котре має індекс 0, оскільки оголошене першим. Аналогічно кнопка editButton при натисканні змінює поточне меню на меню маніпулювання текстом.

Прямокутник labelList знаходиться поверх панелі меню, оскільки його властивість z рівна 1. Чим більше значення z, тим "вище" знаходиться елемент. По замовчуванню z дорівнює 0.

Rectangle{
 id: labelList
 
 z: 1
 Row{
 anchors.centerIn: parent
 spacing:40
 Button{
 label: "Файл"
 id: fileButton
 
 onButtonClick: menuListView.currentIndex = 0
 }
 Button{
 id: editButton
 label: "Редагування"
 
 onButtonClick: menuListView.currentIndex = 1
 }
 }
 }

Створену нами панель меню можна перемикати перетягуванням миші або ж натисканням на кнопки заголовка, що робить її використання інтуїтивним та зручним.

http://doc.qt.io/qt-4.8/images/qml-texteditor2_menubar.png

Розробка редактора тексту

Оголошення елемента TextEdit

Кожен текстовий редактор повинен мати текстову область, доступну для редагування. QML-елемент TextEdit дозволяє редагувати багаторядковий текст, чим і відрізняється від елемента Text, котрий не дає можливість користувачу змінювати текст.

 TextEdit{
 id: textEditor
 anchors.fill:parent
 width:parent.width; height:parent.height
 color:"midnightblue"
 focus: true

wrapMode: TextEdit.Wrap

onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
 }

Редактор має власні властивості для встановлення кольору тексту а також для переносу тексту на новий рядок. Елемент TextEdit знаходиться всередині області прокрутки, тому при виході курсора за межі видимості текст буде прокручуватись. Для обчислення необхідності прокручувати текст використовуватиметься функція ensureVisible(). Скрипти у QML пишуться на мові Javascript, і як уже раніше згадувалося, Javascript-файли можна імпортувати у власні QML-файли.

 function ensureVisible®{
 if (contentX >= r.x)
 contentX = r.x;
 else if (contentX+width <= r.x+r.width)
 contentX = r.x+r.width-width;
 if (contentY >= r.y)
 contentY = r.y;
 else if (contentY+height <= r.y+r.height)
 contentY = r.y+r.height-height;
 }

Поєднання компонентів текстового редактора

Нарешті ми готові до створення загального макету нашого текстового редактора за допомогою QML. Редактор складається з двох компонентів: панелі меню та області редагування тексту. Оскільки QML дозволяє повторне використання компонентів, код стає простіший після розбивання його на файли, та імпортування одних файлів у інші. Наш текстовий редактор ділить вікно на дві частини. Третину вікна займає панель меню, решту - область редагування тексту.

 Rectangle{

id: screen
 width: 1000; height: 1000

// властивість partition задає співвідношення між розміром панелі меню та розміром області редагування тексту
 property int partition: height/3

MenuBar{
 id:menuBar
 height: partition
 width:parent.width
 z: 1
 }

TextArea{
 id:textArea
 anchors.bottom:parent.bottom
 y: partition
 color: "white"
 height: partition*2
 width:parent.width
 }
 }

Завдяки імпортуванню раніше розроблених компонентів, наш текстовий редактор виглядає дуже простим. Ми можемо пізніше модифікувати основну програму без будь-яких змін в уже готові компоненти з визначеною поведінкою, що робить процес створення макетів і компонентів ІК надзвичайно швидким і простим.

http://doc.qt.io/qt-4.8/images/qml-texteditor3_texteditor.png

Оформлення текстового редактора

Сховаємо все непотрібне в шухляду

Наш текстовий редактор виглядає занадто просто, спробуємо прикрасити його. Використовуючи QML ми можемо створювати переходи між станами і оживити нашу програму. Панель меню займає третину робочої площі, тому було б добре, якби вона відображалась тільки тоді, коли потрібно.

Одним із способів "позбутися" панелі меню коли вона не потрібна є реалізація "шухляди", яка буде показувати та ховати панель меню при натисканні на неї. Реалізуємо "шухляду" у вигляді тонкого прямокутника, який реагує на натискання кнопки миші. Елемент drawer (шухляда), як і вся програма, має два стани: "шухляда відкрита" та "шухляда закрита", і являє собою тонку прямокутну смужку. Всередині смужки знаходиться елемент Image (картинка), котрий вказує на те, що у центрі прямокутника буде зображено стрілку. При натисканні на смужку, "шухляда" встановлює стан програми через ідентифікатор screen.

 Rectangle{
 id:drawer
 height:15

Image{
 id: arrowIcon
 source: "images/arrow.png"
 anchors.horizontalCenter: parent.horizontalCenter
 }

MouseArea{
 id: drawerMouseArea
 anchors.fill:parent
 onClicked:{
 if (screen.state  "DRAWER_CLOSED"){
                     screen.state = "DRAWER_OPEN"
                 }
                 else if (screen.state  "DRAWER_OPEN"){
 screen.state = "DRAWER_CLOSED"
 }
 }
 
 }
 }

Стан (state) - це всього лиш сукупність налаштувань, і він задається елементом State. Список станів може бути перелічений у властивості states через кому. У нашій програмі задано два стани: DRAWER_CLOSED (шухляда закрита) та DRAWER_OPEN (шухляда відкрита). Для кожного стану за допомогою елемента PropertyChanges задається перелік налаштувань. Для прикладу у стані DRAWER_OPEN є 4 налаштування, котрі роблять наступне: область редагування тексту опускається вниз, для того щоб звільнити місце для панелі меню; шухляда разом із панеллю меню також опускаються на своє місце над областю редагування тексту; стрілка на шухляді зміню свій напрям на протилежний. При закритті шухляди відбуваються зворотні налаштування.

 states:[
 State {
 name: "DRAWER_OPEN"
 PropertyChanges { target: menuBar; y: 0}
 PropertyChanges { target: textArea; y: partition + drawer.height}
 PropertyChanges { target: drawer; y: partition}
 PropertyChanges { target: arrowIcon; rotation: 180}
 },
 State {
 name: "DRAWER_CLOSED"
 PropertyChanges { target: menuBar; y:-height; }
 PropertyChanges { target: textArea; y: drawer.height; height: screen.height- drawer.height }
 PropertyChanges { target: drawer; y: 0 }
 PropertyChanges { target: arrowIcon; rotation: 0 }
 }
 ]

Зміна станів відбувається раптово і часто потребує повільнішого переходу. Переходи між станами визначаються елементом Transition, який може бути прикріплений до властивості transition елемента. Ця властивість може містити довільну кількість елементів переходу, перелічених через кому. Наш текстовий редактор повинен здійснювати переходи між станами DRAWER_OPEN та DRAWER_CLOSED. Для кожного переходу повинен бути визначений початковий стан (from) та кінцевий стан (to), проте у нашому випадку можна використати символ *, щоб вказати, що даний перехід відноситься до усіх змін станів.

Під час переходів ми можемо призначити різні анімації для змін властивостей елементів, які беруть участь у переходах. Наш menuBar змінює позицію від y:0 до y:-partition і ми можемо анімувати цей перехід, використавши елемент NumberAnimation. Ми декларуємо, що властивості цільового елемента (target) будуть змінюватись протягом певного періоду (duration) з використанням певної кривої послаблення (easing). Крива послаблення контролює швидкість анімації а також інтерполює, величину яка змінюється у процесі переходу. Ми використали криву послаблення Easing.OutQuint, яка сповільнює рух у кінці анімації. Детальніше про анімацію можна прочитати у статті QML's Animation.

 transitions: [
 Transition {
 to: "*"
 NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }
 NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
 NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
 }
 ]

Інший спосіб анімування змін властивостей- використання елемента Behavior (поведінка). У той час, коли переходи працюють тільки при зміні станів, елемент Behavior задає анімацію будь-якої зміни значення певної властивості. У нашому текстовому редакторі стрілка повертається, використовуючи анімацію NumberAnimation для властивості rotation. Ця анімація відбувається при будь-якій зміні значення rotation.

 In TextEditor.qml:

Behavior{
 NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
 }

Повертаючись до наших компонентів із розумінням статусів та анімацій, ми можемо покращити їх вигляд. У файлі Button.qml додамо зміну властивостей color та scale при натисненні на кнопку. Зміна кольору анімується за допомогою ColorAnimation, а зміна числових властивостей - використовуючи NumberAnimation. Синтаксичну конструкцію on propertyName наведену нижче зручно використовувати при анімуванні тільки однієї властивості.

In Button.qml:
 

color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
 Behavior on color { ColorAnimation{ duration: 55} }

scale: buttonMouseArea.pressed ? 1.1 : 1.00
 Behavior on scale { NumberAnimation{ duration: 55} }

Окрім того, ми можемо покращити вигляд наших QML-компонентів, надавши їм різних кольорових ефектів, таких як градієнт і прозорість. Для прикладу властивість color варто замінити елементом Gradient. Для того, щоб задати параметри градієнту, достатньо описати кілька елементів GradientStop, кожен з яких являє собою певний колір на шкалі градієнту від 0.0 до 1.0.

 In MenuBar.qml
 gradient: Gradient {
 GradientStop { position: 0.0; color: "#8C8F8C" }
 GradientStop { position: 0.17; color: "#6A6D6A" }
 GradientStop { position: 0.98;color: "#3F3F3F" }
 GradientStop { position: 1.0; color: "#0e1B20" }
 }

Наведений вище градієнт використовується для панелі меню надаючи їй глибини.

Що далі?

Нарешті ми завершили розробку макету ІК простого текстового редактора. Забігаючи наперед, інтерфейс користувача закінчений і ми можемо перейти до реалізації програмної логіки, використовуючи Qt та C+. QML прекрасно виконує роль інструменту для розмежування програмної логіки та дизайну.

http://doc.qt.io/qt-4.8/images/qml-texteditor4_texteditor.png

Доповнення QML за допомогою Qt C.

Завершивши макет нашого текстового редактора, можна перейти до реалізації функціоналу. Використання QML дозволяє створювати програмну логіку використовуючи Qt. Ми можемо створити QML-контекст у програмі, написані на С+ за допомогою Декларативних класів Qt і відобразити наш макет як графічну сцену. Або ж ми можемо експортувати наш С++ код у розширення і використати його у qmlviewer-і. У нашому випадку ми скористаємось другим варіантом: оформимо функції зчитування і збереження файлу у вигляді розширення. Таким чином ми зможемо користуватись нашим текстовим редактором без генерації виконуваного файлу.

Експорт класів С++ для використання у QML

Реалізуємо роботу з файлами у нашому редакторі використовуючи Qt та С+. Зареєстровані класи C+ можуть бути використані у QML. Такий клас повинен бути скомпільований як розширення, а QML-файл повинен знати місце знаходження цього розширення.

Для нашої програми нам потрібно створити наступні елементи:

  1. клас Directory для роботи з каталогами
  2. клас File на базі QObject для імітації списку файлів у каталозі
  3. клас розширення для реєстрації у QML-контексті
  4. файл Qt-проекту для компіляції розширення
  5. файл qmldir, у якому буде зберігатись інформація про місце знаходження розширення для qmlviewer.

Компіляція розширення Qt

Для компілювання розширення потрібно налаштувати файл проекту: вказати всі необхідні файли коду, заголовки, та модулі Qt. Весь код програми повинен знаходитись у каталозі filedialog.

 In cppPlugins.pro:

TEMPLATE = lib
 CONFIG += qt plugin
 QT''= declarative

DESTDIR += ../plugins
 OBJECTS_DIR = tmp
 MOC_DIR = tmp

 TARGET = FileDialog

 HEADERS''= directory.h  file.h  dialogPlugin.h

SOURCES += directory.cpp  file.cpp  dialogPlugin.cpp

In particular, we compile Qt with the declarative module and configure it as a plugin, needing a lib template. We shall put the compiled plugin into the parent's plugins directory. Registering a Class into QML

In dialogPlugin.h:

#include <QDeclarativeExtensionPlugin>

class DialogPlugin : public QDeclarativeExtensionPlugin
 {
 Q_OBJECT

public:
 void registerTypes(const char *uri);

};

Клас нашого розширення наслідуємо від класу QDeclarativeExtensionPlugin, обов'язково реалізовуючи функцію registerTypes(). Файл dialogPlugin.cpp має виглядати наступним чином:

 DialogPlugin.cpp:

#include "dialogPlugin.h"
 #include "directory.h"
 #include "file.h"
 #include <qdeclarative.h>

void DialogPlugin::registerTypes(const char *uri){

 qmlRegisterType<Directory>(uri, 1, 0, "Directory");
 qmlRegisterType<File>(uri, 1, 0,"File");
 }

 Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);

Шаблонна функція qmlRegisterType() реєструє наші класи File та Directory у QML. Цій функції потрібен клас, старша версія, молодша версія та назва класу в QML.

Нам потрібно експортувати розширення, використовуючи макрос Q_EXPORT_PLUGIN2. Зверніть увагу на те, що файлі dialogPlugin.h ми використали макрос Q_OBJECT у на початку класу, тому потрібно перезапустити qmake для проекту щоб згенерувався весь необхідний мета-об'єктний код.

Додавання QML-властивостей до класів С++

У нас є можливість створити QML-елементи і властивості, використовуючи С++ і систему мета-об'єктів Qt. Ми можемо реалізувати властивості, використовуючи слоти та сигнали, поінформувавши Qt про них. Такі властивості можуть потім використовуватись у QML-коді.

Для текстового редактора, ми повинні мати можливість відкривати та зберігати файли. Зазвичай такі можливості зібрані у діалоговому вікні вибору файлу. На щастя ми можемо використати QDir, QFile та QTextStream для реалізації зчитування каталогів та файлів.

 class Directory : public QObject{

 Q_OBJECT

 Q_PROPERTY(int filesCount READ filesCount CONSTANT)
 Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
 Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
 Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

 

Клас Directory використовує систему мета-об'єктів Qt для реєстрації своїх властивостей. Клас для роботи з каталогами експортується як розширення і використовується у QML як елемент Directory. Кожна властивість, позначена макросом Q_PROPERTY будо доступною у QML-файлі.

Макрос Q_PROPERTY повідомляє систему мета-об'єктів Qt про властивість, та функції її зчитування і запису. Для прикладу, властивість filename має тип QString зчитується функцією filename() і записується функцією setFilename(). Окрім того створюється сигнал filenameChanged(), який надсилається при зміні значення властивості. Функції зчитування і запису мають бути оголошені із ключовим словом public.

Аналогічно оголошені інші властивості, відповідно до їх призначення. Властивість filesCount показує кількість файлів у каталозі, fileName - назву поточного вибраного файлу, fileContent - вміст файлу із яким працює користувач.

 Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

Властивість files містить список всіх відфільтрованих файлів у каталозі. Клас Directory відкидає усі файли окрім файлі із розширенням .txt. Клас QDeclarativeListProperty аналог QList у QML. Елементами списку файлів є об'єкти класу File, який тоже наслідуєтсья від QObject.

 class File : public QObject{

 Q_OBJECT
 Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

 
 };


Усі властивості оголошені за допомогою макроса Q_PROPERTY можуть використовуватися з QML-коду. Зверніть увагу, що властивість id оголошувати у класі С++ не потрібно.

 Directory{
 id: directory

 filesCount
 filename
 fileContent
 files

 files[0].name
 }

Оскільки QML використовує синтаксис і структуру Javascript, ми можемо пройтися циклом по списку файлів і отримати інформацію про них. Наприклад, для того щоб отримати назву першого файлу достатньо написати

files[0].name

Звичайні функції С++ також можна зробити доступними з QML. Функції відкриття та збереження файлу реалізовані на С++ і оголошені з макросом Q_INVOKABLE. Проте можна оголосити функції як слоти і вони також будуть доступні з QML

 In Directory.h:

 Q_INVOKABLE void saveFile();
 Q_INVOKABLE void loadFile();

Клас Directory повинен повідомляти інші об'єкти про зміну вмісту каталогу. Використаємо для цього сигнал. Раніше згадувалося, що сигнали у QML мають відповідні обробники, назва яких починається префіксом on. Назвемо сигнал directoryChanged і будемо посилати його при обновлені каталогу. Обновлення каталогу буде спричиняти повторне зчитування списку файлів. Інші QML-елементи можуть отримувати інформацію про оновлення, підключивши свої обробники до сигналу directoryChanged.

Властивості, які являють собою списки варто розглянути детальніше, тому що вони використовують функції зворотнього виклику для доступу до списку та його модифікації. Властивість files має тип QDeclarativeListProperty<File>. Тип File повинен наслідувати QObject. При створенні QDeclarativeListProperty, потрібно передати конструктору вказівники на функції додавання елемента до списку, отримання розміру списку, отримання елемента списку та очищення списку. Також власне список, у якому зберігатимуться дані потрібно передати у вигляді вказівника.

Конструктор QDeclarativeListProperty а також його виклик у конструкторі Directory виглядає наступним чином:

 QDeclarativeListProperty ( QObject* object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )
 QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );

Конструктор передає вказівники на функції, які будуть додавати нові елементи, повертати кількість елементів, повертати елемент з певним індексом а також очищати список. Тільки функція доповнення списку є обов'язкової. Зверніть увагу, що функції, котрі передаються у вигляді вказівників повинні відповідати оголошенням AppendFunction, CountFunction, AtFunction, та ClearFunction.

void appendFiles(QDeclarativeListProperty<File> * property, File * file)
 File* fileAt(QDeclarativeListProperty<File> * property, int index)
 int filesSize(QDeclarativeListProperty<File> * property)
 void clearFilesPtr(QDeclarativeListProperty<File> *property)

Для спрощення діалогу вибору файлу, клас Directory відкидає усі файли окрім тих, що мають розширення .txt, тобто якщо файл не має розширення .txt, він не буде видимий у діалозі. Ще однією важливою функцією елемента є контроль розширення фалу при його збереженні. Елемент Directory використовує QTextStream для того щоб зчитувати і записувати вміст файл.

Використовуючи Directory ми можемо отримати список файлів, взнати скільки файлів є у каталозі, отримати назву файлу і його вміст у вигляді рядки, а також дізнатися при всі зміни вмісту каталогу.

Для компіляції розширення достатньо запустити qmake, передавши йому файл проекта cppPlugins.pro у якості параметра, після цього виконати make для компіляції розширення і переміщення готового файлу у каталог plugins.

Вставка розширення у QML

Програма qmlviewer імпортує файли, які знаходяться у тому ж каталозі, що й програма. Також можна створити файл qmldir і прописати у ньому усі QML-файли, які треба підключити. Файл qmldir може містити посилання на розширення та інші ресурси.

Файл qmldir:

Button ./Button.qml
 FileDialog ./FileDialog.qml
 TextArea ./TextArea.qml
 TextEditor ./TextEditor.qml
 EditMenu ./EditMenu.qml

plugin FileDialog plugins

Розширення, яке ми створили називається FileDialog, як вказано у полі TARGET проекту. Скомпільований файл розширення знаходиться у каталозі plugins.

Інтеграція діалогу вибору файлу у файлове меню.

Елемент FileMenu повинен відображати діалог FileDialog, котрий містить список файлів доступних для редагування при натисканні користувачем. Ми також маємо присвоїти кнопкам створення, відкриття та збереження відповідні дії. Елемент FileMenu містить текстове поле, у якому користувач може ввести ім'я файлу з клавіатури.

Елемент Directory використовується у FileMenu.qml і повідомляє FileDialog про зміни у каталозі. Це повідомлення реалізоване у обробнику сигналу onDirectoryChanged.

 У файлі FileMenu.qml:

Directory{
 id:directory
 filename: textInput.text
 onDirectoryChanged: fileDialog.notifyRefresh()
 }

Для простоти, діалог вибору файлу завжди відображатиметься у програмі і буде містити тільки текстові файли доступні для редагування (файли із розширенням .txt).

У файлі FileDialog.qml:

signal notifyRefresh()
 onNotifyRefresh: dirView.model = directory.files

Елемент FileDialog відображатиме вміст каталогу, зчитуючи його із своєї властивості files. Список файлів використовується як модель елемента GridView, який відображає дані за допомогою делегата у вигляді таблиці. Делегат виконує власне відображення моделі, у нашій програмі діалог вибору файлу буде просто відображати файли у вигляді таблиці з вирівняним по центру текстом. При натисканні на назву файлу, навколо неї з'являтиметься прямокутне виділення. При зміні вмісту каталогу, елементу FileDialog надсилатиметься сигнал notifyRefresh, і список файлів буде зчинуватись заново.

 In FileMenu.qml:

Button{
 id: newButton
 label: "Новий"
 onButtonClick:{
 textArea.textContent = ""
 }
 }
 Button{
 id: loadButton
 label: "Відкрити"
 onButtonClick:{
 directory.filename = textInput.text
 directory.loadFile()
 textArea.textContent = directory.fileContent
 }
 }
 Button{
 id: saveButton
 label: "Зберегти"
 onButtonClick:{
 directory.fileContent = textArea.textContent
 directory.filename = textInput.text
 directory.saveFile()
 }
 }
 Button{
 id: exitButton
 label: "Вийти"
 onButtonClick:{
 Qt.quit()
 }
 }

Тепер можна приєднати кнопки меню управління файлом (FileMenu) до відповідних обробників. Кнопка saveButton передаватиме текст з області редагування тексту TextEdit до властивості fileContent елемента directory, копіюватиме назву файлу із textInput, і зрештою викликатиме функцію saveFile() для зберігання файлу. Кнопка loadButton аналогічно зчитуватиме вміст файлу у область редагування. При натисканні на кнопку newButton відбуватиметься очищення редактора.

Аналогічно, кнопки маніпуляції текстом меню EditMenu прикріплені до відповідних функцій елемента TextEdit для копіювання, вставки та виділення тексту у редакторі

http://doc.qt.io/qt-4.8/images/qml-texteditor5_filemenu.png

Завершення розробки

http://doc.qt.io/qt-4.8/images/qml-texteditor5_newfile.png