GeoProc.com
GeoProc.com

How to use QML form in QGIS V3.x

Figure 1: Final QML form

An introduction to Qt Markup Language (QML) for form design in QGIS versions 3.4 and above.

"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.

Objective

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.

Content

  1. What do we need
  2. Data used
  3. QGIS project setup
  4. QML design
  5. Conclusion

1 - What is needed?

In order to follow this tutorial you will need the following:

  • QGIS ≥3.4
  • Spreadsheet Layers plugin
  • data: in this case, a simple spreadsheet

The final project can be downloaded from here, if you do not want to work with your own data.

2 - Data

Who has painted that town?

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.

Data

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):

  • Saint Paul de Vence, France
  • Venice, Italy

author: with fields: id, name and link to artist's web page:

  • Claude Monet

  • Canaletto

  • Guido Borelli

  • Marilyn Dunlap

  • Pierre Auguste Renoir

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

3 - Set-up GIS project


Figure 2: Add "Spreadsheet Layers" plugin

Frame3
Figure 3: Add spreadsheet layer


Figure 4: Form used to load each sheet into a layer from spreadsheet


Figure 5: Styled town layer

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?

4 - Introduction to QML form design

QML is made of imbricated objects that interact with each other and with the user.

Accessing QML from QGIS

Frame9
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 {
    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 {
               width: 400
               height: 26
               radius: 6
               color: "lightblue"
               Text{ anchors.centerIn: parent
                     font.pointSize: 14
                     font.bold: true
                     text: expression.evaluate(" concat('   ',\"name\")") }
            }

Note the use of radius and color to define the look of Rectangle. Also text characteristics are fully customisable.

            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 {
                ...
            }
            Repeater {
                model: expression.evaluate("string_to_array(relation_aggregate( 'paintings__IDC_paintings__id','concatenate',coalesce(\"author_Author\",'')||'ÿ'||coalesce(\"image\",'')||')ÿ'||coalesce(\"author_link\",''), ','))")
                ...
            }


Figure 11: Relation's name: paintings__IDC_paintings__id

             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}
                            }
                        ]
                    }
                }

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:

  • Warning: It seems that a qml form cannot be edited. If you plan to add new data from within QGIS you need to delete the QML form and design a more traditional, simple form.
  • In the example given here, the Flickable does not work!
  • Hovering over artist's name changes background colour

QML objects reference

5 - Conclusion

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.