How to use QML form in QGIS V3.x
"QML is a user interface markup language. It is a declarative language for designing user interface–centric applications. Inline JavaScript code handles imperative aspects. It is associated with Qt Quick, the UI creation kit originally developed by Nokia within the Qt framework." - Wikipedia
QML forms were introduced in later version of QGIS 2.x branches (See here)
QML forms are fully functinal in QGIS 3.x branches.
This tutorial is an introduction to QML form design to display information about a clicked feature in QGIS.
QML forms are used to produce nice looking information forms that are used by end-users, see image on the right (note background effect when mouse is over a field).
As a by-product we will also show how to establish one-to-many relations between tables.
In order to follow this tutorial you will need the following:
The final project can be downloaded from here, if you do not want to work with your own data.
In order to illustrate this tutorial let's say we have been tasked to show on a map a list of towns that have been painted by various artists. This means that each town location will link to one or more artists. Furthermore we have an aerial photo of the town and a link to that town information that we want to show the user, and, for each artist we have an image of his/her painting and a link to his/her bio.
For simplicity, all data is stored in a spreadsheet file having three tabs:
town: with fields: id
, location
, name
, image
(aerial photo) and link
(to info about town):
author: with fields: id
, name
and link
to artist's web page:
lkup: lookup table to establish the one-to-many relation between town and author tables, having fields: IDC
(the town's id), IDA
(the author's id) and image
Figure 2: Add "Spreadsheet Layers" plugin
Figure 3: Add spreadsheet layer
Figure 4: Form used to load each sheet into a layer from spreadsheet
In this order:
Figure 6: Relation between lkup and author tables
Figure 7: Relation between town layer and lkup table
Figure 8: Default Feature Attributes form
The default form format is plain boring, it shows the one-to-many relation. But user has to click an id to open more info in the PAINT pane.
Let's improve with QML... What about displaying images and creating links?
QML is made of imbricated objects that interact with each other and with the user.
Figure 9: Drag and drop designer mode
Configure QML Widget window opens
Figure 10: QML design form
In your favourite text editor:
import QtQuick 2.0
This is the library containing all the necessary QtQuick objects used by QML.
Rectangle
Rectangle { id: theRect width: 400; height: 2000 ... }
Note: Commands can be on the same line, in that case use ;
to separate them. If one command per line then no need to end the line with ;
Rectangle { width: 400; height: 2000 Column { spacing: 5 ... } }
Rectangle
and a Text
object to define the field we want to display (name). No need to display ids or links
Rectangle { width: 400 height: 26 radius: 6 color: "lightblue" Text{ anchors.centerIn: parent font.pointSize: 14 font.bold: true text: expression.evaluate(" concat(' ',\"name\")") } }
Rectangle
is a positional objectText
is an object that needs to be inside a positional objectexpression.evaluate("")
, field name must be between double-quotes, as defined by normal QGIS relation syntax, and those double-quotes are escaped within the double-quotes of expression.evaluate
.concat()
is used to add space to the left of name.Note the use of radius
and color
to define the look of Rectangle
. Also text characteristics are fully customisable.
Image
object to display the image field of town table:Image { width:400 sourceSize.width: 400 horizontalAlignment: Image.AlignLeft source: expression.evaluate(" concat('http://www.geoproc.com/be/qmlimg/', \"image\") ") MouseArea { id: mouseArea1 anchors.fill: parent hoverEnabled: true onClicked: Qt.openUrlExternally( expression.evaluate(" \"link\" ") ) } }
source
parameter (URL format) tells where the image is stored. Here a web address is used but URL can point to a local resource (use: file:///path/to/local/resources/
format). Even on Windows system the directory separator is /
, not \
. You need to adjust that url to your specs.MouseArea
object is used to create a link so that the user can mouse-over and click the image to open more info about the town. In this case a web site giving information about the town itself.Rectangle
/Text
objects:Rectangle { width: 400 height: 26 radius: 6 color: "white" Text{ font.pointSize: 14; color:"blue" font.underline: true font.italic: true text: "Painters" } }
Repeater
object to define a template for what to show:Repeater { ... }
Repeater
needs a data source to repeat (i.e. a model
). Here is all the fun to play with SQL-like syntax in order to define what information to extract from the databaseRepeater { model: expression.evaluate("string_to_array(relation_aggregate( 'paintings__IDC_paintings__id','concatenate',coalesce(\"author_Author\",'')||'ÿ'||coalesce(\"image\",'')||')ÿ'||coalesce(\"author_link\",''), ','))") ... }
model
, i.e. an object storing the results of a database query. Here it is an array (string_to_array(string, delimiter)
) using QGIS expression syntax within an expression.evaluate
function.relation_aggregate
to return a string made of the fields we want to showrelation_aggregate(relation_name, operator, sql_definition)
. When operator
is concatenate
, return is a string with all matching records separated by comas
relation_name
is found by clicking PAINT
) and copy its content (paintings__IDC_paintings__id
)
Figure 11: Relation's name: paintings__IDC_paintings__id
ÿ
as a component separator to allow further processingcoalesce()
is used to make sure no Null
are returnedmodelData
objectRepeater { model: expression.evaluate("string_to_array(relation_aggregate( 'paintings__IDC_paintings__id','concatenate',coalesce(\"author_Author\",'')||'ÿ'||coalesce(\"image\",'')||'ÿ'||coalesce(\"author_link\",''), ','))") Row { spacing: 2 Image { id: imgField width: 98 sourceSize.width: 98 source: 'http://www.geoproc.com/be/qmlimg/' + modelData.toString().split("ÿ")[1] MouseArea { id: mouseAreaI anchors.fill: parent onClicked: Qt.openUrlExternally(modelData.toString().split("ÿ")[2] ) } } Rectangle { id: linkField width: 298; height:20; radius: 6 color: mouseAreaL.containsMouse ? "#ccccee" : "#eecccc" Text{ font.weight:Font.ExtraLight; text: ' ' + modelData.toString().split("ÿ")[0] } MouseArea { id: mouseAreaL anchors.fill: parent hoverEnabled: true onPressed: linkField.state = PRESSED onReleased: linkField.state = RELEASED onClicked: Qt.openUrlExternally(modelData.toString().split("ÿ")[2] ) } states: [ State { name: PRESSED PropertyChanges { target: linkField; color: "lightgreen"} }, State { name: RELEASED PropertyChanges { target: linkField; color: "#eecccc"} } ] transitions: [ Transition { from: PRESSED to: RELEASED ColorAnimation { target: linkField; duration: 1000} }, Transition { from: RELEASED to: PRESSED ColorAnimation { target: linkField; duration: 1000} } ] } }
States
and transitions
code taken from opengis.ch blog entry on QMLRow
object to display an image of the painting to the left and the painter's name on the right. Both fields can be clicked and will open a web browser window displaying more information about the painter.Flickable
object.Final code is shown below:
import QtQuick 2.0 Flickable { id: theFlick width: 400; height: 800 contentWidth: theRect.width; contentHeight: theRect.height clip: true Rectangle { width: 400; height: 2000 Column { spacing: 5 Rectangle { width: 400 height: 26 radius: 6 color: "lightblue" Text{ anchors.centerIn: parent font.pointSize: 14 font.bold: true text: expression.evaluate(" concat(' ',\"name\")") } } Image { width:400 sourceSize.width: 400 horizontalAlignment: Image.AlignLeft source: expression.evaluate(" concat('http://www.geoproc.com/be/qmlimg/', \"image\") ") MouseArea { id: mouseArea1 anchors.fill: parent hoverEnabled: true onClicked: Qt.openUrlExternally( expression.evaluate(" \"link\" ") ) } } Rectangle { width: 400 height: 26 radius: 6 color: "white" Text{ font.pointSize: 14; color:"blue" font.underline: true font.italic: true text: "Painters" } } Repeater { model: expression.evaluate("string_to_array(relation_aggregate( 'paintings__IDC_paintings__id','concatenate',coalesce(\"author_Author\",'')||'ÿ'||coalesce(\"image\",'')||'ÿ'||coalesce(\"author_link\",''), ','))") Row { spacing: 2 Image { id: imgField width: 98; sourceSize.width: 98 source: 'http://www.geoproc.com/be/qmlimg/' + modelData.toString().split("ÿ")[1] MouseArea { id: mouseAreaI anchors.fill: parent onClicked: Qt.openUrlExternally(modelData.toString().split("ÿ")[2] ) } } Rectangle { id: linkField width: 298; height:20; radius: 6 color: mouseAreaL.containsMouse ? "#ccccee" : "#eecccc" Text{ font.weight:Font.ExtraLight; text: ' ' + modelData.toString().split("ÿ")[0] } MouseArea { id: mouseAreaL anchors.fill: parent hoverEnabled: true onPressed: linkField.state = PRESSED onReleased: linkField.state = RELEASED onClicked: Qt.openUrlExternally(modelData.toString().split("ÿ")[2] ) } states: [ State { name: PRESSED PropertyChanges { target: linkField; color: "lightgreen"} }, State { name: RELEASED PropertyChanges { target: linkField; color: "#eecccc"} } ] transitions: [ Transition { from: PRESSED to: RELEASED ColorAnimation { target: linkField; duration: 1000} }, Transition { from: RELEASED to: PRESSED ColorAnimation { target: linkField; duration: 1000} } ] } } } } } }
Copy-paste from your text editor into the text box and a sample of the form should display on the right. If nothing is displayed then there is a bug in the code and it needs to be checked!!
Figure 12: Testing the QML code
Note:
Flickable
does not work!QML forms are a very powerful way to lighten up data presentation to users. With a bit of practice very pleasant results can be achieved.
And, it is not that difficult to learn QML!
Hopefully this short tutorial has shown you what can be done and gave you the incentive to investigate further.
Image references
Published date: 04 Aug 2019.