-
Notifications
You must be signed in to change notification settings - Fork 0
3.02
Tino: New suggestion. Views
Tino: Let's have this before the delegate part (previous session) and let's introduce List, Grid, Path views. Let's very shortly introduce delegates, highlight, header, footer, keyboard handling.
Explanation of the contents of a topic page @ Topic reference page
Objective: Using QML views
Tino: Using QML views
- What is a view?
- What is a list, grid, and path views?
- How delegates are created?
- What is a cache buffer?
- How keyboard is used in delegates?
- How many items is shown by the view?
- What is a hater and footer?
For dynamic views Qt Quick provides two commonly used types, ListView
and GridView
. They both inherit from the Flickable
type, which enables users to scroll around in a larger data set. Third view provided by Quick is PathView
, which is a more powerful/customisable/word/thing view, but it's also slightly more complex. In section 3.00 we quickly introduced the ListView
type, and now we will have a more thorough look into the three different views. We'll start by having a more in-depth look into ListView
.
ListView
is a simple type, and in many ways similar in usage to Repeater
covered in section 3.00. The data presented comes from a model
, and the view instantiates a delegate
which is used to present the data. The model
can be an actual model type, such as ListModel
, XmlListModel
, or a custom model defined in C++, or it can be a simple integer, as in the following example.
J: This might be the simple example we give already in 3.00 if we want to have a quick intro to views there already, will see.
ListView {
anchors.fill: parent
anchors.margins: 10
clip: true
model: 50
delegate: numberDelegate
spacing: 5
}
Component {
id: numberDelegate
Rectangle {
width: 35
height: 35
color: "lightGreen"
border.color: "black"
Text {
text: index
font.pointSize: 12
anchors.centerIn: parent
}
}
}
J: Constantly moving thing in the material is probably annoying, might replace with just a picture.
In the above example we used an explicitly defined delegate. More thorough introduction to delegates will follow later in this part of the course, but let's cover some basics here. Delegate is the third part of Quick's model-view concept. A view will visualize each item list according to the template defined by the delegate. Each delegate gets an access to a number of attached properties, both from the model and the view. For example, the ListView to which the delegate is bound is accessible from the delegate through the ListView.view
property.
The clip
property will ensure that any list items outside of the view will not be visible. If set false
, items will 'flow over' the view. It should be noted that we should avoid using clip
in the delegate. If clip
is enabled inside a delegate, each delegate will be batched separately (i.e. there will be an OpenGL state change between each batch), which affects the rendering performance. By allowing the view (parent of the delegates) to do the clipping, as in the above example, there will be only one batch in the best case.
There are plenty of other behaviours we can change as well, such as orientation of the list (ListView.Vertical
vs ListView.Horizontal
), all of which are can be viewed in the ListView documentation.
Views allow visual customization through decoration properties such as the header
and footer
. By binding an object, usually another visual object, to these properties, the views are decoratable. As an example, a footer may include a Rectangle
type showcasing borders, or a header that displays a logo on top of the list. It should be noted that headers and footers don't respect the spacing
property in ListView
, and thus any spacing needs to be a part of the header/footer itself.
Window {
visible: true
width: 200
height: 480
title: qsTr("Hello World")
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 4
delegate: numberDelegate
spacing: 2
header: headerComponent
footer: footerComponent
}
Component {
id: headerComponent
Rectangle {
width: ListView.view.width
height: 20
color: "lightBlue"
Text { text: 'Header'; anchors.centerIn: parent; }
}
}
Component {
id: footerComponent
Rectangle {
width: ListView.view.width
height: 20
color: "lightGreen"
Text { text: 'Footer'; anchors.centerIn: parent; }
}
}
Component {
id: numberDelegate
Rectangle {
width: ListView.view.width
height: 40
border.color: "black"
Text { text: 'Item ' + index; anchors.centerIn: parent; }
}
}
}
When using a keyboard to navigate in the ListView, some form of highlighting is necessary to tell which item is currently selected. Two things are necessary to allow keyboard navigation in the ListView. First, the view needs to be given keyboard focus with the property focus: true
, and second a special highlight delegate needs to be defined. This is demonstrated in the following example:
ListView {
id: view
anchors.fill: parent
anchors.margins: 20
clip: true
model: 20
delegate: numberDelegate
spacing: 5
highlight: highlightComponent
focus: true
}
Component {
id: highlightComponent
Rectangle {
color: "lightblue"
radius: 10
}
}
Component {
id: numberDelegate
Item {
width: ListView.view.width
height: 40
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
The highlight delegate is given the x
, y
and height
of the current item. If the width
is not specified, the width of the current item is used.
Sometimes we don't want to just list the data, but divide it up in categories. For example, albums by artists, or employees under their departments. With ListView
we can divide the displayed data into categories, or sections
.
When using sections, two properties need to be considered. In ListView
the property section.property
defines which property is used to divide the data into sections. It's important to note that the model needs to be sorted so that each property forming a section is continuous. If a property forming the section appears in multiple non-continuos places in the model, the same section might appeart multiple times in the view. The second property to be considered is section.criteria
. It's set to ViewSection.FullString
by default, which means that the whole property is used as the section. If set to ViewSection.FirstCharacter
only the first character in the property is used for the section, for example when dividing up a list of names to sections based on the first letter of the last name.
After the sections have been defined, they can be accessed from the items using the attached properties ListView.section
, ListView.previousSection
, and ListView.nextSection
.
We can also assign a special section delegate to create a header before each section, by assigning it to the property section.delegate
in the ListView
.
In the following example the data model has a list of artist and their albums, which are divided into sections by artist.
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: albumsAndArtists
delegate: albumDelegate
section.property: "artist"
section.delegate: sectionDelegate
}
Component {
id: albumDelegate
Item {
width: ListView.view.width
height: 20
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
font.pixelSize: 12
text: album
}
}
}
Component {
id: sectionDelegate
Rectangle {
width: ListView.view.width
height: 20
color: "lightblue"
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 6
font.pixelSize: 14
text: section
}
}
}
ListModel {
id: albumsAndArtists
ListElement { album: "Crazy World"; artist: "Scorpions"; }
ListElement { album: "Love at First Sting"; artist: "Scorpions"; }
ListElement { album: "Agents of Fortune"; artist: "Blue Öyster Cult"; }
ListElement { album: "Spectres"; artist: "Blue Öyster Cult"; }
ListElement { album: "The Vale of Shadows"; artist: "Red Raven Down"; }
ListElement { album: "Definitely Maybe"; artist: "Oasis"; }
ListElement { album: "(What's the Story) Morning Glory?"; artist: "Oasis"; }
ListElement { album: "Moving Pictures"; artist: "Rush"; }
ListElement { album: "Dookie"; artist: "Green Day"; }
ListElement { album: "Eclipse"; artist: "Amorphis"; }
ListElement { album: "High Voltage"; artist: "AC/DC"; }
ListElement { album: "Highway to Hell"; artist: "AC/DC"; }
ListElement { album: "1984"; artist: "Van Halen"; }
}
GridView
is largely similar to ListView
, and it's used in almost the same way. Main difference is, that it doen't rely on spacing and size of delegates, and instead cellWidth
and cellHeight
are defined in the view. Header, footers, keyboard navigation with highlighting are all available in GridView
. Orientation is set in the flow
property, options being GridView.LeftToRight
(default, grid is filled from left to right, and rows added to the bottom) and GridView.TopToBottom
(grid is filled from top to bottom, and columns are added to the right. Scrolling is horizontal). The following example showcases the usage of GridView
.
GridView {
id: grid
anchors.fill: parent
cellWidth: 80;
cellHeight: 80
model: 100
delegate: numberDelegate
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
focus: true
}
Component {
id: numberDelegate
Item {
width: grid.cellWidth
height: grid.cellHeight
Text {
text: index
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
}
}
PathView is the most powerful and customisable of the dynamic views provided by Qt Quick, though it's also a little bit more complex to use. In this section we'll familiarize ourselves with PathView
. PathView
will display the model data on a Path that can be arbitarily defined. When using PathView
we need to define the Path
and a delegate
. Beyond these, we can customize PathView
through a wide variety of properties, such as pathItemCount
, which controls the numbers of visible items at once, and preferredHighlightBegin
and preferredHighlightEnd
, which are used to control where along the path the current item is shown. They expect real values between 0 and 1. Setting both of them to, for instance, 0.5 would display the current item at the location 50% along the path.
A Path
is the path that the delegates follow when the view is scrolled. It defines the path using startX
and startY
properties, alongside path segments that are defined types such as PathQuad or PathLine. All the different path segments can be found in the official Qt documentation.
This example shows items on a straight path:
PathView {
id: view
model: 20
anchors.fill: parent
path: Path {
startX: 0
startY: height
PathCurve {
x: view.width
y: 0
}
}
delegate: Text {
text: "Index " + index
}
}
When the path has been defined, we can further tune it using PathPercent
and PathAttribute
objects. These objects can be placed in between the path segments to provide a more fine grained control over the path and the delegates. The PathPercent
allows you to manipulate the spacing between items on a PathView's path. You can use it to bunch together items on part of the path, and spread them out on other parts of the path. The PathAttribute
object allows attributes consisting of a name and a value to be specified for various points along the path. The attributes are exposed to the delegate as Attached Properties, and can be used to control any property. The value of an attribute at any particular point along the path is interpolated from the PathAttributes
bounding that point.
Next we have a larger example of PathView
, where the path is defined with PathQuad
and the size and opacity of the items is changed with PathAttribute
. We've also enabled keyboard navigation, which is not available by default. It can be done by giving the keyboard focus by setting the focus
property to true, and defining what the keys should do (in this case Keys.onLeftPressed: decrementCurrentIndex()
and Keys.onRightPressed: incrementCurrentIndex()
to allov moving back and forth).
/// ContactModel.qml
ListModel {
ListElement { name: "Linus Torvalds"; icon: "pics/qtlogo.png"; }
ListElement { name: "Alan Turing"; icon: "pics/qtlogo.png"; }
ListElement { name: "Margaret Hamilton"; icon: "pics/qtlogo.png"; }
ListElement { name: "Ada Lovelace"; icon: "pics/qtlogo.png"; }
ListElement { name: "Tim Berners-Lee"; icon: "pics/qtlogo.png"; }
ListElement { name: "Grace Hopper"; icon: "pics/qtlogo.png"; }
}
Rectangle {
width: 250; height: 200
Component {
id: delegate
Item {
width: 80; height: 80
scale: PathView.iconScale
opacity: PathView.iconOpacity
Column {
Image { anchors.horizontalCenter: nameText.horizontalCenter; width: 64; height: 64; source: icon }
Text { id: nameText; text: name; font.pointSize: 12 }
}
}
}
PathView {
anchors.fill: parent
model: ContactModel {}
delegate: delegate
focus: true
Keys.onLeftPressed: decrementCurrentIndex()
Keys.onRightPressed: incrementCurrentIndex()
path: Path {
startX: 120
startY: 100
PathAttribute { name: "iconScale"; value: 1.0 }
PathAttribute { name: "iconOpacity"; value: 1.0 }
PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 }
PathAttribute { name: "iconScale"; value: 0.3 }
PathAttribute { name: "iconOpacity"; value: 0.5 }
PathQuad { x: 120; y: 100; controlX: -20; controlY: 0 }
}
}
}
Tip from QML Book: "When transforming images or other complex elements on in PathView, a performance optimization trick that is common to use is to bind the smooth property of the Image element to the attached property PathView.view.moving
. This means that the images are less pretty while moving, but smoothly transformed when stationary. There is no point spending processing power on smooth scaling when the view is in motion, as the user will not be able to see this anyway."
https://qmlbook.github.io/en/ch06/index.html
https://doc.qt.io/qt-5/qml-qtquick-listview.html
https://doc.qt.io/qt-5/qml-qtquick-gridview.html
https://doc.qt.io/qt-5/qml-qtquick-pathview.html