QML – deklaratiivisen käyttöliittymän alkeet

Artikkeli on alun perin julkaistu Skrollin numerossa 2013.1.

QML (Qt Meta Language) on Digian Qt-ympäristöön kuuluva käyttöliittymien toteutukseen tarkoitettu kuvauskieli, jolla voi tehdä näyttäviä käyttöliittymiä jopa ilman ohjelmointitaitoa.

QML julkaistiin vuonna 2009 ja se on Nokian Harmattan- alustan ja MeeGon “virallinen” tapa toteuttaa käyttöliittymiä. Näkyvin QML:n käyttäjä lienee tällä hetkellä Nokian N9-puhelin. Tulevaisuudessa ainakin Jollan Sailfish ja Ubuntu Mobile käyttävät QML:ää käyttöliittymissään.

Perinteisesti graafiset käyttöliittymät on rakennettu sovellusohjelmointikielellä käyttöliittymäkomponenteista eli widgeteistä. Tämä on hidasta ja virhealtista. Toki tarkoitukseen on suunnittelusovelluksiakin, mutta niillä on omat rajoitteensa. Etenkään jos halutaan tehdä helposti ja nopeasti samantapaisia animoituja käyttöliittymiä kuin vaikkapa iPhonessa, perinteiset lähestymistavat eivät toimi.

QML eriyttää käyttöliittymän kuvauksen kokonaan sovellusohjelmointikielestä eli tyypillisesti C++:sta. Qt-kirjasto tekee kuitenkin QML:n ja C++:n yhteistyön hyvin helpoksi. Kun QML:n rajat tulevat vastaan, voidaan toiminnallisuutta helposti laajentaa niin C++:lla kuin webbiohjelmoinnista tutulla JavaScriptilläkin. Tyypillinen QML:ää käyttävä sovellus on C++-ohjelma, joka vain avaa QML:llä toteutetun käyttöliittymän.

Esimerkki kuvan luomisesta deklaratiivisesti ja perinteisellä widget-ohjelmoinnilla:

Deklaratiivinen kuvaus Perinteinen ohjelmointi
 
Image {
    width: 300
    height: 200
    source: "skrolli.png"
}
 
Image *image = new Image();
image->setWidth(300);
image->setHeight(200);
image->setSource("skrolli.png");

QML:ää kannattaa käyttää kosketusnäyttö- tai hiirivetoisissa sovelluksissa, jotka pyörivät omassa ikkunassaan tai koko ruudulla. Mobiilimaailman ulkopuolella tällaisia voisivat olla XBMC:n kaltaiset mediatoistimet tai Angry Birdsin kaltaiset 2D-pelit. Peleissä fysiikkamottorin voisi toteuttaa C++:lla ja peligrafiikat QML:llä. Perinteiseen ohjelmointiin kannattaa turvautua, jos sovellus käyttää useampaa ikkunaa, esittää paljon dataa tai sisältää paljon syöttölomakkeita. QML:ää voi kuitenkin käyttää myös osana widget-pohjaista sovellusta.

Kuten koko Qt, myös QML on tarkoitettu alustariippumattomaksi. Saman QML-sovelluksen saa tällä hetkellä toimimaan suoraan Windowsissa, työpöytä-Linuxeissa, OS X:ssä, Maemossa, MeeGossa, Symbianissa ja Windows CE:ssä. Android-QML toimii myös, vaikka onkin vielä kehitysasteella. iOS- ja WP8-porttaukset ovat tekeillä.

Esimerkki 1: Ensitervehdys maailmalle

QML-ohjelmointi on helpointa aloittaa asentamalla Digian Qt SDK. Qt Creator, virallinen Qt-kehitystyökalu, tukee täysin QML-ohjelmointia. Näissä esimerkeissä on käytetty Qt SDK:n versiota 5.0 ja sen mukana tulevaa Qt Creator 2.6.1:tä. Käynnistä Qt Creator, valitse alkuruudussa “Create Project”, “Applications” / ”Qt Quick 2 UI” ja anna projektin nimeksi “skrolli”. Qt Creator luo QML-tiedoston skrolli.qml, jonka sisältö on seuraava:

 
import QtQuick 2.0
 
Rectangle {
	width: 360
	height: 360
        Text {
    		anchors.centerIn: parent
    		text: "Hello World"
	}
	MouseArea {
    		anchors.fill: parent
    		onClicked: {
        		Qt.quit();
    		}
	}
}

Voit käynnistää ohjelman painamalla Ctrl-R. Tämä näyttää ikkunan, jossa lukee “Hello World”. Ohjelma sulkeutuu klikkaamalla ikkunan sisällä.

Ensimmäinen import-rivi lataa QML:n peruskomponenttikirjaston. Muut tarjolla olevat kirjastot riippuvat Qt:n versiosta ja ympäristöstä. Niitä voi ohjelmoida myös itse.

QML-dokumentti koostuu aaltosuluilla merkitystä lohkoista, joita kutsutaan elementeiksi. Ennen sulkeita annetaan elementin tyyppi. Sulkeiden sisällä määritellään elementin ominaisuudet (properties) ja lapsielementit. Esimerkissä kuvataan Rectangle-elementti eli nelikulmio, jonka koko on 360×360 pikseliä, ja jonka lapsina ovat Text- ja MouseArea-elementit.

Anchors-ominaisuus määrittelee piirrettävän elementin niin kutsutut ankkuripisteet. Text-elementin anchors.centerIn: parent keskittää tekstin isäelementtinsä eli tässä tapauksessa nelikulmion keskelle. Tämä määrittely ei vaikuta Text-elementin kokoon, joten se on automaattisesti piirrettävän tekstin kokoinen.

Ankkuripisteillä voidaan vapaasti määritellä elementtien sijainnit. Esimerkiksi anchors.left: toinenelementti.right määrää elementin vasemman laidan olevan aina toisen elementin oikeassa laidassa. Ankkuripisteillä elementit pitävät paikkansa, vaikka niiden suhteelliset koot ja sijainnit muuttuisivat. Koot ja paikat kannattaa määritellä suhteessa toisiinsa aina kun mahdollista, jotta käyttöliittymä skaalautuisi erikokoisille näytöille.

MouseArea-elementti ei näy ruudulla, vaan sitä käytetään hiiren tai sormen klikkausten tunnistamiseen. Sen kooksi on määritelty anchors.fill: parent, joka asettaa sen koon ja paikan vastaamaan isäelementtiään eli nelikulmiota. MouseArean onClicked on signaalinkäsittelijä (signal handler) ja kutsuu JavaScriptillä ohjelman sulkevaa funktiota Qt.quit().

Kaikista QML-elementeistä ja niiden ominaisuuksista saa Qt Creatorissa ohjeet F1-näppäimellä. Ctrl-Space täydentää osittain kirjoitetun elementin tai ominaisuuden nimen, ja automaattinen sisennys onnistuu painamalla Ctrl-a ja Ctrl-i. Kommentteja kirjoitetaan kuten C++:ssa, eli esimerkiksi // muuttaa loppurivin kommentiksi.

Oman elementtityypin luominen

QML:ssä voidaan luoda omia elementtityyppejä yksinkertaisesti tekemällä uusi QML-tiedosto. Seuraavassa esimerkissä luomme MyButton-nimisen elementin.

Paina Qt Creatorin Projects-listassa oikealla napilla projektisi nimeä (skrolli) ja valitse “Add New”. Aukeavasta dialogista valitse “Qt”, “QML File (Qt Quick 2)”. Anna tiedostolle nimi “MyButton”. Naputtele tiedostoon:

 
import QtQuick 2.0
 
Rectangle {
	id: mybutton
	property string text: "Nappi"
	signal clicked
	width: textItem.width + 15
	height: textItem.height + 5
	color: "blue"
	radius: 5
	Text {
    		id: textItem
    		anchors.centerIn: mybutton
    		text: mybutton.text
    		color: "white"
	}
	MouseArea {
    		anchors.fill: mybutton
    		onClicked: mybutton.clicked()
	}
}

Otamme nyt tämän elementin käyttöön skrolli.qml.ää. Muokkaa sen sisältö vastaamaan seuraavaa listausta ja käynnistä painamalla Ctrl-r.

 
import QtQuick 2.0
 
Rectangle {
	width: 360
	height: 360
	MyButton {
		anchors.centerIn: parent
		text: "Hello World"
		onClicked: {
			Qt.quit();
		}
	}
}

Omien elementtien määrittelyllä vältetään tiedostojen kasvaminen ylisuuriksi ja mahdollistetaan samojen elementtien uudelleenkäyttö.

MyButtonin juurielementti on nelikulmio (Rectangle), jolle on määritelty koon, värin ja reunojen pyöristyksen (radius) lisäksi id mybutton, ominaisuus text ja signaali clicked. Id on yksittäisen elementin tunniste, johon voidaan viitata sen lapsi- ja sisarelementeissä. Nelikulmion koko on esimerkissä määritelty suhteessa sen sisällä olevaan tekstielementtiin, jonka id on textItem.

QML-elementeille voidaan määritellä omia ominaisuuksia. MyButton.qml:n property-alkuinen rivi määrittelee tyypille ominaisuuden text, joka on merkkijono (string) ja oletusarvoltaan “Nappi”. Ominaisuutta käytetään Text-elementissä ja sille asetetaan skrolli.qml:ssä uusi arvo “Hello World”.

Napin painaminen aikaansaa clicked-signaalin sen sisällä olevaan MouseArea-elementtiin. Jotta tästä olisi hyötyä nappia käyttävissä ohjelmissa, on myös napin juurielementillä oltava oma clicked-signaali. MouseArea-elementin signaalinkäsittelijä välittää clicked-signaalinsa eteenpäin MyButtonille, jonka signaalinkäsittelijä onClicked on määritelty skrolli.qml:n puolella ohjelman lopetukseksi. Signaaleilla voi olla myös parametreja funktiokutsujen tapaan.

Esimerkki 2: Pieni verkkoselain

Seuraavaksi teemme yksinkertaisen www-selaimen käyttäen QML:n WebView-elementtiä. Yläreunaan tehdään ohjauspalkki, josta voidaan hiirellä siirtyä johonkin etukäteen annetuista osoitteista. Ohjauspalkin voi myös piilottaa ja palauttaa näkyviin.

Muokkaa skrolli.qml seuraavan listauksen mukaiseksi:

 
import QtQuick 2.0
import QtWebKit 3.0
 
Item {
	width: 800
	height: 600
	WebTools {
    		z: 10
    		anchors.fill: parent
	}
	WebView {
    		id: webview
    		url: "http://skrolli.fi"
    		anchors.fill: parent
	}
}

Juurena on yleiselementti Item, jonka täyttävät seuraavaksi tehtävä WebTools-elementti ja QtWebKit-kirjastosta tuleva WebView-elementti. WebToolsin positiivinen z-koordinaatti varmistaa, että se piirretään aina WebViewin päälle.

Tiedostoon WebTools.qml kirjoitetaan:

import QtQuick 2.0
 
Item {
	id: webtools
	state: "SHOWN"
	states: [
	    	State {
	        	name: "SHOWN"
	        	PropertyChanges {
	            	target: hidabletools
	            	opacity: 1
	            	scale: 1
	        	}
	    	},
	    	State {
	        	name: "HIDDEN"
	        	PropertyChanges {
	            	target: hidabletools
	            	opacity: 0
	            	scale: 0
	        	}
	    	}
	]
	transitions: Transition {
	    	NumberAnimation {
	        	properties: "scale, opacity"; easing.type: Easing.InOutBack
	    	}
	}
 
	MyButton {
	    	id: hidebutton
	    	text: webtools.state == "SHOWN" ? "Hide" : "Show"
	    	z: 10
	    	onClicked: webtools.state = webtools.state=="SHOWN" ? "HIDDEN" : "SHOWN"
	}
 
	Row {
    		id: hidabletools
	    	spacing: 20
	    	anchors.horizontalCenter: parent.horizontalCenter
 
	    	MyButton {
	        	text: "Skrolli.fi"
	        	onClicked: webview.url = "http://skrolli.fi"
	    	}
	    	MyButton {
	        	text: "Skrolli FB"
	        	onClicked: webview.url =
	        	"https://www.facebook.com/Skrollilehti"
	    	}
	    	MyButton {
	        	text: "Skrolli Twitter"
	        	onClicked: webview.url =
	        	"https://twitter.com/skrollilehti"
	    	}
	}
}

Tiloilla (states) voi rakentaa käyttöliittymille mukavasti toimintalogiikkaa, esimerkiksi elementtien piilotus tarpeen mukaan. WebToolsin juurena on Item, jonka tilat ovat “HIDDEN” ja “SHOWN”. PropertyChanges-elementeillä määritellään minkä elementin (target) mitkä ominaisuudet muuttuvat, kun tilaan saavutaan. Shown-tilassa muutetaan hidabletools-elementin läpinäkyvyys ja koko ykkösiksi, jolloin se näkyy täysikokoisena. Hidden-tilassa ne taas vaihdetaan nolliksi, jolloin elementti muuttuu näkymättömäksi ja hyvin pieneksi.

Tilamuutokset voidaan animoida näyttävästi määrittelemällä transitioita (transitions). Esimerkin NumberAnimation määrittelee scale– ja opacity-ominaisuudet muuttumaan vähitellen käyrän nimeltä InOutBack mukaisesti. Näin nappulat zoomaavat ilmestyessään ja kadotessaan. QML:n animaatio-ominaisuudet ovat laajat, joten eri transitiovaihtoehtoja kannattaa kokeilla dokumentaation avustuksella.

Nappi hidebutton näyttää ja piilottaa muut napit. C-kielestä tuttu a?b:c -operaattori luetaan “jos a on tosi, niin b, muuten c”. Sillä saa kätevästi ehtolauseen yhdelle riville.

Muut napit ovat Row-elementin nimeltä hidabletools sisällä. Ne asettavat webview:in url-ominaisuudeksi halutun sivun osoitteen.

Esimerkki 3: Yksinkertainen peli

Lopuksi teemme klassisen Pong-pelin QML:llä. Tässä yksin pelattavassa versiossa mailaa ohjataan hiirellä ja jokaisesta torjutusta pallosta saa yhden pisteen. Luo Pong.qml ja kirjoita siihen seuraava lähdekoodi:

 
import QtQuick 2.0
 
Rectangle {
	color: "black"
	MouseArea {
    		id: mouseArea
    		hoverEnabled: true
    		anchors.fill: parent
	}
	Rectangle {
    		id: paddle
    		width: parent.width/100
    		height: parent.height/8
    		x: width
    		y: mouseArea.mouseY
	}
	Rectangle {
    		id: topWall
    		width: parent.width
    		height: parent.height/40
	}
	Rectangle {
    		id: bottomWall
    		width: parent.width
    		height: parent.height/40
    		anchors.bottom: parent.bottom
	}
	Rectangle {
    		id: backWall
    		width: parent.width/40
    		height: parent.height
    		anchors.right: parent.right
	}
	Rectangle {
    		id: ball
    		width: parent.width/50
    		height: width
    		x: parent.width/2
    		y: parent.height/2
    		z: 10
    		property real dx: 5
    		property real dy: Math.random()*6-3
	}
	Text {
 	   	id: scoreDisplay
    		anchors.horizontalCenter: parent.horizontalCenter
    		anchors.top: topWall.bottom
    		font.pointSize: parent.height/20
    		color: "white"
    		property int score: 0
    		text: score
	}
	Image {
    		anchors.centerIn: parent
    		source: "http://www.skrolli.fi/logo1.png"
    		opacity: 0.5
	}
	Timer {
    		interval: 16
    		repeat: true
    		running: true
    		onTriggered: {
        		ball.x += ball.dx
        		ball.y += ball.dy
 
        		// Törmäykset ylä- ja alaseinämiin
        		if(ball.y >= bottomWall.y-bottomWall.height ||
                		ball.y <= topWall.y + topWall.height) ball.dy = -ball.dy
 
        		// Törmäys takaseinään
        		if(ball.x >= backWall.x - backWall.width) ball.dx = -ball.dx
 
        		// Törmäys mailaan, pisteiden ja nopeuden lisäys
        		if(ball.x <= paddle.x + paddle.width &&
                		ball.y > paddle.y &&
                		ball.y <, paddle.y+paddle.height) {
		            	ball.dx = Math.abs(ball.dx)*1.1
		            	ball.dy *= 1.1
		            	scoreDisplay.score++
	        	} else if(ball.x < 0) { // Pelin häviäminen
		            	ball.dx = 5
		            	ball.dy = Math.random()*6-3
		            	ball.x = parent.width/2
		            	ball.y = parent.height/2
		            	scoreDisplay.score = 0
	        	}
    		}
	}
}

Pongin juurielementtinä on musta nelikulmio. Paddle on pelaajan ohjaama maila, joka saadaan liikkumaan asettamalla sen y-ominaisuudeksi MouseArealta saatava hiiren y-koordinaatti. Jotta koordinaatti päivittyisi muulloinkin kuin nappia painaessa on hoverEnabled-ominaisuus kytkettävä päälle. Ball on pallo, jonka ominaisuudet dx ja dy merkitsevät pallon nopeutta X- ja Y-akselien suhteen.

Mielenkiintoisin elementti tässä esimerkissä on Timer, jolla suoritetaan JavaScript-koodia ajastettuna. Interval-ominaisuus on liipaisun väliaika millisekunteina, ja Repeat saa liipaisun toistumaan säännöllisesti. Liipaisussa toteutettava koodi on onTriggered-signaalinkäsittelijässä ja sisältää Pongin pelilogiikan JavaScript-koodina. Kaikki QML:n elementtien id:t, muuttujat ja signaalit näkyvät JavaScript-koodiin. Pongia pääset pelaamaan lisäämällä sen esimerkiksi skrolli.qml:ään halutun kokoisena.

Qt Quick ja Qt Declarative

Nämä termit tulevat usein vastaan QML:n yhteydessä. Qt Quick tarkoittaa koko sovelluskehystä, jonka osana QML-kieli ja siihen liittyvät C++-luokat ovat. Qt Declarative taas on Qt Quickin osa, joka lataa QML-tiedoston ja piirtää sen ruudulle.

Qt Quick 1 ja 2

Qt Quick 2:n sisältävä Qt 5 julkaistiin joulukuussa 2012. Tämä artikkeli kirjoitettiin alun perin Qt 4.8:aa ja Qt Quick 1:tä varten, joten esimerkit toimivat myös Qt Quick 1:ssä hyvin pienin muutoksin. Qt Quick 2:n uusia ominaisuuksia ovat mm. OpenGL-varjostimet, joilla saadaan aikaiseksi silmäkarkkia, Canvas-elementti, johon voidaan piirtää 2D-grafiikkaa ja ikkunoiden hallinta. OpenGL-tukensa ansiosta Qt Quick 2 myös piirtää grafiikkaa nopeammin.

QtQuick3D

QtQuick3D:llä kaksiulotteisten QML-käyttöliittymien sekaan saadaan myös 3D-sisältöä, jota voidaan animoida yhtä helposti kuin 2D-sisältöäkin. Myös OpenGL-varjostimia tuetaan. QtQuick3D on näillä näkymin tulossa osaksi Qt 5:tä.

Linkkejä

Teksti: Ville Ranki
Kuvat: Ville Ranki