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ä
- Digia Qt SDK (tätä kirjoittaessa latauslinkeissä lukee virheellisesti “Qt Libraries for ..” eikä Qt SDK)
- Qt Quick-dokumentaatio
- Tämän artikkelin esimerkkiohjelmat
Teksti: Ville Ranki
Kuvat: Ville Ranki