Tapahtumapohjainen ohjelmointi

Osaamistavoitteet

Kierroksen aikana opit toteuttamaan graafisia käyttöliittymiä JavaFX-kirjaston avulla. Kierros kertaa tapahtumapohjaisuuden perusteet ja opit hyödyntämään tapahtumia Java-ohjelmissa.

Tapahtumat vs. ohjausrakenteet

Tapahtumapohjaisten ohjelmien toteuttaminen on tullut esiin jo aeimmilla kursseilla. Ohjelmointi 1:llä perusteita käsiteltiin Pythonin Tkinter-kirjaston avulla. Ohjelmointi 2:lla Qt:n signaali ja slot -mekanismia käytettiin saman toiminnallisuuden toteuttamiseen. Ohjelmointi 3:lla käytetään JavaFX-kirjastoa. Tarkastellaan kuitenkin ensin tapahtumapohjaisuutta yleisesti.

Tapahtuma

Tähän saakka olemme toteuttaneet ohjelmat ns. perinteisinä ohjelmina, joiden eteneminen määräytyy koodista sen sisältämien ohjausrakenteiden perusteella (engl. sequential programming). Tapahtumapohjainen ohjelmointi (engl. event driven programming) poikkeaa tästä siinä, että ohjelma etenee erilaisten ohjelman ulko- tai sisäpuolisten tapahtumien ohjaamana, jolloin ohjelman suoritusta ei voida enää selkeänä sekvenssinä seurata. Tapahtuma on notifikaatio siitä, että ohjelmassa tapahtui jotain, joka saattaa olla kiinnostava ohjelman toiminnan kannalta. Tapahtumia synnyttävät mm. ohjelma käyttäjän toimet (hiiren- ja näppäimistön painallukset, kosketusnäytöllä tehtävät eleet) ja ohjelman sisäiset ajastimet. Myös käyttöjärjestelmä, ohjelmakomponentti tai toinen sovellus voi tuottaa tapahtuman, johon ohjelman tulee reagoida. Koska tapahtumia voi syntyä eri lähteistä ja hyvin eri puolilla ohjelmaa, ohjelmalla ei ole enää suoraviivaisesti etenevää suoritusta. Tapahtumapohjaisten ohjelmien testaaminen on myös näin ollen hankalampaa. Tapahtumien käsittelyyn vaadittava koodi on myös sellaista, jota ei suoriteta ilman sitä vastaavaa tapahtumaa.

Tapahtumapohjaisessa ohjelmassa tapahtumien käsittelyä varten ohjelmassa ns. tapahtumasilmukka (engl. event loop tai main loop). Tapahtumasilmukka kuuntelee tapahtumia ja havaitessaan sellaisen käynnistää sitä vastaavan käsittelyfunktion (engl. event handler) tai käsittelyfunktiot.

Tapahtumasilmukka

Tapahtumat käsitellään jonosta eli tapahtuessaan tapahtuma menee jonoon, jota tapahtumasilmukka purkaa tapahtuma kerrallaan.

Tapahtuma JavaFX:ssä

Vaikka perusperiaatteet ovat samanlaiset, tapahtumien käsittelylle on jokaisella kehitysympäristöllä ja kirjastolla oma lähestymistapansa. Qt:ssä tapahtumakäsittely on rakennettu signaalien ja slotien varaan. Myös JavaFX:llä on oma rakenteinen tapansa ottaa tapahtuma kiinni, kohdentaa se oikeaan kohteeseen ja mahdollistaa ohjelmalle tapahtuman käsittely tarvittaessa. Perusidea näissä kaikissa on kuitenkin hyvin samankaltainen: ohjelmassa viriää tapahtumia ohjelman toimintaa mahdollisesti kiinnostavista asioista ja niihin reagoidaan niitä kuuntelemaan kiinnitetyillä käsittelijöillä.

JavaFX:ssä tapahtuma on luokan javafx.event.Event tai jonkin sen aliluokan instanssi. JavaFX tarjoaa suoraan useita tapahtumatyyppejä kuten näppäimistön näppäinten painalluksista syntyvä KeyEvent sekä hiiren tuottamat MouseEvent ja ScrollEvent. Myös omia tapahtumia voi määritellä erikoistamalla niitä Event-luokasta. Jokaisella tapahtumalla on:

  • Kohde (engl. target), joka kertoo minkä käyttöliittymän elementin kohdalla tapahtuma tapahtui.

  • Lähde (engl. source), joka kertoo mistä tapahtuma on peräisin. Esimerkiksi hiiri tai näppäimistö

  • Tyyppi (engl. type), joka kertoo tapahtuman tyypin. Esimerksi näppäintä painettu tai hiiren nappi vapautettu.

Tapahtuman käsittelyn tulee huomioida JavaFX:n ohjelmaikkunan perusrakenne. Kun tapahtuma tapahtuu, sen käsittely käy läpi seuraavat vaiheet:

  1. Kohteen valinta (engl. target selection). JavaFX päättelee, minkä käyttöliittymäelementin kohdalla tapahtuma tapahtui. Tällä on merkitystä tapahtuman jatkokäsittelyn kannalta. Esimerkiksi hiiren toiminnan aiheuttamille tapahtumille kohdeelementiksi tulee se, jonka kohdalla hiiren kursori oli. Jatkuvista eleistä syntyvien tapahtumien, joita syntyy esim koskteusnäyttöä käytettäessä, kohde on elementti, joka sijaistee kaikkien kosketusten keskipisteessä laskien eleen alusta.

  2. Tapahtuman reitin rakentaminen (engl. route construction). Tapahtumalle määritellään tapahtumakäsittelyketju (engl. event dispatch chain) stagesta (eli koko ikkunan määrittämästä ylimmän tason JavaFX-säiliöstä) kohde-elementtiin.

  3. Tapahtuman kaappaus (engl. event capture). Sovelluksen juurielementti lähettää tapahtuman ylöspäin käsittelyketjua, ylhäältä alas. Jos millään elementeistä ketjussa on filtteri (engl. filter) rekisteröity syntyneeseen tapahtumaan, se suoritetaan. Jos yhdelläkään ei ole filtteriä, tapahtuma välitetään kohde-elementille ja lopuksi kohde käsittelee tapahtuman.

  4. Tapahtuman kuplinta (engl. event bubbling). Tapahtuma kulkee kohteesta ylöspäin kohti stagea. Jos millään elementeistä ketjussa on tapahtumalle rekisteröity käsittelijä (engl. handler), se suoritetaan. Jos yhdelläkään ei ole käsittelijää, tapahtuma saavuttaa juurielementin ja tapahtuman käsittely päättyy.

Tapahtuma siis käsitellään kuplintavaiheessa. Useampi elementti voi reagoida samaan tapahtumaan. Tapahtuman käsittelevät filtterit ja käsittelijät, jotka toteuttavat EventHandler rajapinnan ja jotka rekisteröidään ohjelmassa tiettyyn tapahtumaan kiinni. Filtterin ja käsittelijän ero on niiden suoritusvaiheessa. Filtteri suoritetaan jo kaappausvaiheessa, kun taas käsittelijä vasta kuplintavaiheessa. Filtteri tarjoaa ylemmän tason elementeille mahdollisuuden tehdä kaikille lapsielementeille yhteistä tapahtumankäsittelyä tai napata tapahtuma kiinni siten, että se estää lapsia reagoimasta siihen.

Tapahtuman käsittelyyn voit tutustua tarkemmin JavaFX-tutoriaalissa. Parhaiten kaikesta saa käsityksen tekemällä JavaFX-ohjelman. Lähdetään siis seuraavaksi toteuttamaan sellainen.

Tapahtumapohjainen ohjelmointi (kesto 19:30)

Tapahtumapohjaiselle ohjelmoinnille pätee