QField on nyt entistä monipuolisempi – näin tehdään QField-lisäosa
Kesäkuussa 2024 julkaistiin QFieldin versio 3.3.0 – Darién, jossa yhtenä uutena merkittävänä ominaisuutena saatiin tuki lisäosien rakentamiselle. Jatkossa siis kuka tahansa voi luoda QFieldille omia lisäosia yleiseen, omaan tai organisaation käyttöön, aivan kuten QGISissä. QGIS-lisäosista poiketen QFieldissä ohjelmointikielenä toimii Pythonin sijaan Qt-ympäristöön kuuluva QML (Qt Modeling Language), jota käytetään lisäosan käyttöliittymän luomiseen ja joka sisältää myös tuen JavaScript-kielelle, jolla toteutetaan lisäosan varsinaiset toiminnallisuudet.
QField-lisäosa
Lisäosia voi QFieldissä käyttää kahdella tapaa, projektikohtaisena tai koko sovelluksen laajuisena. Projektikohtaisen lisäosan lähdekoodi tulee sisällyttää samaan kansioon QField-projektin kanssa ja antaa lisäosan .qml-tiedostolle sama nimi kuin projektitiedostolle. Sovelluslisäosan voi asentaa käyttöliittymän kautta syöttämällä linkin, josta lisäosa on ladattavissa .zip-tiedostona.

Lisäosan kehittämisessä on kätevää käyttää projektikohtaista lisäosaa, koska sen voi päivittää ja asentaa testikäyttöön yksinkertaisesti kopioimalla lähdekoodin projektitiedoston kanssa samaan sijaintiin ja avaamalla projektitiedoston. Vaikka QField on tarkoitettu ensisijaisesti puhelin- ja tablettikäyttöön, siitä löytyy myös työpöytäversio, mikä helpottaa lisäosien kehittämistä ja testaamista. Täältä voit ladata viimeisimmän QField-julkaisun työpöytäkäyttöön.
Näin teet QField-lisäosan
Käydään läpi yksinkertaisen lisäosan kehittäminen vaiheittain. Voit kokeilla pluginia tallentamalla samaan kansioon QGIS-projektin ja QML-tiedoston, esimerkiksi nimillä test_plugin.qgz ja test_plugin.qml. Kun avaat QGIS-projektin QFieldillä, sen pitäisi kysyä otetaanko lisäosa käyttöön.
test_plugin.qml:
import QtQuick // tuodaan QML-standardikirjasto
import org.qfield
Item { // osa QtQuickia
id: examplePlugin // määritellään tunniste lisäosalle
// samoin kuin QGIS-lisäosissa käytössä on "iface"-muuttuja,
// joka antaa pääsyn QFieldin eri komponentteihin
// tallennetaan muuttujaan viittaus sovelluksen ikkunaan
property var mainWindow: iface.mainWindow()
// kun lisäosa on luotu se lähettää "completed()"
// signaalin. Tässä määritellään ns. signal handler
// johon kirjoitetaan JavaScriptillä mitä
// halutaan tapahtuvan kun lisäosa on ladattu
Component.onCompleted: {
// tässä tapauksessa mainWindow-muuttujaa
// hyödyntäen lähetetään viesti, joka
// näkyy käyttöliittymässä hetken ajan
mainWindow.displayToast('Hello world!');
}
}
Esimerkin pitäisi näyttää tältä, kun QField avataan lisäosan kanssa:

Tähän hyvin yksinkertaiseen lisäosaan koodattu toiminto (”Hello world!”) tapahtuu, kun QField avataan. Tässä yksinkertaisessa lisäosassa toimintoa ei voi toistaa mitenkään (paitsi sulkemalla ja käynnistämällä uudestaan). Koska nappulan painaminen on kivaa ja tärkeää, lisätään lisäosaan seuraavaksi painike:
import QtQuick
import org.qfield
import Theme // tuodaan QFieldin teemakirjasto
Item {
id: examplePlugin
property var mainWindow: iface.mainWindow()
Component.onCompleted: {
// lisätään alla määriteltävä painike
// lisäosien työkalupalkkiin
iface.addItemToPluginsToolbar(pluginButton)
}
// QField sisältää valmiita käyttöliittymäelementtejä,
// joita voi hyödyntää. Tässä esimerkkinä QfToolButton
QfToolButton {
id: pluginButton
// iconSource: '' // halutessaan painikkeelle voi määrittää ikonin.
// Tällöin ikonitiedosto pitää sisällyttää osaksi lisäosaa
// Lisätään teksti painikkeelle:
Text {
text: "Toast"
anchors.centerIn: parent
font: Theme.defaultFont
color: Theme.light
}
bgcolor: Theme.darkGraySemiOpaque // määritellään painikkeen väri. QFieldin teemakirjasto sisältää valmiita värejä
round: true
// määritellään JavaScriptillä mitä tapahtuu, kun
// painiketta painetaan
onClicked: {
mainWindow.displayToast('Hello world!');
}
}
}
Nyt lisäosassamme on painike, jota painamalla Hello world! -tekstin saa ruudulle näkyviin.
Lisäosaan halutaan todennäköisesti enemmän toimintoja ja paineltavia nappuloita. Nämä sijoitetaan erikseen avautuvaan dialogi-ikkuna tai käyttöliittymään, jotta niiden käyttäminen olisi kätevää. Tehdään siis seuraavaksi yksinkertainen käyttöliittymä, joka avautuu, kun painiketta painetaan:
import QtQuick
// Uusi import-komento Dialogia varten
import QtQuick.Controls
import org.qfield
import Theme
Item {
id: examplePlugin
property var mainWindow: iface.mainWindow()
// Määritellään dialogikomponentti
Dialog {
id: dialog
parent: mainWindow.contentItem
title: "Example plugin"
// Ei näytetä dialogia heti
visible: false
// Modaalisuus viittaa tässä siihen
// että dialogi täytyy sulkea ennen
// kuin voi vuorovaikuttaa muiden
// käyttöliittymäelementtien kanssa
modal: true
focus: true
font: Theme.defaultFont
// Keskitetään dialogi
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: 300
height: 400
standardButtons: Dialog.Close
// Lisätään teksielementti
Text {
text: "This is a dialog!"
font: Theme.defaultFont
horizontalAlignment: Text.AlignLeft
}
}
QfToolButton {
id: pluginButton
bgcolor: Theme.darkGraySemiOpaque
round: true
Text {
anchors.centerIn: parent
text: "Dialog"
color: Theme.light
}
onClicked: {
// Näytetään yllä määritelty dialogi
dialog.open();
}
}
Component.onCompleted: {
iface.addItemToPluginsToolbar(pluginButton)
}
}
Nyt meillä on dialogi, muttei sisältöä. Lisätään dialogiin muutama painike, jotka demonstroivat joitakin ohjelmointirajapinnan ominaisuuksista:
import QtQuick
import QtQuick.Controls
// uusi import-komento
import QtQuick.Layouts
import org.qfield
import Theme
Item {
id: examplePlugin
property var mainWindow: iface.mainWindow()
// tallennetaan muutama olio, joita
// tarvitaan myöhemmin
property var layerTree: iface.findItemByObjectName('dashBoard').layerTree
property var mapSettings: iface.mapCanvas().mapSettings
Dialog {
id: dialog
parent: mainWindow.contentItem
title: "Example plugin"
visible: false
modal: true
focus: true
font: Theme.defaultFont
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: 300
height: 400
standardButtons: Dialog.Close
// käytetään Column- ja RowLayouteja
// järjestelemään eri käyttöliittymän
// komponentit
ColumnLayout {
spacing: 10
RowLayout {
Layout.fillWidth: true
// lisätään toimintoa kuvaava
// tekstikenttä (Label)
Label {
text: "Text Input:"
font: Theme.defaultFont
color: Theme.mainTextColor
Layout.fillWidth: true
}
// lisätään teksikenttä, johon käyttäjä
// voi kirjoittaa tekstiä
QfTextField {
id: textField
text: "Hello"
Layout.fillWidth: true
Layout.preferredWidth: 100
Layout.preferredHeight: font.height + 20
horizontalAlignment: TextInput.AlignHCenter
font: Theme.defaultFont
enabled: true
visible: true
}
// lisätään painike
QfToolButton {
id: textFieldButton
bgcolor: Theme.darkGraySemiOpaque
round: true
Text {
anchors.centerIn: parent
text: "Toast"
color: Theme.light
}
onClicked: {
// näytetään tekstikentän teksti
mainWindow.displayToast(textField.text);
}
}
}
RowLayout {
Layout.fillWidth: true
Label {
text: "List layers"
font: Theme.defaultFont
color: Theme.mainTextColor
Layout.fillWidth: true
}
QfToolButton {
id: listLayersButton
bgcolor: Theme.darkGraySemiOpaque
round: true
Text {
anchors.centerIn: parent
text: "List"
color: Theme.light
}
onClicked: {
let list = "Layers:";
// iteroidaan layerTree-olion rivejä
for (let i = 0; i < layerTree.rowCount(); i++) {
let index = layerTree.index(i, 0);
// haetaan tason nimi
let name = layerTree.data(index, FlatLayerTreeModel.Name);
list = list.concat("\n", name);
}
mainWindow.displayToast(list);
}
}
}
Label {
text: "Jump To Coordinates (WGS 84)"
font: Theme.defaultFont
color: Theme.mainTextColor
}
RowLayout {
Layout.fillWidth: true
Label {
text: "X"
font: Theme.defaultFont
color: Theme.mainTextColor
Layout.fillWidth: true
}
QfTextField {
id: xField
text: "0"
Layout.fillWidth: true
Layout.preferredWidth: 30
Layout.preferredHeight: font.height + 20
horizontalAlignment: TextInput.AlignHCenter
font: Theme.defaultFont
enabled: true
visible: true
inputMethodHints: Qt.ImhFormattedNumbersOnly
}
Label {
text: "Y"
font: Theme.defaultFont
color: Theme.mainTextColor
Layout.fillWidth: true
}
QfTextField {
id: yField
text: "0"
Layout.fillWidth: true
Layout.preferredWidth: 30
Layout.preferredHeight: font.height + 20
horizontalAlignment: TextInput.AlignHCenter
font: Theme.defaultFont
enabled: true
visible: true
inputMethodHints: Qt.ImhFormattedNumbersOnly
}
QfToolButton {
id: addFeatureButton
bgcolor: Theme.darkGraySemiOpaque
round: true
Text {
anchors.centerIn: parent
text: "Jump"
color: Theme.light
}
onClicked: {
let x = Number(xField.text);
let y = Number(yField.text);
if (isNaN(x) || isNaN(y))
{
mainWindow.displayToast('Invalid input!', 'error');
return;
}
mainWindow.displayToast('Jumping to (x: ' + xField.text + ', y: ' + yField.text + ')');
// luodaan piste käyttäjän antamista koordinaateista
let point = GeometryUtils.point(x, y);
// muunnetaan piste projektin koordinaattijärjestelmään
let reprojected_point = GeometryUtils.reprojectPoint(point, CoordinateReferenceSystemUtils.wgs84Crs(), mapSettings.destinationCrs);
// siirretään karttanäkymän keskikohta projisoituun pisteeseen
mapSettings.setCenter(reprojected_point);
}
}
}
}
}
QfToolButton {
id: pluginButton
bgcolor: Theme.darkGraySemiOpaque
round: true
Text {
anchors.centerIn: parent
text: "Dialog"
color: Theme.light
}
onClicked: {
dialog.open();
}
}
Component.onCompleted: {
iface.addItemToPluginsToolbar(pluginButton)
}
}
Tältä lisäosa näyttää käytössä:
Nyt meillä on siis lisäosa, jonka painikkeen takaa aukeaa dialogi-ikkuna. Tässä valikossa meillä on kolme painiketta
- Toast: julkaisee käyttäjän määrittelemän tekstin ruudulle
- List: listaa projektin karttatasot
- Jump: siirtää näkymän käyttäjän määrittämiin koordinaatteihin
Tarvitsetko lisää ominaisuuksia QFieldiisi? Ota yhteyttä ja me autamme.
