📜 ⬆️ ⬇️

Model-View in QML. Part Three: Models in QML and JavaScript

The model we have is responsible for access to the data. The model can be implemented both in QML itself and in C ++. The choice here is most dependent on where the data source is located. If C ++ code is used as a data source, then it is more convenient to make a model there. If the data comes directly into QML (for example, they are obtained from the network using XMLHttpRequest), then it is better to implement the model in QML. Otherwise, you will have to transfer data in C ++ in order to get it back to display it, which will only complicate the code.

By the way the models are implemented, I will divide them into three categories:

JavaScript models I brought into a separate category, because they have certain features, I will tell about them a little later.
We will begin consideration with the models implemented by means of QML.

Model-View in QML:
  1. Model-View in QML. Part zero, introductory
  2. Model-View in QML. Part One: Predefined Component Views
  3. Model-View in QML. Part Two: Custom Submissions
  4. Model-View in QML. Part Three: Models in QML and JavaScript
  5. Model-View in QML. Part Four: C ++ Models


1. ListModel

This is a fairly simple and, at the same time, functional component. Items in the ListModel can be defined statically (this is demonstrated in the first example), or added / removed dynamically (respectively, in the second example). Let us examine both methods in more detail.
')
1) Static

When we define model elements statically, we need to define data in the child elements that are of type ListElement and are defined within the model. The data is defined in the properties of the ListElement object and is available as roles in the delegate.
With static data definition in ListModel, the data types that can be written to ListElement are very limited. In fact, all data must be constants. Those. you can use strings or numbers, but the object or function will not work. In this case, you will get the "ListElement: cannot use script for property value" error. But you can use a list whose elements are all the same ListElement objects.

import QtQuick 2.0 Rectangle { width: 360 height: 240 ListModel { id: dataModel ListElement { color: "orange" texts: [ ListElement { text: "one" }, ListElement { text: "two" } ] } ListElement { color: "skyblue" texts: [ ListElement { text: "three" }, ListElement { text: "four" } ] } } ListView { id: view anchors.margins: 10 anchors.fill: parent spacing: 10 model: dataModel delegate: Rectangle { width: view.width height: 100 color: model.color Row { anchors.margins: 10 anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter spacing: 10 Repeater { model: texts delegate: Text { verticalAlignment: Text.AlignVCenter renderType: Text.NativeRendering text: model.text } } } } } } 

We use the texts role inside the delegate as a model, so we can achieve several levels of nesting.
As a result, we get something like this:



Another important point. In the statically described model, in all ListElement objects, each role must store data of only one type. Those. it is impossible to write a number in one element, and a line in another. For example, consider a slightly modified model from the very first example:

 ListModel { id: dataModel ListElement { color: "orange" text: 1 } ListElement { color: "skyblue" text: "second" } } 

We get this error: “Can't assign to existing role 'text' of different type [String -> Number]” and instead of text in the second delegate we get 0.

2) Dynamic

This way gives us much more opportunities, than static. Not all of them are described in the documentation and may be obvious, so we consider them in more detail.

The interface for manipulating elements in ListModel is similar to the interface of a regular list. Items can be added / deleted / moved, their value can be retrieved and replaced or edited.

ListModel accepts the value of an element as a JavaScript object. Accordingly, the properties of this object will become roles in the delegate.
If you take the very first example, the model can be rewritten so that it is dynamically filled:

 ListModel { id: dataModel Component.onCompleted: { append({ color: "orange", text: "first" }) append({ color: "skyblue", text: "second" }) } } 

An object can be set not only by a literal, but can be passed to the variable that this object contains:

 var value = { color: "orange", text: "first" } append(value) 

When I wrote about static content, I said that the data types that can be placed in the model should be constants. I have good news :) When we fill the model dynamically, these restrictions do not apply. We can as a property value and arrays, and objects. Even functions, but with small features. Let's take the same example and rewrite it a little:

 QtObject { id: obj function alive() { console.log("It's alive!") } } ListModel { id: dataModel Component.onCompleted: { var value value = { data: { color: "orange", text: "first" }, functions: obj } append(value) value = { data: { color: "skyblue", text: "second" }, functions: obj } append(value) } } 

Since we placed the color and text properties in the data object, they will be in the delegate as properties of this object, i.e. model.data.color.

With features a bit more complicated. If we simply make a property in an object and assign it a function, then inside the delegate we will see that this function has become an empty object. But if you use the QtObject type, then everything inside it is saved and nothing disappears. So in the definition of the component we can add the following line:

 Component.onCompleted: model.functions.alive() 

and this function will be called after creating the component.

Placing functions in data is more like a hack, and I recommend not to get too carried away with such things, but placing objects into a model is a very necessary thing. For example, if data comes from the network directly to QML (using XMLHttpRequest) in JSON format (and usually happens when working with web resources), then decoding JSON, we get a JavaScript object that can be simply added to the ListModel.

I already wrote about the fact that in all statically defined elements of ListModel roles must be of the same types. By default, for items added to ListModel dynamically this rule also applies. And the first element added determines what type of roles there will be. But in Qt 5, the ability to make role types dynamic has been added. To do this, set the dynamicRoles property to ListModel to true.

 ListModel { id: dataModel dynamicRoles: true Component.onCompleted: { append({ color: "orange", text: "first" }) append({ color: "skyblue", text: 2 }) } } 

A handy thing, but there are a couple of important points to remember. The price for this convenience is performance - the Qt developers claim that it will be 4-6 times less. In addition, dynamic role types will not work for a model with statically defined elements.

Another very important point. The first element to be added to the model determines not only the types of roles, but also what roles will be in the model at all. If there are no roles in it, then they cannot be added later. But there is one exception. If elements are added at the stage of model creation (that is, in the Component.onCompleted handler), then the model will have all the roles that were in all these elements.

Let's take the second example and redo it a little so that when creating a model one element is added without the text property, and then by clicking on the button we will add elements with the text “new”.

 import QtQuick 2.0 Rectangle { width: 360 height: 360 ListModel { id: dataModel dynamicRoles: true Component.onCompleted: { append({ color: "orange" }) } } Column { anchors.margins: 10 anchors.fill: parent spacing: 10 ListView { id: view width: parent.width height: parent.height - button.height - parent.spacing spacing: 10 model: dataModel clip: true delegate: Rectangle { width: view.width height: 40 color: model.color Text { anchors.centerIn: parent renderType: Text.NativeRendering text: model.text || "old" } } } Rectangle { id: button width: 100 height: 40 anchors.horizontalCenter: parent.horizontalCenter border { color: "black" width: 1 } Text { anchors.centerIn: parent renderType: Text.NativeRendering text: "Add" } MouseArea { anchors.fill: parent onClicked: dataModel.append({ color: "skyblue", text: "new" }) } } } } 

As a result, all new text elements will not and will have “old” as text:



Let's rewrite the definition of the model and add another element with the text property but no color property at the creation stage:

 ListModel { id: dataModel Component.onCompleted: { append({ color: "orange" }) append({ text: "another old" }) } } 

Correct the delegate definition to use the default color if it is not specified:

 color: model.color || "lightgray" 

As a result, the model is formed with both roles and when adding new elements, everything is displayed as planned:



We can also combine static and dynamic model content. But using the static method imposes all its limitations and dynamically we will be able to add only objects with roles of the same types.

Some news: in Qt 5.1, this model has been moved from QtQuick to a separate QtQml.Models module. To use it, you need to connect this module:

 import QtQml.Models 2.1 

But rushing to rewrite everything is not necessary — for compatibility with existing code, the model will be available in the QtQuick module.

ListModel can be considered a QML version of models from Qt. It has similar functionality, allows you to manipulate data and is the active model. I can say that in QML it is the most functional and convenient component for creating models.

2. VisualItemModel (ObjectModel)

The Model-View architecture of the Qt framework identifies two main entities: the model and the view and one auxiliary, the delegate. Since the view here is a container for delegate instances, the delegate is usually defined there.

This component allows you to transfer a delegate from the view to the model itself. This is realized by the fact that not the data, but ready-made visual elements are placed in the model. Accordingly, the representation in this case does not need a delegate and it is used only as a container, ensuring the positioning and navigation of elements.

One interesting feature of VisualItemModel is that you can put objects of different types into it. The regular delegate model uses objects of the same type to display all data. When it is required to display elements of different types in one view, such a model is one of the solutions to this problem.

As an example, put the objects of the Rectangle and Text types in the model and display them using the ListView:

 import QtQuick 2.0 Rectangle { width: 360 height: 240 VisualItemModel { id: itemModel Rectangle { width: view.width height: 100 color: "orange" } Text { width: view.width height: 100 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter renderType: Text.NativeRendering text: "second" } } ListView { id: view anchors.margins: 10 anchors.fill: parent spacing: 10 model: itemModel } } 

In Qt 5.1, this model is moved from QtQuick to a separate QtQml.Models module and is called ObjectModel. Just as with ListModel, to use this model, you must connect the appropriate module. The interface remains the same, simply replace the VisualDataModel with ObjectModel.

The model will still be available through VisualDataModel, so as not to break compatibility with the old code. But if you develop a new version, it is better to immediately use the new name.

3. XmlListModel

When working with web resources, XML is often used. In particular, it is used in such things as RSS, XSPF, various podcasts, etc. So, we have a task to get this file and parse it. Another XML may contain a list of elements (for example, a list of songs in the case of XSPF), from which we will need to create a model. It is not the most convenient way to iterate over the element tree and fill in the model manually, so you need to be able to select the elements from the XML file automatically and present them as a model. These tasks are implemented by XmlListModel.

We are required to specify the address of the XML file, specify the criterion by which we need to select the elements and determine which roles should be visible in the delegate. As a criterion for the selection of elements, we write a request in XPath format. For delegate roles, we also specify an XPath query, based on which data for the role will be obtained from the element. For simple cases like RSS parsing, these requests will also be simple and essentially describe the path in the XML file. I will not go into the jungle of XPath here, and if you don’t really understand what kind of animal it is, I recommend reading the corresponding section in the Qt documentation. Here I will use examples that do not make any tricky sampling, so I hope that everything will be clear enough.

As an example, we will get the Habr RSS feed and display the article titles.

 Rectangle { width: 360 height: 360 color: "lightsteelblue" XmlListModel { id: dataModel source: "http://habrahabr.ru/rss/hubs/" query: "/rss/channel/item" XmlRole { name: "title" query: "title/string()" } } ListView { id: view anchors.margins: 10 anchors.fill: parent spacing: 10 model: dataModel delegate: Rectangle { width: view.width height: 40 radius: 10 Text { anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight wrapMode: Text.Wrap renderType: Text.NativeRendering text: model.title } } } } 

The elements we need are blocks that are nested in, and that in turn in. From this path, we construct our first XPath expression. We will have only one role containing the title of the article. To get it, you need to take the element and bring it into a string. From this we form the second XPath expression. On this, the formation of the model is completed, it remains only to display it. As a result, we get something like this:



This model is placed in a separate module. To use it, you need to additionally connect this module:

 import QtQuick.XmlListModel 2.0 

4. FolderListModel

For many applications, access to the file system is not superfluous. QML has an experimental component for this, representing the directory of the file system as a model - FileSystemModel. To use it, you need to connect the module of the same name:

 import Qt.labs.folderlistmodel 1.0 

While he is experimental, he enters Qt Labs, but in the future he may be moved to Qt Quick or anywhere else.
In order to use the model, we need, first of all, to set the directory using the folder property. The path must be specified in the URL format, i.e. The path to the directory at the file system is set via “file:”. You can specify the path for resources using "qrc:".

You can set filters for file names using the nameFilters property, which accepts a list of masks for selecting the necessary files. You can also configure to get into the directory model and file sorting.

For example, we will get a list of files in the directory and display information about these files as a table:

 import QtQuick 2.0 import QtQuick.Controls 1.0 import Qt.labs.folderlistmodel 1.0 Rectangle { width: 600 height: 300 FolderListModel { id: dataModel showDirs: false nameFilters: [ "*.jpg", "*.png" ] folder: "file:///mnt/store/Pictures/Wallpapers" } TableView { id: view anchors.margins: 10 anchors.fill: parent model: dataModel clip: true TableViewColumn { width: 300 title: "Name" role: "fileName" } TableViewColumn { width: 100 title: "Size" role: "fileSize" } TableViewColumn { width: 100 title: "Modified" role: "fileModified" } itemDelegate: Item { Text { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter renderType: Text.NativeRendering text: styleData.value } } } } 



We remove catalogs from the model and leave only the files * .jpg and * .png.

With this model, information about the file is available for the delegate: path, name, etc. We use here the name, size and modification time.

We have learned how to access the file system. But looking at the names of the pictures may not be so very exciting, so as a bonus, let's make a slightly more interesting display of them :) We have already considered such a thing as CoverFlow. It's time to apply it here.

So, take the example of CoverFlow and change it a bit. We will take the model from the previous example. Increase the size of the item:

 property int itemSize: 400 

And change the delegate:

 delegate: Image { property real rotationAngle: PathView.angle property real rotationOrigin: PathView.origin width: itemSize height: width z: PathView.z fillMode: Image.PreserveAspectFit source: model.filePath transform: Rotation { axis { x: 0; y: 1; z: 0 } angle: rotationAngle origin.x: rotationOrigin } } 

Now let's take a look at the cool thing we did:



FolderListModel is a very useful component that gives us access to the file system and, despite its experimentation, it is quite possible to use it now.

5. JavaScript Models

In addition to specially designed to create models of components, many other objects can also act as a model. And this option may even be easier than using special components for the model.

In general, such models are obtained passive, and are suitable when the number of elements is fixed or rarely changes.

We will consider these types as models:


1) Lists / arrays


You can use ordinary JavaScript arrays as a model. A delegate will be created for each array element and the data of the array element itself will be available in the delegate through the modelData property.

 import QtQuick 2.0 Rectangle { property var dataModel: [ { color: "orange" }, { color: "skyblue", text: "second" } ] width: 360 height: 240 ListView { id: view anchors.margins: 10 anchors.fill: parent spacing: 10 model: dataModel delegate: Rectangle { width: view.width height: 100 color: modelData.color Text { anchors.centerIn: parent renderType: Text.NativeRendering text: modelData.text || "empty text" } } } } 

If there are objects in the array, then modelData will also be an object and will contain all the properties of the original object. If the elements are simple values, then they will be modelData. For example:

 property var dataModel: [ "orange", "skyblue" ] 

and in the delegate we refer to the model data as follows:

 color: modelData 

And just like in ListModel, we can put a function in these models. As with the ListModel, if you put it in a regular JavaScript object, then in the delegate it will be visible as an empty object. Therefore, here we also use the trick with QtObject.

 property var dataModel: [ { color: "orange", functions: obj }, { color: "skyblue", text: "second", functions: obj } ] QtObject { id: obj function alive() { console.log("It's alive!") } } 

And in the delegate we call the function:

 Component.onCompleted: modelData.functions.alive() 

I have already said that almost all JavaScript models are passive and this one is not an exception. When you change items and add / delete them, the view will not know that they have changed. This happens because the properties of JavaScript objects have no signals that are called when the property changes, unlike Qt objects and, accordingly, QML objects. The view will receive a signal if we change the property itself used as a model, replace the model. But there is one trick: we can not only assign a new model to this property, but also reassign the old one. For example:

 dataModel.push({ color: "skyblue", text: "something new" }) dataModel = dataModel 

This model is well suited for data that comes from web resources and is rarely updated and / or completely.

2) objects

JavaScript objects and QML objects can act as a model. This model will have one element and the properties of the object will be roles in the delegate.
Take the very first example and redo it to use a JavaScript object as a model:

 property var dataModel: null Component.onCompleted: { dataModel = { color: "orange", text: "some text" } } 

Object properties in the delegate are available via modelData:

 color: modelData.color 

As with JavaScript arrays, changing an object after it has been set as a model does not affect the display, i.e. This is also a passive model.

I also referred to JavaScript models using one QML object as a model. Although these objects can be used as a full-fledged QML model, in terms of functionality it is almost an analogue of using a regular JavaScript object, with some features. Therefore, I consider them together.

Let's change the same example for use as a QML object model:

 Item { id: dataModel property color color: "orange" property string text: "some text" } 

Item is selected here to show that any QML object can be a model. In practice, if you only need to store data, QtObject is best suited. This is the most basic and, accordingly, the lightest QML object. Item, in this case, contains too much extra.

With such a model, the data in the delegate is available through both model and modelData.

Also, this model is the only active of the JavaScript-models. Since the properties of QML objects have signals that are called when a property changes, changing a property in an object will change the data in the delegate.

3) Integer

The simplest model :) We can use an integer as a model. This number is the number of model elements.

 property int dataModel: 5 

Or you can directly specify a constant as a model:

 model: 5 

The delegate will have a modelData property that contains an index. The index will also be available through model.index.

This model is well suited when you need to create a certain number of identical elements.

As output

We reviewed the models that are implemented by QML and JavaScript. There are many options, but from myself I’ll say that the most frequently used are ListModel and JavaScript arrays.

The considered models are implemented quite simply, if we do not need any special tricks (such as storing functions in ListModel). In cases where this option is suitable, we can implement all the components of MVC in one language and thereby reduce the complexity of the program.

But, I want to draw attention to one thing. It is not necessary to drag everything into QML, you should be guided by practical considerations. Some things may be easier to implement in C ++. It is the C ++ model that we will look at in the next section.

Source: https://habr.com/ru/post/195706/


All Articles