Оглавление
Начало программирования с QMLДобро пожаловать в мир QML, декларативного языка пользовательского интерфейса. В этом руководстве по началу работы мы создадим приложение простого текстового редактора, используя QML. После прочтения данного руководства вы будете готовы разрабатывать свои собственные приложения используя QML и Qt C++. УстановкаFirst, we would need to install the latest version of Qt that includes Qt Quick, which is Qt 4.7. The installation guide contains installation instructions and requirements for different platforms. Qt Quick includes a declarative language called QML, the Qt Declarative Module, and QML Viewer. QML для построения пользовательского интерфейсаСоздаваемое приложение является простым текстовым редактором, который будет загружать, сохранять и выполнять некоторые манипуляции с текстом. Руководство состоит из двух частей. Первая часть затрагивает проектирование в QML компоновки и поведения приложения с использованием декларативного языка. Во второй части будет реализованы загрузка и сохранение с использование Qt C++. Используя метаобъектную систему Qt, мы можем сделать видимыми функции C++ в качестве свойств, которые могут использовать элементы QML. Используя QML и Qt C++, мы можем эффективно отделить логику интерфейса от логики приложения. The final source code is in the examples/tutorials/gettingStarted/gsQml directory. You may need to compile the C++ plugin in the examples/tutorials/gettingStarted/gsQml/ first. This will put the C++ plugin in a directory where the QML files may find it. To launch the text editor, merely provide the included qmlviewer tool with the QML file as the argument. В части C++ данного руководства предполагается, что читатель владеет базовыми знаниями процедур компиляции Qt. Уроки руководства:
Объявление кнопки и менюБазовый компонент - кнопкаНачнём создание нашего текстового редактора с создания кнопки. С функциональной точки зрения, кнопка обладает областью, чувствительной к мыши, и меткой. Кнопки выполняют действия когда пользователь нажимают на кнопку. В QML, основным видимым элементом является элемент Rectangle. Элемент Rectangle имеет свойства для управления внешним видом и местоположением элемента. import QtQuick 1.0 Rectangle { id: simplebutton color: "grey" width: 150; height: 75 Text{ id: buttonLabel anchors.centerIn: parent text: "button label" } } First, the import QtQuick 1.0 allows the qmlviewer tool to import the QML elements we will later use. Эта строка должна присуствовать в каждом файле QML. Заметьте, что версия модулей Qt включается в оператор импорта. Этот простой прямоугольник обладает уникальным идентификатором, simplebutton, который примыкает к свойству id. Свойства элемента Rectangle привызываются к значениям путём перечисления свойства, следующего за ним двоеточия, а затем - значения. В коде примера цвет grey привязывается к свойству Rectangle'а, color. Подобным же образом мы присваиваем значения width и height элемента Rectangle. Элемент Text - это нередактируемое текстовое поле. Дадим этому объекту Text имя buttonLabel. Чтобы установить строковое значение в текстовое поле, присвоим значение свойству text. Метка содержится внутри прямоугольника Rectangle и чтобы отцентрировать её в среднем положении мы установим якоря элемента Text к его родителю, который называется simplebutton. Якоря могут быть привязаны к другим якорям элемента, делая возможность присваиваний размещения проще. Сохраним этот код как SimpleButton.qml. Запущенный qmlviewer с файлом в качестве аргумента выведет на экран серый прямоугольник с текстовой меткой. Для реализации функциональности нажатия кнопки можно использовать обработку событий в QML. Обработка событий в QML очень проста по сравнению с механизмом сигналов и слотов Qt. Сигналы отправляются и вызывается присоединённый слот. Rectangle{ id:simplebutton ... MouseArea{ id: buttonMouseArea anchors.fill: parent //прикрепим все стороны области мыши к якорям прямоугольника //onClicked обработает корректные щелчки кнопки мыши onClicked: console.log(buttonLabel.text + " clicked" ) } } Включим элемент MouseArea в наш simplebutton. Элементы MouseArea описывают интерактивную область, где обнаруживаются перемещения мыши. Для нашей кнопки, мы прикрепим всю область MouseArea к её родителю - simplebutton. Синтаксис anchors.fill - это один из способов присвоения определённого свойства с именем fill внутри группы свойств с именем anchors. QML uses anchor-based layouts where items can anchor to another item, creating robust layouts. The MouseArea has many signal handlers that are called during mouse movements within the specified MouseArea boundaries. Один из них - 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 + " clicked" ) } 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. В фрагменты кода данной статьи не включён некоторый код, обозначенный эллипсами, поскольку он либо был представлен в предыдущих разделах, либо не имеет отношения к обсуждению текущего кода. Пользовательские свойства объявляются используя синтаксис свойство тип имя. In the code, the property buttonColor, of type color, is declared and bound to the value "lightblue". The buttonColor is later used in a conditional operation to determine the buttons's fill color. Note that property value assignment is possible using the = equals sign, in addition to value binding using the : colon character. Пользовательские свойства позволяют получить доступ к внутренним элементам извне области видимости Rectangle'а. There are basic QML types such as int, string, real, as well as a type called variant. Привязав обработчики сигналов onEntered и onExited с цветами, граница кнопки будет становиться жёлтой, когда мышь наводится на кнопку, и возвращаться к предшествующему цвету, когда мышь выходит за пределы области мыши. Сигнал buttonClick() объявлен в Button.qml путём размещения ключевого слова signal перед именем сигнала. Все сигналы имеют собственные обработчики, создаваемые автоматически, их имена начинаются с on. В результате, onButtonClick - это обработчик buttonClick'а. Затем onButtonClick присваивается действию для выполнения. В нашем примере кнопки, обработчик мыши onClicked просто вызовет onButtonClick, который выведет на экран текст. onButtonClick разрешает внешним объектам легко получить доступ к области мыши Button'а. Например, элементы могут иметь более одного объявления MouseArea и сигнал buttonClick может лучше различать несколько обработчиков сигнала MouseArea. Теперь мы обладаем базовыми знаниями по реализации в QML элементов, которые могут обрабатывать основные движения мыши. Мы создали метку Text внутри прямоугольника Rectangle, настроили её свойства и реализовали варианты поведения, которые реагируют на движения мыши. Эта идея создания элементов внутри элементов повторяется повсюду в приложении текстового редактора. Эта кнопка не приносит пользы, пока не используется в качестве компонента для выполнения действия. В следующем разделе мы скоро создадим меню, содержащее несколько таких кнопок. Создание страницы менюДо этого шага мы рассмотрели, как создавать элементы и определять поведение внутри одного файла QML. В этом разделе мы охватываем импорт элементов QML и то, как повторно использовать некоторые из созданных компонентов для создания других компонентов. Меню отображают содержимое списка, каждый пункт имеет возможность выполнять действие. В QML меню можно создать несколькими способами. Сначала мы создадим меню, содержащее кнопки, которые в конце концов будут выполнять различные действия. Код меню в FileMenu.qml. import QtQuick 1.0 \\импорт главного модуля QML Qt import "folderName" \\импорт содержимого каталога folder import "script.js" as Script \\импорт файла и присвоение ему имени Script Вышеприведённый синтаксис показывает, как использовать ключевое слово import. This is required to use JavaScript files, or QML files that are not within the same directory. Since Button.qml is in the same directory as FileMenu.qml, we do not need to import the Button.qml file to use it. We can directly create a Button element by declaring Button{}, similar to a Rectangle{} declaration. В FileMenu.qml: Row{ anchors.centerIn: parent spacing: parent.width/6 Button{ id: loadButton buttonColor: "lightgrey" label: "Load" } Button{ buttonColor: "grey" id: saveButton label: "Save" } Button{ id: exitButton label: "Exit" buttonColor: "darkgrey" onButtonClick: Qt.quit() } } В FileMenu.qml мы объявляем три элемента Button. Они объявляются внутри элемента Row, позиционера, который будет помещать свои дочерние элементы в вертикальный ряд. Объявление Button находится в Button.qml, который тот же самый, что и Button.qml, используемый нами в предыдущем разделе. Привязки новых свойств могут быть объявлены внутри вновь созданных кнопок, фактически перезаписывая свойства, установленные в Button.qml. При щелчке по кнопке с именем exitButton она завершит сеанс работы и закроет окно. onButtonClick в Button.qml будет вызываться в добавок к обработчику onButtonClick в exitButton. Объявление Row объявляется в Rectangle, создавая прямоугольный контейнер для ряда кнопок. Этот дополнительный прямоугольник создаёт косвенный способ упорядочения ряда кнопок внутри меню. Объявление меню редактирования очень похоже на этот этап. В меню имеются кнопки с метками: Copy, Paste и Select All. Вооружившись нашим знанием импортирования и настройки предварительно созданных компонентов, мы можем теперь сочетать такие страницы меню для создания панели меню, содержащей кнопки для выбора меню и выглядящей так, как будто мы можем структурировать данные используя QML. Реализация панели менюНашему приложению текстового редактора необходим способ отображения меню с использованием панели меню. Панель меню будет переключать разные меню и пользователь сможет выбрать какое меню отображать. Переключение меню предполагает, что нужны меню более структурированные, чем просто отображать их в ряд. QML использует модели и представления для структурирования данных и отображения структурированных данных. Использование моделей данных и представленийQML содержит различные представления данных, отображающих модели данных. Наша панель меню будет отображать меню в списке с заголовком, в котором отображается строка имен меню. Список меню объявляется внутри VisualItemModel. Элемент VisualItemModel содержит пункты, которые уже имеют представления, например, элементы Rectangle и импортированные элементы пользовательского интерфейса. Другим типам моделей, такие как элемент ListModel, нужен делегат для отображения их данных. Мы объявили два видимых пункта в menuListModel, FileMenu и EditMenu. Настроим эти два меню и выведем их на экран используя ListView. Файл MenuBar.qml содержит объявления QML и меню простого редактирования определено в EditMenu.qml. VisualItemModel{ id: menuListModel FileMenu{ width: menuListView.width height: menuBar.height color: fileColor } EditMenu{ color: editColor width: menuListView.width height: menuBar.height } } Элемент ListView выведет на экран модель, соответствующую делегату. Делегат может объявить элементы модели для отображения в элементе Row или в сетке. Наша 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 обслуживает элементы модели через index и каждый видимый элемент модели доступен через index, в том порядке в котором объявлялись. Изменение currentIndex практически изменяет выделенный элемент в ListView. Заголовок нашей панели меню иллюстрирует этот эффект. Имеются две кнопки в ряд, обе при щелчке по ним меняют текущее меню. fileButton, при щелчке по нему, изменяет текущее меню на меню файлов, index будет равно 0 поскольку FileMenu объявлено первым в menuListModel. Аналогично, editButton будет при щелчке по нему изменять текущее меню на EditMenu. В прямоугольнике labelList z имеет значение 1, означающее что он отображается перед панелью меню. Элементы с более высокими значениями z отображаются перед элементами с более низкими значениями z. По умолчанию значение z равно 0. Rectangle{ id: labelList ... z: 1 Row{ anchors.centerIn: parent spacing:40 Button{ label: "File" id: fileButton ... onButtonClick: menuListView.currentIndex = 0 } Button{ id: editButton label: "Edit" ... onButtonClick: menuListView.currentIndex = 1 } } } Созданная панель меню может двигать рывком для доступа к меню или щелчком на имени меню в верхней части. Экраны переключения меню воспринимаются интуитивно и чувствительно. Создание текстового редактораОбъявление TextAreaНаш текстовый редактор не является текстовым редактором, если в нем нет область редактирования текста. Элемент QML'а, TextEdit, позволяет объявить многострочную область редактирования текста. 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(r){ 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 //экран разделен на MenuBar и TextArea. 1/3 экрана назначена MenuBar 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 } } Импортируя повторно используемые компоненты код нашего TextEditor выглядит проще. Затем мы можем настроить главное приложение, не беспокоясь о свойствах, поведение которых уже определено. Используя этот подход можно легко создавать компоновки приложения и компонентов пользовательского интерфейса. Украшение текстового редактораImplementing a Drawer InterfaceOur text editor looks simple and we need to decorate it. Using QML, we can declare transitions and animate our text editor. Our menu bar is occupying one-third of the screen and it would be nice to have it only appear when we want it. We can add a drawer interface, that will contract or expand the menu bar when clicked. In our implementation, we have a thin rectangle that responds to mouse clicks. The drawer, as well as the application, has two sates: the "drawer is open" state and the "drawer is closed" state. The drawer item is a strip of rectangle with a small height. There is a nested Image element declaring that an arrow icon will be centered inside the drawer. The drawer assigns a state to the whole application, with the identifier screen, whenever a user clicks the mouse area. 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" } } ... } } A state is simply a collection of configurations and it is declared in a State element. A list of states can be listed and bound to the states property. In our application, the two states are called DRAWER_CLOSED and DRAWER_OPEN. Item configurations are declared in PropertyChanges elements. In the DRAWER_OPEN state, there are four items that will receive property changes. The first target, menuBar, will change its y property to 0. Similarly, the textArea will lower to a new position when the state is DRAWER_OPEN. The textArea, the drawer, and the drawer's icon will undergo property changes to meet the current state. 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 } } ] State changes are abrupt and needs smoother transitions. Transitions between states are defined using the Transition element, which can then bind to the item's transitions property. Our text editor has a state transition whenever the state changes to either DRAWER_OPEN or DRAWER_CLOSED. Importantly, the transition needs a from and a to state but for our transitions, we can use the wild card * symbol to denote that the transition applies to all state changes. During transitions, we can assign animations to the property changes. Our menuBar switches position from y:0 to y:-partition and we can animate this transition using the NumberAnimation element. We declare that the targets' properties will animate for a certain duration of time and using a certain easing curve. An easing curve controls the animation rates and interpolation behavior during state transitions. The easing curve we chose is Easing.OutQuint, which slows the movement near the end of the animation. Please read QML's Animation article. 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 } } ] Another way of animating property changes is by declaring a Behavior element. A transition only works during state changes and Behavior can set an animation for a general property change. In the text editor, the arrow has a NumberAnimation animating its rotation property whenever the property changes. В TextEditor.qml: Behavior{ NumberAnimation{property: "rotation";easing.type: Easing.OutExpo } } Going back to our components with knowledge of states and animations, we can improve the appearances of the components. In Button.qml, we can add color and scale property changes when the button is clicked. Color types are animated using ColorAnimation and numbers are animated using NumberAnimation. The on propertyName syntax displayed below is helpful when targeting a single property. 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} } Additionally, we can enhance the appearances of our QML components by adding color effects such as gradients and opacity effects. Declaring a Gradient element will override the color property of the element. You may declare a color in the gradient using the GradientStop element. The gradient is positioned using a scale, between 0.0 and 1.0. В 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" } } This gradient is used by the menu bar to display a gradient simulating depth. The first color starts at 0.0 and the last color is at 1.0. Where to Go from HereWe are finished building the user interface of a very simple text editor. Going forward, the user interface is complete, and we can implement the application logic using regular Qt and C++. QML works nicely as a prototyping tool, separating the application logic away from the UI design. Расширение QML с использованием Qt C++Now that we have our text editor layout, we may now implement the text editor functionalities in C++. Using QML with C++ enables us to create our application logic using Qt. We can create a QML context in a C++ application using the Qt's Declarative classes and display the QML elements using a Graphics Scene. Alternatively, we can export our C++ code into a plugin that the qmlviewer tool can read. For our application, we shall implement the load and save functions in C++ and export it as a plugin. This way, we only need to load the QML file directly instead of running an executable. Exposing C++ Classes to QMLWe will be implementing file loading and saving using Qt and C++. C++ classes and functions can be used in QML by registering them. The class also needs to be compiled as a Qt plugin and the QML file will need to know where the plugin is located. For our application, we need to create the following items:
Building a Qt PluginTo build a plugin, we need to set the following in a Qt project file. First, the necessary sources, headers, and Qt modules need to be added into our project file. All the C++ code and project files are in the filedialog directory. В filedialog.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. Регистрация класса в QMLВ dialogPlugin.h: #include <QtDeclarative/QDeclarativeExtensionPlugin> class DialogPlugin : public QDeclarativeExtensionPlugin { Q_OBJECT public: void registerTypes(const char *uri); }; Our plugin class, DialogPlugin is a subclass of QDeclarativeExtensionPlugin. We need to implement the inherited function, registerTypes(). The dialogPlugin.cpp file looks like this: 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); The registerTypes() function registers our File and Directory classes into QML. This function needs the class name for its template, a major version number, a minor version number, and a name for our classes. We need to export the plugin using the Q_EXPORT_PLUGIN2 macro. Note that in our dialogPlugin.h file, we have the Q_OBJECT macro at the top of our class. As well, we need to run qmake on the project file to generate the necessary meta-object code. Создание свойств QML в классе C++We can create QML elements and properties using C++ and Qt's Meta-Object System. We can implement properties using slots and signals, making Qt aware of these properties. These properties can then be used in QML. For the text editor, we need to be able to load and save files. Typically, these features are contained in a file dialog. Fortunately, we can use QDir, QFile, and QTextStream to implement directory reading and input/output streams. 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 ) ... The Directory class uses Qt's Meta-Object System to register properties it needs to accomplish file handling. The Directory class is exported as a plugin and is useable in QML as the Directory element. Each of the listed properties using the Q_PROPERTY macro is a QML property. The Q_PROPERTY declares a property as well as its read and write functions into Qt's Meta-Object System. For example, the filename property, of type QString, is readable using the filename() function and writable using the function setFilename(). Additionally, there is a signal associated to the filename property called filenameChanged(), which is emitted whenever the property changes. The read and write functions are declared as public in the header file. Similarly, we have the other properties declared according to their uses. The filesCount property indicates the number of files in a directory. The filename property is set to the currently selected file's name and the loaded/saved file content is stored in fileContent property. Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT ) The files list property is a list of all the filtered files in a directory. The Directory class is implemented to filter out invalid text files; only files with a .txt extension are valid. Further, QLists can be used in QML files by declaring them as a QDeclarativeListProperty in C++. The templated object needs to inherit from a QObject, therefore, the File class must also inherit from QObject. In the Directory class, the list of File objects is stored in a QList called m_fileList. class File : public QObject{ Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) ... }; The properties can then be used in QML as part of the Directory element's properties. Note that we do not have to create an identifier id property in our C++ code. Directory{ id: directory filesCount filename fileContent files files[0].name } Because QML uses Javascript's syntax and structure, we can iterate through the list of files and retrieve its properties. To retrieve the first file's name property, we can call files[0].name. Regular C++ functions are also accessible from QML. The file loading and saving functions are implemented in C++ and declared using the Q_INVOKABLE macro. Alternatively, we can declare the functions as a slot and the functions will be accessible from QML. В Directory.h: Q_INVOKABLE void saveFile(); Q_INVOKABLE void loadFile(); The Directory class also has to notify other objects whenever the directory contents change. This feature is performed using a signal. As previously mentioned, QML signals have a corresponding handler with their names prepended with on. The signal is called directoryChanged and it is emitted whenever there is a directory refresh. The refresh simply reloads the directory contents and updates the list of valid files in the directory. QML items can then be notified by attaching an action to the onDirectoryChanged signal handler. The list properties need to be explored further. This is because list properties use callbacks to access and modify the list contents. The list property is of type QDeclarativeListProperty<File>. Whenever the list is accessed, the accessor function needs to return a QDeclarativeListProperty<File>. The template type, File, needs to be a QObject derivative. Further, to create the QDeclarativeListProperty, the list's accessor and modifiers need to be passed to the constructor as function pointers. The list, a QList in our case, also needs to be a list of File pointers. The constructor of QDeclarativeListProperty constructor and the Directory implementation: 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 ); The constructor passes pointers to functions that will append the list, count the list, retrieve the item using an index, and empty the list. Only the append function is mandatory. Note that the function pointers must match the definition of AppendFunction, CountFunction, AtFunction, or 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) To simplify our file dialog, the Directory class filters out invalid text files, which are files that do not have a .txt extension. If a file name doesn't have the .txt extension, then it won't be seen in our file dialog. Also, the implementation makes sure that saved files have a .txt extension in the file name. Directory uses QTextStream to read the file and to output the file contents to a file. With our Directory element, we can retrieve the files as a list, know how many text files is in the application directory, get the file's name and content as a string, and be notified whenever there are changes in the directory contents. To build the plugin, run qmake on the filedialog.pro project file, then run make to build and transfer the plugin to the plugins directory. Importing a Plugin in QMLThe qmlviewer tool imports files that are in the same directory as the application. We can also create a qmldir file containing the locations of QML files we wish to import. The qmldir file can also store locations of plugins and other resources. В qmldir: Button ./Button.qml FileDialog ./FileDialog.qml TextArea ./TextArea.qml TextEditor ./TextEditor.qml EditMenu ./EditMenu.qml plugin FileDialog plugins The plugin we just created is called FileDialog, as indicated by the TARGET field in the project file. The compiled plugin is in the plugins directory. Integrating a File Dialog into the File MenuOur FileMenu needs to display the FileDialog element, containing a list of the text files in a directory thus allowing the user to select the file by clicking on the list. We also need to assign the save, load, and new buttons to their respective actions. The FileMenu contains an editable text input to allow the user to type a file name using the keyboard. The Directory element is used in the FileMenu.qml file and it notifies the FileDialog element that the directory refreshed its contents. This notification is performed in the signal handler, onDirectoryChanged. В FileMenu.qml: Directory{ id:directory filename: textInput.text onDirectoryChanged: fileDialog.notifyRefresh() } Keeping with the simplicity of our application, the file dialog will always be visible and will not display invalid text files, which do not have a .txt extension to their filenames. В FileDialog.qml: signal notifyRefresh() onNotifyRefresh: dirView.model = directory.files The FileDialog element will display the contents of a directory by reading its list property called files. The files are used as the model of a GridView element, which displays data items in a grid according to a delegate. The delegate handles the appearance of the model and our file dialog will simply create a grid with text centered in the middle. Clicking on the file name will result in the appearance of a rectangle to highlight the file name. The FileDialog is notified whenever the notifyRefresh signal is emitted, reloading the files in the directory. В 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() } } Our FileMenu can now connect to their respective actions. The saveButton will transfer the text from the TextEdit onto the directory's fileContent property, then copy its file name from the editable text input. Finally, the button calls the saveFile() function, saving the file. The loadButton has a similar execution. Also, the New action will empty the contents of the TextEdit. Further, the EditMenu buttons are connected to the TextEdit functions to copy, paste, and select all the text in the text editor. Text Editor CompletionThe application can function as a simple text editor, able to accept text and save the text into a file. The text editor can also load from a file and perform text manipulation. Running the Text EditorWe need to compile the file dialog C++ plugin before the text editor can run. To compile, enter the gsQml directory, then run qmake and compile using make or nmake, depending on your platform. To run, launch qmlviewer and open the texteditor.qml file. The source code is in the examples/tutorials/gettingStarted/gsQml directory. |
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |