-
В 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" }
-
}
Этот градиент используется для придания объема панели меню. Первый цвет начинается с 0.0 и последний заканчивается на 1.0
Что делать дальше?
Мы закончили разработку пользовательского интерфейса очень простого текстового редактора. Продолжаем двигаться дальше, пользовательский интерфейс готов, и мы можем разрабатывать логику приложения, используя обычный Qt и C++. QML хорошо подходит как инструмент для построения прототипов и отделения логики приложения от графического пользовательского интерфейса.
Расширяем возможности QML с помощью C++
Теперь, когда мы имеем макет нашего приложения, мы можем приступить к реализации возможностей текстового редактора на C++. Использование QML вместе с C++ позволяет нам реализовать логику приложения с помощью Qt. Мы можем создать QML-контекст в приложении C++ используя классы Qt Declarative [doc.qt.nokia.com] и отображать элементы QML при помощи QDeclarativeView [doc.qt.nokia.com], который основан на Graphics View Framework [doc.qt.nokia.com]. Или же, мы можем экспортировать C++ код в плагин, который сможет использоваться в qmlviewer. Для нашего приложения мы разработаем функции загрузки и сохранения файла на C++ и экспортируем их как плагин. Это позволит запускать QML-приложение непосредственно в qmlviewer, а также полноценно работать с дизайнером QML в Qt Creator.
Делаем C++ классы доступными в QML
Мы будем реализовывать загрузку и сохранение файла используя Qt и C++. C++ классы и функции могут использоваться в QML только после того, как будут зарегистрированы в нем. Класс должен быть собран как Qt-плагин, а QML-приложение должно знать месторасположение плагина.
Для нашего приложения, нам потребуется создать следующие элементы:
-
Класс Directory, который хранит список файлов (обьекты типа File) и отвечает за операции с каталогами
-
Класс File, наследник QObject [doc.qt.nokia.com], который хранит имя файла
-
Класс плагина, который будет регистрироваться QML-контексте
-
Файл проекта Qt (с расширением .pro), для описания настроек сборки плагина
-
Файл qmldir, который сообщает qmlviewer о том, где искать наш плагин
Сборка плагина
Чтобы собрать плагин, нам необходимо добавить указания на исходники, заголовки, и модули Qt в наш файл проекта. Весь код на C++ и файлы проекта находятся в директории filedialog.
-
-
В 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
Мы включаем в сборку модуль declarative и указываем, что мы хотим собрать плагин (TEMPLATE = lib, CONFIG = plugin ). Также мы указываем, что собранный плагин должен быть помещен в директорию plugins, расположенную в родительском каталоге.
Регистрация класса в QML
-
В dialogPlugin.h:
-
-
#include <QtDeclarative/QDeclarativeExtensionPlugin>
-
-
class DialogPlugin : public QDeclarativeExtensionPlugin
-
{
-
Q_OBJECT
-
-
public:
-
void registerTypes(const char *uri);
-
-
};
Наш класс плагина DialogPlugin является наследником QDeclarativeExtensionPlugin [doc.qt.nokia.com]. Нам необходимо переопределить виртуальную функцию registerTypes() [doc.qt.nokia.com]. Файл dialogPlugin.cpp выглядит следующим образом:
-
DialogPlugin.cpp:
-
-
#include "dialogPlugin.h"
-
#include "directory.h"
-
#include "file.h"
-
#include <QtDeclarative/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);
Функция registerTypes() [doc.qt.nokia.com] регистрирует наши классы File И Directory в QML. Эта функция требует названия классов для их прототипов, номера старшей и младшей версий классов и их имена.
Нам необходимо экспортировать плагин с помощью макроса Q_EXPORT_PLUGIN2 [doc.qt.nokia.com]. Обратите внимание, что в файле dialogPlugin.h мы имеем макрос Q_OBJECT [doc.qt.nokia.com] в начале нашего класса. Также нам надо запустить qmake для генерации мета-информации о наших классах.
Создание QML-свойств в C++ классе
Мы можем создавать QML-элементы и определять их свойства, используя C ++ и систему мета-информации Qt (Meta-Object System [doc.qt.nokia.com]). Мы можем сообщить Qt о свойствах наших объектов, используя для этого сигналы и слоты, тогда эти свойства будут доступны в QML.
Текстовый редактор должен иметь возможность загружать и сохранять файлы. Как правило, эти возможности содержатся в файловом диалоге. К счастью, мы можем использовать QDir [doc.qt.nokia.com], QFile [doc.qt.nokia.com] и QTextStream [doc.qt.nokia.com] для реализации чтения директории и операций ввода/вывода.
-
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 для регистрации свойств, необходимых для работы с файлами. Класс Directory экспортируется как плагин и может использоваться как элемент в контексте QML. Каждое из перечисленных свойств, использующих макрос Q_PROPERTY [doc.qt.nokia.com], является свойством QML.
Q_PROPERTY [doc.qt.nokia.com] объявляет свойство, а также его функции чтения и записи. Например, свойство filename, имеющее тип QString [doc.qt.nokia.com], читается при помощи функции filename() и устанавливается при помощи функции setFilename(). А каждый раз когда значение этого свойства меняется, генерируется сигнал filenameChanged(). Функции чтения и записи свойств обьявлены как public в файле заголовка.
Точно так же у нас есть другие свойства, объявленные в соответствии с их использованием. Свойство filesCount указывает количество файлов в директории. Свойство filename содержит имя текущего выбранного файла. Содержимое файла для чтения и записи хранится в свойстве fileContent.
-
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
Свойство files содержит список всех отфильтрованных файлов в директории. Класс Directory реализован так, чтобы отображать только корректные текстовые файлы; в данном случае, корректными считаются только файлы с расширением “.txt”. Объекты класса QList [doc.qt.nokia.com] могут использоваться в QML после объявления их с ключевым словом QDeclarativeListProperty в коде на C++. Заметим, что класс, указанный в качестве параметр-класса, должен быть потомком QObject, то есть класс File также должен наследовать класс QObject. В классе Directory список объектов типа File хранится в переменной m_fileList типа QList [doc.qt.nokia.com].
-
class File : public QObject{
-
-
Q_OBJECT
-
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
-
-
...
-
};
Свойства могут использоваться в QML как свойства элементов Directory. Заметим, что нам не следует создавать свойство id для объектов на языке C++.
-
Directory{
-
id: directory
-
-
filesCount
-
filename
-
fileContent
-
files
-
-
files[0].name
-
}
Поскольку QML использует синтаксис и структуру языка Javascript, мы можем пройти по списку файлов как по массиву и узнать их свойства. Например, чтобы получить имя первого файла, нам достаточно обратиться к свойству files0.name.
Обычные функции C++ также доступны из QML. Функции загрузки и сохранения файлов реализованы на языке C++ и определены с макросом Q_INVOKABLE. Также функцию можно объявить как слот (slot) и тогда она тоже будет доступна в QML.
-
В файле Directory.h:
-
-
Q_INVOKABLE void saveFile();
-
Q_INVOKABLE void loadFile();
Класс Directory также должен сообщать другим объектам о том, что содержимое каталога изменилось. Для этого объект данного класса генерирует определенный сигнал (signal). Как уже отмечалось ранее, сигналы в QML имеют соответствующие обработчики, начинающиеся с приставки on. В данном случае, сигнал называется directoryChanged и генерируется каждый раз при обновлении содержимого каталога. При обновлении заново загружается список файлов в каталоге и создается список корректных текстовых файлов. Для того, чтобы объекты QML могли получать этот сигнал, в них необходимо реализовать обработчик сигнала onDirectoryChanged.
Более подробно стоит рассмотреть свойства объектов на C++. Свойства списка используют обратный вызов (callback) для получения и изменения содержимого списка. Свойства списка имеют тип QDeclarativeListProperty<File>. Каждый раз при обращении к списку, функция доступа должна возвращать QDeclarativeListProperty<File>. Так как параметр-класс File является наследником QObject, то при создании свойств QDeclarativeListProperty необходимо передать в конструктор ссылки на функции доступа и модификаторы. Также класс списка (в нашем случае QList) должен быть списком ссылок на объекты File.
Ниже приведено определение конструктора 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 [doc.qt.nokia.com], CountFunction [doc.qt.nokia.com], AtFunction [doc.qt.nokia.com] или ClearFunction [doc.qt.nokia.com].
-
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. Также проверяется, чтобы сохраняемый файл также имел расширение .txt. Directory использует класс QTextStream [doc.qt.nokia.com] для чтения данных и записи в файл.
Используя наш элемент Directory, мы можем получить список файлов, определить количество текстовых файлов в каталоге приложения, получить имя файла и его содержимое в виде строки QString [doc.qt.nokia.com], а также получить информацию об изменении содержимого каталога.
Для создания плагина необходимо выполнить команду qmake для файла проекта cppPlugins.pro. Чтобы собрать бинарный файл и поместить его в каталог с плагинами (plugins), необходимо выполнить команду _make.
Импорт плагина в QML
Утилита qmlviewer импортирует файлы, расположенные в той же директории, что и разрабатываемое QML-приложение. Мы также можем создать файл 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.
-
In 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 будет обновлять список файлов при получении соответствующего сигнала.
-
В FileMenu.qml:
-
-
Button{
-
id: newButton
-
label: "New"
-
onButtonClick:{
-
textArea.textContent = ""
-
}
-
}
-
Button{
-
id: loadButton
-
label: "Load"
-
onButtonClick:{
-
directory.filename = textInput.text
-
directory.loadFile()
-
textArea.textContent = directory.fileContent
-
}
-
}
-
Button{
-
id: saveButton
-
label: "Save"
-
onButtonClick:{
-
directory.fileContent = textArea.textContent
-
directory.filename = textInput.text
-
directory.saveFile()
-
}
-
}
-
Button{
-
id: exitButton
-
label: "Exit"
-
onButtonClick:{
-
Qt.quit()
-
}
-
}
Теперь можно соединить элементы меню FileMenu с соответствующими действиями. При нажатии на кнопку saveButton, текст будет передан из TextEdit в свойство fileContent элемента directory, а имя редактируемого файла будет скопировано из поля ввода. И после этого будет вызвана функция saveFile() для сохраниения файла. Кнопка loadButton похожа по функциональности. Кнопка newButton очищает содержимое TextEdit.
Аналогично, кнопки EditMenu будут связаны с функциями TextEdit, выполняющими копирование, вставку и выбор всего текста в редакторе.
Текстовый редактор завершен
Итак, созданное приложение может использоваться как простой текстовый редактор, способное редактировать текст и сохранять его в файл.
Исходный код
Полный исходный код этого приложения можно взять здесь [qt.gitorious.org].