Getting Started Programming with QML/uk: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
 
(11 intermediate revisions by 4 users not shown)
Line 1: Line 1:
[toc align_right="yes" depth="2"]
{{Cleanup | reason=Auto-imported from ExpressionEngine.}}
{{Outdated | reason=Broken links, old API Level, English version available?}}
 


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


Ласкаво просимо у світ QML та декларативної мови розробки інтерфейсу користувача (ІК). У цьому "Легкому старті", ми створимо простий текстовий редактор, використовуючи QML. Прочитавши цю статтю, Ви будете готові розробляти власні програми за допомогою QML та Qt C+''.
Ласкаво просимо у світ QML та декларативної мови розробки інтерфейсу користувача (ІК). У цьому "Легкому старті", ми створимо простий текстовий редактор, використовуючи QML. Прочитавши цю статтю, Ви будете готові розробляти власні програми за допомогою QML та Qt C+''.
<br />h2. QML як засіб створення інтерфейсу користувача
 
<br />p. Нам потрібно розробити простий текстовий редактор, за допомогою якого користувач зможе відкрити текстовий файл, виконати певні маніпуляції з текстом, та зберегти результат. Ця стаття складається з двох частин. Перша частина описує створення макету програми, та опис її поведінки. У другій частині йдеться про роботу з файлами, за допомогою Qt C. Завдяки системі мета-об'єктів Qt, функції С''+ доступні елементам QML як властивості об'єктів. Об'єднавши QML і Qt C+'', можна ефективно відокремити програмну логіку від логіки інтерфейсу користувача.
== QML як засіб створення інтерфейсу користувача ==
<br />p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor5_editmenu.png]]
Нам потрібно розробити простий текстовий редактор, за допомогою якого користувач зможе відкрити текстовий файл, виконати певні маніпуляції з текстом, та зберегти результат. Ця стаття складається з двох частин. Перша частина описує створення макету програми, та опис її поведінки. У другій частині йдеться про роботу з файлами, за допомогою Qt C. Завдяки системі мета-об'єктів Qt, функції С''+ доступні елементам QML як властивості об'єктів. Об'єднавши QML і Qt C+'', можна ефективно відокремити програмну логіку від логіки інтерфейсу користувача.
<br />p. Для того щоб виконати QML-код достатньо запустити програму ''qmlviewer'' (знаходиться у каталозі $QTDIR$\bin), передавши їй в якості аргумента QML-файл. Для того, щоб зрозуміти ту частину програми, яка написана на С, читачу достатньо мати базові знання Qt та процесу компіляції проекту.
 
<br />p. Розробка програми складається з наступних кроків:<br /># Опис елементів ''Button'' і ''Menu''<br /># Релалізація рядка меню<br /># Створення редактора тексту<br /># Оформлення редактора тексту<br /># Доповнення QML за допомогою С''+
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor5_editmenu.png|http://doc.qt.io/qt-4.8/images/qml-texteditor5_editmenu.png]]
 
Для того щоб виконати QML-код достатньо запустити програму ''qmlviewer'' (знаходиться у каталозі $QTDIR$\bin), передавши їй в якості аргумента QML-файл. Для того, щоб зрозуміти ту частину програми, яка написана на С, читачу достатньо мати базові знання Qt та процесу компіляції проекту.
 
Розробка програми складається з наступних кроків:
# Опис елементів ''Button'' і ''Menu''
# Релалізація рядка меню
# Створення редактора тексту
# Оформлення редактора тексту
# Доповнення QML за допомогою С++


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


<code><br />import Qt 4.7
<code>
import Qt 4.7


Rectangle{<br /> id: simplebutton<br /> width: 150;<br /> height: 50;<br /> color: grey;<br /> Text {<br /> id: buttonLabel;<br /> text: &quot;button label&amp;quot;;<br /> anchors.centerIn: parent;<br /> }<br /> }<br /></code>
Rectangle{
id: simplebutton
width: 150;
height: 50;
color: grey;
Text {
id: buttonLabel;
text: "button label";
anchors.centerIn: parent;
}
}
</code>


Розглянемо наведений QML-код детально. Спочатку, команда ''import Qt 4.7'' повідомляє програмі ''qmlviewer'' про необхідність підключити QML-елементи, які пізніше будуть використовуватись. Цей рядок є обов'язковим для всіх QML-файлів. Зверніть увагу на те, що потрібно обов'язково вказати версію модулів Qt.
Розглянемо наведений QML-код детально. Спочатку, команда ''import Qt 4.7'' повідомляє програмі ''qmlviewer'' про необхідність підключити QML-елементи, які пізніше будуть використовуватись. Цей рядок є обов'язковим для всіх QML-файлів. Зверніть увагу на те, що потрібно обов'язково вказати версію модулів Qt.
Line 26: Line 49:
Цей простий прямокутник має унікальний ідентифікатор, ''simplebutton'', який задається властивістю ''id''. Властивості елементу ''Rectangle'' задаються переліком у такому вигляді: назва властивості, двокрапка, значення. У прикладі сірий колір (''grey'') задається властивістю ''color'' прямокутника. Аналогічно ми прив'язуємо значення розмірів елемента ''Rectangle'' (властивості ''width'' та ''height'').
Цей простий прямокутник має унікальний ідентифікатор, ''simplebutton'', який задається властивістю ''id''. Властивості елементу ''Rectangle'' задаються переліком у такому вигляді: назва властивості, двокрапка, значення. У прикладі сірий колір (''grey'') задається властивістю ''color'' прямокутника. Аналогічно ми прив'язуємо значення розмірів елемента ''Rectangle'' (властивості ''width'' та ''height'').


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


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


p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_simplebutton.png]]
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor1_simplebutton.png|http://doc.qt.io/qt-4.8/images/qml-texteditor1_simplebutton.png]]


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


<code><br /> Rectangle{<br /> id: simplebutton<br />
<code>
Rectangle{
id: simplebutton


MouseArea{<br /> id: buttonMouseArea
MouseArea{
id: buttonMouseArea


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


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


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


<code><br /> Rectangle {<br /> id:Button<br />
<code>
Rectangle {
id:Button


property color buttonColor: &quot;lightblue&amp;quot;<br /> property color onHoverColor: &quot;gold&amp;quot;<br /> property color borderColor: &quot;white&amp;quot;
property color buttonColor: "lightblue"
property color onHoverColor: "gold"
property color borderColor: "white"


signal buttonClick()<br /> onButtonClick: {<br /> console.log(&quot;Натиснуто кнопку &quot; + buttonLabel.text)<br /> }
signal buttonClick()
onButtonClick: {
console.log("Натиснуто кнопку " + buttonLabel.text)
}


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


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


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


Елементи QML можуть мати нестандартні властивості (властивості, визначені користувачем). Такі властивості описуються синтаксичною структурою ''property'' ''type'' ''name''. У наведеному вище фрагменті описано властивість ''buttonColor'' типу ''color'', яка дорівнює ''lightblue'' (світло-синій колір). Ця властивість пізніше використовується для обчислення кольору кнопки. Зверніть увагу на те, що є можливість задавати значення властивості за допомогою знака рівності =, крім того властивість одного елемента може бути &quot;прив'язана&amp;quot; до властивості іншого елемента певною залежністю (формулою). Нестандартні властивості дозволяють отримати доступ до внутрішніх елементів ззовні області видимості ''Rectangle''. Перелік базових класів QML містить типи ''int'', ''string'', ''real'', особливий тип ''variant'' та деякі &quot;інші&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativebasictypes.html.
Елементи QML можуть мати нестандартні властивості (властивості, визначені користувачем). Такі властивості описуються синтаксичною структурою ''property'' ''type'' ''name''. У наведеному вище фрагменті описано властивість ''buttonColor'' типу ''color'', яка дорівнює ''lightblue'' (світло-синій колір). Ця властивість пізніше використовується для обчислення кольору кнопки. Зверніть увагу на те, що є можливість задавати значення властивості за допомогою знака рівності =, крім того властивість одного елемента може бути "прив'язана" до властивості іншого елемента певною залежністю (формулою). Нестандартні властивості дозволяють отримати доступ до внутрішніх елементів ззовні області видимості ''Rectangle''. Перелік базових класів QML містить типи ''int'', ''string'', ''real'', особливий тип ''variant'' та деякі [http://doc.qt.io/qt-4.8/qdeclarativebasictypes.html інші].


Обробники слотів ''onEntered'', ''onExited'' змінюють колір границі кнопки: вона стає жовтою при наведені миші.
Обробники слотів ''onEntered'', ''onExited'' змінюють колір границі кнопки: вона стає жовтою при наведені миші.
Line 68: Line 117:
Створена нами кнопка буде використовуватись як компонент для виконання певних дій. У наступному розділі ми створимо елемент меню, який буде складатись з кількох кнопок.
Створена нами кнопка буде використовуватись як компонент для виконання певних дій. У наступному розділі ми створимо елемент меню, який буде складатись з кількох кнопок.


p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_button.png]]
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor1_button.png|http://doc.qt.io/qt-4.8/images/qml-texteditor1_button.png]]


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


<code><br /> import Qt 4.7 підключаємо основні модулі Qt QML<br /> import &quot;folderName&amp;quot; підключаємо вміст каталогу folderName<br /> import &quot;script.js&amp;quot; as Script підключаємо Javascript-файл і даємо йому псевдонім Script<br /></code>
<code>
import Qt 4.7 підключаємо основні модулі Qt QML
import "folderName" підключаємо вміст каталогу folderName
import "script.js" as Script підключаємо Javascript-файл і даємо йому псевдонім Script
</code>


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


<code><br /> Файл FileMenu.qml:
<code>
Файл FileMenu.qml:


Row{<br /> anchors.centerIn: parent<br /> spacing: parent.width/6
Row{
anchors.centerIn: parent
spacing: parent.width/6


Button{<br /> id: loadButton<br /> buttonColor: &quot;lightgrey&amp;quot;<br /> label: &quot;Відкрити&amp;quot;<br /> }<br /> Button{<br /> buttonColor: &quot;grey&amp;quot;<br /> id: saveButton<br /> label: &quot;Зберегти&amp;quot;<br /> }<br /> Button{<br /> id: exitButton<br /> label: &quot;Вийти&amp;quot;<br /> buttonColor: &quot;darkgrey&amp;quot;
Button{
id: loadButton
buttonColor: "lightgrey"
label: "Відкрити"
}
Button{
buttonColor: "grey"
id: saveButton
label: "Зберегти"
}
Button{
id: exitButton
label: "Вийти"
buttonColor: "darkgrey"


onButtonClick: Qt.quit()<br /> }<br /> }<br /></code>
onButtonClick: Qt.quit()
}
}
</code>


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


p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_filemenu.png]]
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor1_filemenu.png|http://doc.qt.io/qt-4.8/images/qml-texteditor1_filemenu.png]]


Елемент ''Row'' знаходиться всередині ''Rectangle'', контейнера, що дає непряму можливість маніпулювання кнопками меню.
Елемент ''Row'' знаходиться всередині ''Rectangle'', контейнера, що дає непряму можливість маніпулювання кнопками меню.
Line 98: Line 170:
Оголошення меню редагування тексту дуже схоже до основного меню, і складається з наступних кнопок: Копіювати, Вставити та Виділити все.
Оголошення меню редагування тексту дуже схоже до основного меню, і складається з наступних кнопок: Копіювати, Вставити та Виділити все.


p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor1_editmenu.png]]
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor1_editmenu.png|http://doc.qt.io/qt-4.8/images/qml-texteditor1_editmenu.png]]


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


<code><br /> VisualItemModel{<br /> id: menuListModel<br /> FileMenu{<br /> width: menuListView.width<br /> height: menuBar.height<br /> color: fileColor<br /> }<br /> EditMenu{<br /> color: editColor<br /> width: menuListView.width<br /> height: menuBar.height<br /> }<br /> }<br /></code>
<code>
VisualItemModel{
id: menuListModel
FileMenu{
width: menuListView.width
height: menuBar.height
color: fileColor
}
EditMenu{
color: editColor
width: menuListView.width
height: menuBar.height
}
}
</code>


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


<code><br />ListView{<br /> id: menuListView
<code>
ListView{
id: menuListView


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


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


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


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


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


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


<code><br />Rectangle{<br /> id: labelList<br /> <br /> z: 1<br /> Row{<br /> anchors.centerIn: parent<br /> spacing:40<br /> Button{<br /> label: &quot;Файл&amp;quot;<br /> id: fileButton<br /> <br /> onButtonClick: menuListView.currentIndex = 0<br /> }<br /> Button{<br /> id: editButton<br /> label: &quot;Редагування&amp;quot;<br /> <br /> onButtonClick: menuListView.currentIndex = 1<br /> }<br /> }<br /> }<br /></code>
<code>
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
}
}
}
</code>


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


p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor2_menubar.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor2_menubar.png]]
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor2_menubar.png|http://doc.qt.io/qt-4.8/images/qml-texteditor2_menubar.png]]


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


<code><br /> TextEdit{<br /> id: textEditor<br /> anchors.fill:parent<br /> width:parent.width; height:parent.height<br /> color:&quot;midnightblue&amp;quot;<br /> focus: true
<code>
TextEdit{
id: textEditor
anchors.fill:parent
width:parent.width; height:parent.height
color:"midnightblue"
focus: true


wrapMode: TextEdit.Wrap
wrapMode: TextEdit.Wrap


onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)<br /> }<br /></code>
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
}
</code>


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


<code><br /> function ensureVisible®{<br /> if (contentX &gt;= r.x)<br /> contentX = r.x;<br /> else if (contentX+width &lt;= r.x+r.width)<br /> contentX = r.x+r.width-width;<br /> if (contentY &gt;= r.y)<br /> contentY = r.y;<br /> else if (contentY+height &lt;= r.y+r.height)<br /> contentY = r.y+r.height-height;<br /> }<br /></code>
<code>
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;
}
</code>


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


<code><br /> Rectangle{
<code>
Rectangle{


id: screen<br /> width: 1000; height: 1000
id: screen
width: 1000; height: 1000


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


MenuBar{<br /> id:menuBar<br /> height: partition<br /> width:parent.width<br /> z: 1<br /> }
MenuBar{
id:menuBar
height: partition
width:parent.width
z: 1
}


TextArea{<br /> id:textArea<br /> anchors.bottom:parent.bottom<br /> y: partition<br /> color: &quot;white&amp;quot;<br /> height: partition*2<br /> width:parent.width<br /> }<br /> }<br /></code>
TextArea{
id:textArea
anchors.bottom:parent.bottom
y: partition
color: "white"
height: partition*2
width:parent.width
}
}
</code>


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


p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor3_texteditor.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor3_texteditor.png]]
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor3_texteditor.png|http://doc.qt.io/qt-4.8/images/qml-texteditor3_texteditor.png]]


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


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


<code><br /> Rectangle{<br /> id:drawer<br /> height:15
<code>
Rectangle{
id:drawer
height:15


Image{<br /> id: arrowIcon<br /> source: &quot;images/arrow.png&amp;quot;<br /> anchors.horizontalCenter: parent.horizontalCenter<br /> }
Image{
id: arrowIcon
source: "images/arrow.png"
anchors.horizontalCenter: parent.horizontalCenter
}


MouseArea{<br /> id: drawerMouseArea<br /> anchors.fill:parent<br /> onClicked:{<br /> if (screen.state  &amp;quot;DRAWER_CLOSED&amp;quot;)&amp;#123;
MouseArea{
                     screen.state = &amp;quot;DRAWER_OPEN&amp;quot;
id: drawerMouseArea
                 &amp;#125;
anchors.fill:parent
                 else if (screen.state  &quot;DRAWER_OPEN&amp;quot;){<br /> screen.state = &quot;DRAWER_CLOSED&amp;quot;<br /> }<br /> }<br /> <br /> }<br /> }<br /></code>
onClicked:{
if (screen.state  "DRAWER_CLOSED"){
                     screen.state = "DRAWER_OPEN"
                 }
                 else if (screen.state  "DRAWER_OPEN"){
screen.state = "DRAWER_CLOSED"
}
}
}
}
</code>


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


<code><br /> states:[<br /> State {<br /> name: &quot;DRAWER_OPEN&amp;quot;<br /> PropertyChanges { target: menuBar; y: 0}<br /> PropertyChanges { target: textArea; y: partition + drawer.height}<br /> PropertyChanges { target: drawer; y: partition}<br /> PropertyChanges { target: arrowIcon; rotation: 180}<br /> },<br /> State {<br /> name: &quot;DRAWER_CLOSED&amp;quot;<br /> PropertyChanges { target: menuBar; y:<s>height; }<br /> PropertyChanges { target: textArea; y: drawer.height; height: screen.height</s> drawer.height }<br /> PropertyChanges { target: drawer; y: 0 }<br /> PropertyChanges { target: arrowIcon; rotation: 0 }<br /> }<br /> ]<br /></code>
<code>
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 }
}
]
</code>


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


Під час переходів ми можемо призначити різні анімації для змін властивостей елементів, які беруть участь у переходах. Наш ''menuBar'' змінює позицію від ''y'':0 до ''y'':<s>''partition'' і ми можемо анімувати цей перехід, використавши елемент ''NumberAnimation''. Ми декларуємо, що властивості цільового елемента (''target'') будуть змінюватись протягом певного періоду (''duration'') з використанням певної кривої послаблення (''easing''). Крива послаблення контролює швидкість анімації а також інтерполює, величину яка змінюється у процесі переходу. Ми використали криву послаблення ''Easing.OutQuint'', яка сповільнює рух у кінці анімації. Детальніше про анімацію можна прочитати у статті &quot;QML's Animation&amp;quot;:http://doc.qt.nokia.com/4.7/qdeclarativeanimation.html.
Під час переходів ми можемо призначити різні анімації для змін властивостей елементів, які беруть участь у переходах. Наш ''menuBar'' змінює позицію від ''y'':0 до ''y'':-''partition'' і ми можемо анімувати цей перехід, використавши елемент ''NumberAnimation''. Ми декларуємо, що властивості цільового елемента (''target'') будуть змінюватись протягом певного періоду (''duration'') з використанням певної кривої послаблення (''easing''). Крива послаблення контролює швидкість анімації а також інтерполює, величину яка змінюється у процесі переходу. Ми використали криву послаблення ''Easing.OutQuint'', яка сповільнює рух у кінці анімації. Детальніше про анімацію можна прочитати у статті [http://doc.qt.io/qt-4.8/qdeclarativeanimation.html QML's Animation].
<br /><code><br /> transitions: [<br /> Transition {<br /> to: &quot;*&quot;<br /> NumberAnimation { target: textArea; properties: &quot;y, height&amp;quot;; duration: 100; easing.type:Easing.OutExpo }<br /> NumberAnimation { target: menuBar; properties: &quot;y&amp;quot;; duration: 100; easing.type: Easing.OutExpo }<br /> NumberAnimation { target: drawer; properties: &quot;y&amp;quot;; duration: 100; easing.type: Easing.OutExpo }<br /> }<br /> ]<br /></code>
<br />p. Інший спосіб анімування змін властивостей</s> використання елемента ''Behavior'' (поведінка). У той час, коли переходи працюють тільки при зміні станів, елемент ''Behavior'' задає анімацію будь-якої зміни значення певної властивості. У нашому текстовому редакторі стрілка повертається, використовуючи анімацію ''NumberAnimation'' для властивості ''rotation''. Ця анімація відбувається при будь-якій зміні значення ''rotation''.


<code><br /> In TextEditor.qml:
<code>
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 }
}
]
</code>


Behavior{<br /> NumberAnimation{property: &quot;rotation&amp;quot;;easing.type: Easing.OutExpo }<br /> }<br /></code>
Інший спосіб анімування змін властивостей- використання елемента ''Behavior'' (поведінка). У той час, коли переходи працюють тільки при зміні станів, елемент ''Behavior'' задає анімацію будь-якої зміни значення певної властивості. У нашому текстовому редакторі стрілка повертається, використовуючи анімацію ''NumberAnimation'' для властивості ''rotation''. Ця анімація відбувається при будь-якій зміні значення ''rotation''.
 
<code>
In TextEditor.qml:
 
Behavior{
NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
}
</code>


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


<code><br />In Button.qml:<br />
<code>
In Button.qml:


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


scale: buttonMouseArea.pressed ? 1.1 : 1.00<br /> Behavior on scale { NumberAnimation{ duration: 55} }<br /></code>
scale: buttonMouseArea.pressed ? 1.1 : 1.00
Behavior on scale { NumberAnimation{ duration: 55} }
</code>


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


<code><br /> In MenuBar.qml<br /> gradient: Gradient {<br /> GradientStop { position: 0.0; color: &quot;#8C8F8C&amp;quot; }<br /> GradientStop { position: 0.17; color: &quot;#6A6D6A&amp;quot; }<br /> GradientStop { position: 0.98;color: &quot;#3F3F3F&amp;quot; }<br /> GradientStop { position: 1.0; color: &quot;#0e1B20&amp;quot; }<br /> }<br /></code>
<code>
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" }
}
</code>


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


Нарешті ми завершили розробку макету ІК простого текстового редактора. Забігаючи наперед, інтерфейс користувача закінчений і ми можемо перейти до реалізації програмної логіки, використовуючи Qt та C+''. QML прекрасно виконує роль інструменту для розмежування програмної логіки та дизайну.
Нарешті ми завершили розробку макету ІК простого текстового редактора. Забігаючи наперед, інтерфейс користувача закінчений і ми можемо перейти до реалізації програмної логіки, використовуючи Qt та C+''. QML прекрасно виконує роль інструменту для розмежування програмної логіки та дизайну.
<br />p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor4_texteditor.png]]
 
<br />h2. Доповнення QML за допомогою Qt C.
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor4_texteditor.png|http://doc.qt.io/qt-4.8/images/qml-texteditor4_texteditor.png]]
<br />p. Завершивши макет нашого текстового редактора, можна перейти до реалізації функціоналу. Використання QML дозволяє створювати програмну логіку використовуючи Qt. Ми можемо створити QML-контекст у програмі, написані на С''+ за допомогою Декларативних класів Qt і відобразити наш макет як графічну сцену. Або ж ми можемо експортувати наш С++ код у розширення і використати його у qmlviewer-і. У нашому випадку ми скористаємось другим варіантом: оформимо функції зчитування і збереження файлу у вигляді розширення. Таким чином ми зможемо користуватись нашим текстовим редактором без генерації виконуваного файлу.
 
== Доповнення QML за допомогою Qt C. ==
Завершивши макет нашого текстового редактора, можна перейти до реалізації функціоналу. Використання QML дозволяє створювати програмну логіку використовуючи Qt. Ми можемо створити QML-контекст у програмі, написані на С''+ за допомогою Декларативних класів Qt і відобразити наш макет як графічну сцену. Або ж ми можемо експортувати наш С++ код у розширення і використати його у qmlviewer-і. У нашому випадку ми скористаємось другим варіантом: оформимо функції зчитування і збереження файлу у вигляді розширення. Таким чином ми зможемо користуватись нашим текстовим редактором без генерації виконуваного файлу.


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


Для нашої програми нам потрібно створити наступні елементи:<br /># клас ''Directory'' для роботи з каталогами<br /># клас ''File'' на базі QObject для імітації списку файлів у каталозі<br /># клас розширення для реєстрації у QML-контексті<br /># файл Qt-проекту для компіляції розширення<br /># файл ''qmldir'', у якому буде зберігатись інформація про місце знаходження розширення для ''qmlviewer''.
Для нашої програми нам потрібно створити наступні елементи:
# клас ''Directory'' для роботи з каталогами
# клас ''File'' на базі QObject для імітації списку файлів у каталозі
# клас розширення для реєстрації у QML-контексті
# файл Qt-проекту для компіляції розширення
# файл ''qmldir'', у якому буде зберігатись інформація про місце знаходження розширення для ''qmlviewer''.


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


p4. Для компілювання розширення потрібно налаштувати файл проекту: вказати всі необхідні файли коду, заголовки, та модулі Qt. Весь код програми повинен знаходитись у каталозі ''filedialog''.
Для компілювання розширення потрібно налаштувати файл проекту: вказати всі необхідні файли коду, заголовки, та модулі Qt. Весь код програми повинен знаходитись у каталозі ''filedialog''.
 
<code>
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
</code>
 
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
 
<code>
In dialogPlugin.h:
 
#include <QDeclarativeExtensionPlugin>
 
class DialogPlugin : public QDeclarativeExtensionPlugin
{
Q_OBJECT
 
public:
void registerTypes(const char *uri);
 
};
</code>
 
Клас нашого розширення наслідуємо від класу ''QDeclarativeExtensionPlugin'', обов'язково реалізовуючи функцію registerTypes(). Файл ''dialogPlugin.cpp'' має виглядати наступним чином:
 
<code>
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);
</code>
 
Шаблонна функція ''qmlRegisterType()'' реєструє наші класи ''File'' та ''Directory'' у QML. Цій функції потрібен клас, старша версія, молодша версія та назва класу в QML.
 
Нам потрібно експортувати розширення, використовуючи макрос ''Q_EXPORT_PLUGIN2''. Зверніть увагу на те, що файлі ''dialogPlugin.h'' ми використали макрос Q_OBJECT у на початку класу, тому потрібно перезапустити ''qmake'' для проекту щоб згенерувався весь необхідний мета-об'єктний код.
 
==== Додавання QML-властивостей до класів С++ ====
У нас є можливість створити QML-елементи і властивості, використовуючи С++ і систему мета-об'єктів Qt. Ми можемо реалізувати властивості, використовуючи слоти та сигнали, поінформувавши Qt про них. Такі властивості можуть потім використовуватись у QML-коді.
 
Для текстового редактора, ми повинні мати можливість відкривати та зберігати файли. Зазвичай такі можливості зібрані у діалоговому вікні вибору файлу. На щастя ми можемо використати QDir, QFile та QTextStream для реалізації зчитування каталогів та файлів.
 
<code>
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 )
 
</code>
 
Клас ''Directory'' використовує систему мета-об'єктів Qt для реєстрації своїх властивостей. Клас для роботи з каталогами експортується як розширення і використовується у QML як елемент ''Directory''. Кожна властивість, позначена макросом ''Q_PROPERTY'' будо доступною у QML-файлі.
 
Макрос ''Q_PROPERTY'' повідомляє систему мета-об'єктів Qt про властивість, та функції її зчитування і запису. Для прикладу, властивість ''filename'' має тип ''QString'' зчитується функцією ''filename()'' і записується функцією ''setFilename()''. Окрім того створюється сигнал ''filenameChanged()'', який надсилається при зміні значення властивості. Функції зчитування і запису мають бути оголошені із ключовим словом ''public''.
 
Аналогічно оголошені інші властивості, відповідно до їх призначення. Властивість ''filesCount'' показує кількість файлів у каталозі, ''fileName'' - назву поточного вибраного файлу, ''fileContent'' - вміст файлу із яким працює користувач.
 
<code>
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
</code>
 
Властивість ''files'' містить список всіх відфільтрованих файлів у каталозі. Клас ''Directory'' відкидає усі файли окрім файлі із розширенням .txt. Клас ''QDeclarativeListProperty'' аналог ''QList'' у QML. Елементами списку файлів є об'єкти класу File, який тоже наслідуєтсья від QObject.
 
<code>
class File : public QObject{
 
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)


<code><br /> In cppPlugins.pro:
};
</code>


TEMPLATE = lib<br /> CONFIG ''= qt plugin<br /> QT''= declarative


DESTDIR ''= ../plugins<br /> OBJECTS_DIR = tmp<br /> MOC_DIR = tmp
Усі властивості оголошені за допомогою макроса Q_PROPERTY можуть використовуватися з QML-коду. Зверніть увагу, що властивість ''id'' оголошувати у класі С++ не потрібно.
<br /> TARGET = FileDialog
<br /> HEADERS''= directory.h  file.h  dialogPlugin.h


SOURCES += directory.cpp  file.cpp  dialogPlugin.cpp<br /></code>
<code>
Directory{
id: directory


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.<br />Registering a Class into QML
filesCount
filename
fileContent
files


<code><br />In dialogPlugin.h:
files[0].name
}
</code>


#include &lt;QtDeclarative/QDeclarativeExtensionPlugin&amp;gt;
Оскільки QML використовує синтаксис і структуру Javascript, ми можемо пройтися циклом по списку файлів і отримати інформацію про них. Наприклад, для того щоб отримати назву першого файлу достатньо написати


class DialogPlugin : public QDeclarativeExtensionPlugin<br /> {<br /> Q_OBJECT
<code>
files[0].name
</code>


public:<br /> void registerTypes(const char *uri);
Звичайні функції С++ також можна зробити доступними з QML. Функції відкриття та збереження файлу реалізовані на С++ і оголошені з макросом Q_INVOKABLE. Проте можна оголосити функції як слоти і вони також будуть доступні з QML


};<br /></code>
<code>
In Directory.h:


p4. Клас нашого розширення наслідуємо від класу ''QDeclarativeExtensionPlugin'', обов'язково реалізовуючи функцію registerTypes(). Файл ''dialogPlugin.cpp'' має виглядати наступним чином:
Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();
</code>


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


#include &quot;dialogPlugin.h&amp;quot;<br /> #include &quot;directory.h&amp;quot;<br /> #include &quot;file.h&amp;quot;<br /> #include &lt;QtDeclarative/qdeclarative.h&amp;gt;
Властивості, які являють собою списки варто розглянути детальніше, тому що вони використовують функції зворотнього виклику для доступу до списку та його модифікації. Властивість ''files'' має тип ''QDeclarativeListProperty<File>''. Тип ''File'' повинен наслідувати ''QObject''. При створенні ''QDeclarativeListProperty'', потрібно передати конструктору вказівники на функції додавання елемента до списку, отримання розміру списку, отримання елемента списку та очищення списку. Також власне список, у якому зберігатимуться дані потрібно передати у вигляді вказівника.


void DialogPlugin::registerTypes(const char '''uri){
Конструктор ''QDeclarativeListProperty'' а також його виклик у конструкторі ''Directory'' виглядає наступним чином:
<br /> qmlRegisterType&amp;lt;Directory&amp;gt;(uri, 1, 0, &quot;Directory&amp;quot;);<br /> qmlRegisterType&amp;lt;File&amp;gt;(uri, 1, 0,&quot;File&amp;quot;);<br /> }
<br /> Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);<br /></code>
<br />p. Шаблонна функція ''qmlRegisterType()'' реєструє наші класи ''File'' та ''Directory'' у QML. Цій функції потрібен клас, старша версія, молодша версія та назва класу в QML.
<br />p. Нам потрібно експортувати розширення, використовуючи макрос ''Q_EXPORT_PLUGIN2''. Зверніть увагу на те, що файлі ''dialogPlugin.h'' ми використали макрос Q_OBJECT у на початку класу, тому потрібно перезапустити ''qmake'' для проекту щоб згенерувався весь необхідний мета-об'єктний код.
<br />h4. Додавання QML-властивостей до класів С++
<br />p. У нас є можливість створити QML-елементи і властивості, використовуючи С++ і систему мета-об'єктів Qt. Ми можемо реалізувати властивості, використовуючи слоти та сигнали, поінформувавши Qt про них. Такі властивості можуть потім використовуватись у QML-коді.
<br />p. Для текстового редактора, ми повинні мати можливість відкривати та зберігати файли. Зазвичай такі можливості зібрані у діалоговому вікні вибору файлу. На щастя ми можемо використати QDir, QFile та QTextStream для реалізації зчитування каталогів та файлів.
<br /><code><br /> class Directory : public QObject{
<br /> Q_OBJECT
<br /> Q_PROPERTY(int filesCount READ filesCount CONSTANT)<br /> Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)<br /> Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)<br /> Q_PROPERTY(QDeclarativeListProperty&amp;lt;File&amp;gt; files READ files CONSTANT )
<br /> …<br /></code>
<br />p. Клас ''Directory'' використовує систему мета-об'єктів Qt для реєстрації своїх властивостей. Клас для роботи з каталогами експортується як розширення і використовується у QML як елемент ''Directory''. Кожна властивість, позначена макросом ''Q_PROPERTY'' будо доступною у QML-файлі.
<br />p. Макрос ''Q_PROPERTY'' повідомляє систему мета-об'єктів Qt про властивість, та функції її зчитування і запису. Для прикладу, властивість ''filename'' має тип ''QString'' зчитується функцією ''filename()'' і записується функцією ''setFilename()''. Окрім того створюється сигнал ''filenameChanged()'', який надсилається при зміні значення властивості. Функції зчитування і запису мають бути оголошені із ключовим словом ''public''.
<br />p. Аналогічно оголошені інші властивості, відповідно до їх призначення. Властивість ''filesCount'' показує кількість файлів у каталозі, ''fileName'' - назву поточного вибраного файлу, ''fileContent'' - вміст файлу із яким працює користувач.
<br /><code><br /> Q_PROPERTY(QDeclarativeListProperty&amp;lt;File&amp;gt; files READ files CONSTANT )<br /></code>
<br />p. Властивість ''files'' містить список всіх відфільтрованих файлів у каталозі. Клас ''Directory'' відкидає усі файли окрім файлі із розширенням .txt. Клас ''QDeclarativeListProperty'' аналог ''QList'' у QML. Елементами списку файлів є об'єкти класу File, який тоже наслідуєтсья від QObject.
<br /><code><br /> class File : public QObject{
<br /> Q_OBJECT<br /> Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
<br /> …<br /> };<br /></code>


<br />p. Усі властивості оголошені за допомогою макроса Q_PROPERTY можуть використовуватися з QML-коду. Зверніть увагу, що властивість ''id'' оголошувати у класі С++ не потрібно.
<code>
<br /><code><br /> Directory{<br /> id: directory
QDeclarativeListProperty ( QObject* object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )
<br /> filesCount<br /> filename<br /> fileContent<br /> files
QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );
<br /> files[0].name<br /> }<br /></code>
</code>
<br />p. Оскільки QML використовує синтаксис і структуру Javascript, ми можемо пройтися циклом по списку файлів і отримати інформацію про них. Наприклад, для того щоб отримати назву першого файлу достатньо написати
<br /><code><br />files[0].name<br /></code>
<br />p. Звичайні функції С++ також можна зробити доступними з QML. Функції відкриття та збереження файлу реалізовані на С++ і оголошені з макросом Q_INVOKABLE. Проте можна оголосити функції як слоти і вони також будуть доступні з QML
<br /><code><br /> In Directory.h:
<br /> Q_INVOKABLE void saveFile&amp;amp;#40;&amp;#41;;<br /> Q_INVOKABLE void loadFile&amp;amp;#40;&amp;#41;;<br /></code>
<br />p. Клас ''Directory'' повинен повідомляти інші об'єкти про зміну вмісту каталогу. Використаємо для цього сигнал. Раніше згадувалося, що сигнали у QML мають відповідні обробники, назва яких починається префіксом ''on''. Назвемо сигнал ''directoryChanged'' і будемо посилати його при обновлені каталогу. Обновлення каталогу буде спричиняти повторне зчитування списку файлів. Інші QML-елементи можуть отримувати інформацію про оновлення, підключивши свої обробники до сигналу ''directoryChanged''.
<br />p. Властивості, які являють собою списки варто розглянути детальніше, тому що вони використовують функції зворотнього виклику для доступу до списку та його модифікації. Властивість ''files'' має тип ''QDeclarativeListProperty&amp;lt;File&amp;gt;''. Тип ''File'' повинен наслідувати ''QObject''. При створенні ''QDeclarativeListProperty'', потрібно передати конструктору вказівники на функції додавання елемента до списку, отримання розміру списку, отримання елемента списку та очищення списку. Також власне список, у якому зберігатимуться дані потрібно передати у вигляді вказівника.
<br />p. Конструктор ''QDeclarativeListProperty'' а також його виклик у конструкторі ''Directory'' виглядає наступним чином:
<br /><code><br /> QDeclarativeListProperty ( QObject''' object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )<br /> QDeclarativeListProperty&amp;lt;File&amp;gt;( this, &amp;m_fileList, &amp;appendFiles, &amp;filesSize, &amp;fileAt, &amp;clearFilesPtr );<br /></code>


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


<code><br />void appendFiles(QDeclarativeListProperty&amp;lt;File&amp;gt; * property, File * file)<br /> File* fileAt(QDeclarativeListProperty&amp;lt;File&amp;gt; * property, int index)<br /> int filesSize(QDeclarativeListProperty&amp;lt;File&amp;gt; * property)<br /> void clearFilesPtr(QDeclarativeListProperty&amp;lt;File&amp;gt; *property)<br /></code>
<code>
void appendFiles(QDeclarativeListProperty<File> * property, File * file)
File* fileAt(QDeclarativeListProperty<File> * property, int index)
int filesSize(QDeclarativeListProperty<File> * property)
void clearFilesPtr(QDeclarativeListProperty<File> *property)
</code>


Для спрощення діалогу вибору файлу, клас ''Directory'' відкидає усі файли окрім тих, що мають розширення .txt, тобто якщо файл не має розширення .txt, він не буде видимий у діалозі. Ще однією важливою функцією елемента є контроль розширення фалу при його збереженні. Елемент ''Directory'' використовує ''QTextStream'' для того щоб зчитувати і записувати вміст файл.
Для спрощення діалогу вибору файлу, клас ''Directory'' відкидає усі файли окрім тих, що мають розширення .txt, тобто якщо файл не має розширення .txt, він не буде видимий у діалозі. Ще однією важливою функцією елемента є контроль розширення фалу при його збереженні. Елемент ''Directory'' використовує ''QTextStream'' для того щоб зчитувати і записувати вміст файл.
Line 307: Line 620:
Програма ''qmlviewer'' імпортує файли, які знаходяться у тому ж каталозі, що й програма. Також можна створити файл ''qmldir'' і прописати у ньому усі QML-файли, які треба підключити. Файл ''qmldir'' може містити посилання на розширення та інші ресурси.
Програма ''qmlviewer'' імпортує файли, які знаходяться у тому ж каталозі, що й програма. Також можна створити файл ''qmldir'' і прописати у ньому усі QML-файли, які треба підключити. Файл ''qmldir'' може містити посилання на розширення та інші ресурси.


<code><br />Файл qmldir:
<code>
Файл qmldir:


Button ./Button.qml<br /> FileDialog ./FileDialog.qml<br /> TextArea ./TextArea.qml<br /> TextEditor ./TextEditor.qml<br /> EditMenu ./EditMenu.qml
Button ./Button.qml
FileDialog ./FileDialog.qml
TextArea ./TextArea.qml
TextEditor ./TextEditor.qml
EditMenu ./EditMenu.qml


plugin FileDialog plugins<br /></code>
plugin FileDialog plugins
</code>


Розширення, яке ми створили називається ''FileDialog'', як вказано у полі ''TARGET'' проекту. Скомпільований файл розширення знаходиться у каталозі ''plugins''.
Розширення, яке ми створили називається ''FileDialog'', як вказано у полі ''TARGET'' проекту. Скомпільований файл розширення знаходиться у каталозі ''plugins''.
Line 321: Line 640:
Елемент ''Directory'' використовується у ''FileMenu.qml'' і повідомляє ''FileDialog'' про зміни у каталозі. Це повідомлення реалізоване у обробнику сигналу ''onDirectoryChanged''.
Елемент ''Directory'' використовується у ''FileMenu.qml'' і повідомляє ''FileDialog'' про зміни у каталозі. Це повідомлення реалізоване у обробнику сигналу ''onDirectoryChanged''.


<code><br /> У файлі FileMenu.qml:
<code>
У файлі FileMenu.qml:


Directory{<br /> id:directory<br /> filename: textInput.text<br /> onDirectoryChanged: fileDialog.notifyRefresh()<br /> }<br /></code>
Directory{
id:directory
filename: textInput.text
onDirectoryChanged: fileDialog.notifyRefresh()
}
</code>


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


<code><br />У файлі FileDialog.qml:
<code>
У файлі FileDialog.qml:


signal notifyRefresh()<br /> onNotifyRefresh: dirView.model = directory.files<br /></code>
signal notifyRefresh()
onNotifyRefresh: dirView.model = directory.files
</code>


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


<code><br /> In FileMenu.qml:
<code>
In FileMenu.qml:


Button{<br /> id: newButton<br /> label: &quot;Новий&amp;quot;<br /> onButtonClick:{<br /> textArea.textContent = &quot;&quot;<br /> }<br /> }<br /> Button{<br /> id: loadButton<br /> label: &quot;Відкрити&amp;quot;<br /> onButtonClick:{<br /> directory.filename = textInput.text<br /> directory.loadFile&amp;amp;#40;&amp;#41;<br /> textArea.textContent = directory.fileContent<br /> }<br /> }<br /> Button{<br /> id: saveButton<br /> label: &quot;Зберегти&amp;quot;<br /> onButtonClick:{<br /> directory.fileContent = textArea.textContent<br /> directory.filename = textInput.text<br /> directory.saveFile&amp;amp;#40;&amp;#41;<br /> }<br /> }<br /> Button{<br /> id: exitButton<br /> label: &quot;Вийти&amp;quot;<br /> onButtonClick:{<br /> Qt.quit()<br /> }<br /> }<br /></code>
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()
}
}
</code>


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


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


p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor5_filemenu.png]]
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor5_filemenu.png|http://doc.qt.io/qt-4.8/images/qml-texteditor5_filemenu.png]]


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


p=. [[Image:http://doc.qt.nokia.com/4.7/images/qml-texteditor5_newfile.png|http://doc.qt.nokia.com/4.7/images/qml-texteditor5_newfile.png]]
[[Image:http://doc.qt.io/qt-4.8/images/qml-texteditor5_newfile.png|http://doc.qt.io/qt-4.8/images/qml-texteditor5_newfile.png]]

Latest revision as of 10:24, 2 March 2017

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