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.