-
Notifications
You must be signed in to change notification settings - Fork 0
3.00
Tino: This should be: Introduction to QML model/view framework
Explanation of the contents of a topic page @ Topic reference page
Objective: Learn how to use model/view framework in QML
J: Specifics of what is supposed to be taught here escapes me a bit, I assume at least a general introduction to the model/view concept. I'm working on an assumption that both ListView and GridView are introduced here, and the later sections that mention them focus on comparing them to the second one (TreeView/TableView). If this section ends up having too many things in it, it can be broken down to the other sections.
J: Should header/footers, keyboard navigation, highlighting, be introduced here already?
K: IMHO ListView and GridView can be discussed fully in 3.03 and here we just intro the MVD architecture
K: Added model manipulation
Tino: I would keep this as a generic introduction, as my title change suggests. So, the objective could be: Learn, how to use model/view framework in QML.
K: Shortened this to be more of an generic introduction and moved stuff to 3.02
- What are View, Model, and Delegate?
- How do I use ListModel?
- How do I use ListView?
- How do delegates work?
When designing user interfaces, it's important to separate the data from the visual representation. This way changing the representation of the data is easy and straightforward. Qt Quick manages this by following the Model-View pattern. There are models, views, and delegates to display data. They modularize the visualization of data in order to give the developer or designer control over the different aspects of the data. A developer can swap a list to a grid with little changes to the data. Similarly, encapsulating an instance of the data in a delegate allows the developer to dictate how to present or handle a specific data item.
- Model: Contains the data and its structure. There are several QML types for creating models.
- View: A container that displays the data. The view might display the data in a list or a grid.
- Delegate: Dictates how the data should appear in the view. The delegate takes each data in the model and encapsulates it. The data is accessible through the delegate.
The Repeater
type is the most basic way of separating data from the user interface. Repeater
generates items, which are then typically laid out with Item positioners, using data from a model. Combining Repeater
s and positioners is an easy way to lay out lots of items. A Repeater
item can be placed inside a positioner which then arranges the generated items accordingly.
Each Repeater creates a number of items by combining each element of data from a model, specified using the model property. The total number of items is determined by the data in the model. The delegate can be defined as a child item within the Repeater or with the delegate
property. From the given delegate we create instances of it and give each instance access to the data from the model, and each instance will display the data as defined.
The following example shows a Repeater used with a Row
positioner item to arrange a set of Rectangle
items. The Repeater item creates a series of 10 rectangles for the Row
item. The delegate has access to the read-only index
property from the Repeater
, so we can use that to display the index of each delegate instance.
Row {
spacing: 2
Repeater {
model: 10
Rectangle {
width: 70
height: 70
color: "lightgreen"
Text {
text: index
font.pointSize: 30
anchors.centerIn: parent
}
}
}
}
The number of items created by a Repeater
is held by its count
property. It is not possible to set this property to determine the number of items to be created. Instead, as in the above example, we use an integer as the model. We could also replace the integer model value with a JavaScript array, and hold other values, such as strings or objects.
Keep in mind, that the items are created synchronously when the Repeater
is created, so items from a large model will take some time to create. We will later discuss how to load items asynchronously when they are needed.
Tino: I would add here that Repeater creates the items synchronously so creating one million item may be quite time-consuming. QML allows creating delegates also asynchronously, which will be covered later.
Usually we want to have more than one piece of data per an item in an array. This is where models step in. A simple and commonly used model is ListModel
, which is a container for ListElement
definitions. With ListElement
, we are defining data role definitions instead of properties, even though the syntax is the same. These need to be common with all other elements in a given model.
In the following example there are two roles, the name of a person and a color representing their team. The roles are attached to each instantiated delegate by the Repeater,
and thus the properties name
and teamColor
are available from within the scope of each Rectangle
and Text
item.
Column {
spacing: 2
Repeater {
model: ListModel {
ListElement { name: "Lars Knoll"; teamColor: "red" }
ListElement { name: "Alan Kay"; teamColor: "lightBlue" }
ListElement { name: "Trygve Reenskaug"; teamColor: "red" }
ListElement { name: "Adele Goldberg"; teamColor: "yellow" }
ListElement { name: "Ole-Johan Dahl"; teamColor: "red" }
ListElement { name: "Kristen Nygaard"; teamColor: "lightBlue" }
}
Rectangle {
width: 150
height: 35
radius: 5
color: teamColor
Text {
text: name
font.pointSize: 12
anchors.centerIn: parent
}
}
}
}
Repeaters are nice and useful for laying out small sets of data. However, in some situations there is a need to display and navigate a subset of a larger data set. Qt Quick provides various dynamic views such as ListView
or GridView
to do exactly this. Contrary to Repeater
, these views instantiate and destroy the delegates automatically when they come and go out of view.
Here is an example featuring ListView
using ListModel
and a simple delegate:
ListView {
anchors.fill: parent
model: ListModel {
ListElement { name: "Lars Knoll" }
ListElement { name: "Alan Kay" }
ListElement { name: "Trygve Reenskaug" }
ListElement { name: "Adele Goldberg" }
ListElement { name: "Ole-Johan Dahl" }
ListElement { name: "Kristen Nygaard" }
// more data...
}
delegate: Text {
text: name
}
}
As you can see, the primary usage is very similar to Repeater
. We will discuss more advanced usage such as navigation and layout along with other dynamic views more thoroughly in later parts.
We have been using static data inside our ListModel
, but we can also modify the contents dynamically with the methods append()
, insert()
, move()
and remove()
.
Here is an example of adding data to a ListModel
with append
:
ListModel {
id: teamModel
ListElement {
name: "Team C++"
}
}
ListView {
anchors.fill: parent
model: teamModel
delegate: Text {
text: name
}
}
MouseArea {
anchors.fill: parent
onClicked: {
teamModel.append({ name: "Team Quick" })
}
}
To create new items, you can use JSON { "teamName": "Team Qt", "teamColor": "green" }
or JavaScript notation { teamName: "Team Qt", teamColor: "green" }
. It should be noted that the first item inserted or declared to the model will determine the roles available to any views. In other words, if the model is empty and an item is added, only roles defined in that item are bound and used in subsequent added items. To reset the roles, the model needs to be emptied with the ListModel::clear() method.
Modifying the model can be computationally expensive in some cases, and because we are doing it inside the UI thread, it can end up being blocked for quite a while. Computationally heavy updates should preferably be done inside a background thread. For example, if we were to implement a Twitch chat client, we would run into performance problems very fast if we were to use the main thread for updating the message model:
TwitchChat {
// signal messageReceived(string user, string message)
onMessageReceived: {
// Massage the received message data inside the signal handler ...
// Remove old messages from backlog ...
if (messageModel.count > 2147483647)
messageModel.remove(0, 1337)
// Append the message data to the model
messageModel.append({"user": user, "message": message})
}
}
We can use WorkerScript
to offload any heavy data and model manipulation out of the main thread. Messages are passed between the parent thread and new child thread with the sendMessage()
method and onMessage()
handler:
TwitchChat {
// signal messageReceived(string user, string message)
onMessageReceived: {
worker.sendMessage({"user": user, "message": message, "model": messageModel)
}
}
WorkerScript {
id: worker
source: 'script.js'
}
We then need to define the onMessage
handler inside its own script.js
file which is invoked when we call sendMessage()
:
// script.js
WorkerScript.onMessage = function(msg) {
// Massage the received message data ...
// Remove old messages from backlog ...
if (msg.model.count > 2147483647)
msg.model.remove(0, 1337)
// Append the message data to the model
msg.model.append({"user": user, "message": message});
msg.model.sync(); // Needs to be called!
}
Notice how we don't import the script.js
file anywhere! This brings in the restrictions of this threading model: the source
property is evaluated on its own outside of QML. This means we don't have access to any properties or methods inside your QML, and all data needs to be passed to sendMessage()
. The sendMessage()
supports all JavaScript types and objects. Arbitary QObject
types are not supported, only the ListModel
type. Also, if you want your modifications to take effect in the ListModel
, you must call sync()
on it on the worker thread.
Tino: Threaded model manipulation should be added. Molde manipulation may be CPU intensive and may block the GUI thread. That's why it should be done in the background thread, if ether are many items to be manipulated... QML support model manipulations in a worker thread (perhaps a small example). Alternatively, a C++ model can be used and manipulated in a thread created in C++. (My wording is poor, but hopefully you get the idea.)
Kimmo: I have added some WorkerScript introduction there because it looks pretty straightforward, I'll make a note if we can introduce the C++ model and threading in the later parts. I think we have not talked about QThread in the previous parts yet
Possible exercises:
- Start with a simple ListModel and Repeater task
- ListView/ListModel append and modification
- Fetch something or load a JSON document and display it
- Do something asynchronously with WorkerScript?
https://doc-snapshots.qt.io/qt5-5.9/qtquick-modelviewsdata-modelview.html