Helsingin yliopisto / Tietojenkäsittelytieteen laitos / 581258-1 Johdatus ohjelmointiin
Copyright © 1998 Arto Wikla. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

6.3 Sovelma: tapahtumia (Java 1.0.2)

(Muutettu viimeksi 26.10.1998)

Sovelmia esitellään käyttäen Javan versiota 1.0.2, koska Netscape® Communicator 4.04 -selain (käytössä mm. laitoksen Linux-järjestelmässä) ei osaa Javan versioita 1.1.*.

Tapahtumaohjatusta ohjelmasta

Ikkunoihin ja graafiseen käyttöliittymään perustuvat ohjelmat eivät voi toimia siten, että jokin pääohjelma päättäisi käyttäjän puolesta, mitä milloinkin tehdään. Käyttäjä itse päättää, mitä kuvaruudulla näkyviä nappuloita ja muita graafisia elementtejä hiirellä osoittelee, milloin hiiren tai näppäimistön näppäimiä painelee,...

Luonteva ajattelutapa tällaisten ohjelman laadinnassa on ns. tapahtumaohjattu (event driven) ohjelmointi: ohjelmaan rakennetaan algoritmit reagoimaan erilaisiin käyttäjän aiheuttamiin ärsykkeisiin.

Javassa jokainen sovelma perii Applet-luokasta joukon metodeita tapahtumien käsittelemiseen. Edellisistä kappaleista tuttuun tapaan nuo perityt metodit ovat tyhjiä ja niitä korvataan (override) tarpeen mukaan omilla. Applet itse perii ko. metodit luokalta Component.

Tapahtuma esitetään oliona - kuinkas muuten - luokan Event ilmentymänä. Event-luokka kapseloi sisäänsä konekohtaiset tavat toteuttaa graafisen käyttöliittymän tapahtumat.

Sovelma perii seuraavat tapahtumankäsittelymetodit (asiat ovat melko toisin Javan versiossa 1.1, perusidea on silti samantapainen):

(Viimeksimainittu on kaikkien tapahtumien käsittelijä, johon voi keskitetysti ohjelmoida tapahtumien käsittelyt. Sen kautta tapahtumat kulkevat noille erityisille tapahtumankäsittelijöille. Erillisten metodien korvaaminen on kuitenkin usein vaivattomampaa, koska handleEventin ohjelmoija joutuu itse huolehtimaan kaikkien tapahtumien käsittelystä.)

Nämä tapahtumankäsittelijät saavat parametrinaan ainakin Event-olion ja kaikki palauttavat totuusarvon: true ilmaisee, että tapahtuma on loppuunkäsitelty, false puolestaan, että tapahtuma halutaan välittää jatkokäsittelyyn (mahdollisiin ulompiin "containereihin").

Mitä tapahtuu?

Sovelmalla "Tapahtuu" (Tapahtuu.html, Tapahtuu.java) voi seurata, miten selain (tms.) aiheuttaa tapahtumia. Sovelma kirjoittaa Java-konsolille ja selaimen tilariville kaikki havaitsemansa tapahtumat. Hiiritapahtumiin liittyy myös tieto tapahtuman koordinaateista, näppäinpainallustapahtuma tietää, mitä näppäintä painettiin.

Kokeile

Selain ei ymmärrä Javaa!

Tämän sovelman tapahtumankäsittelijät paint-metodia lukuunottamatta vain tulostavat ilmoituksen suorituksestaan Java-konsolille ja selaimen tilariville. (Tässä esimerkissä totuusarvoiset tapahtumankäsittelijät palauttavat arvon false. Se merkitsee, että tapahtumat saadaan käsiteltyä loppuun vasta kun on käyty suorittamassa myös ulommat samannimiset - tässä tapauksessa tyhjät -tapahtumankäsittelijät.):

import java.applet.Applet;
import java.awt.*;

public class Tapahtuu extends Applet {

  // testitulostuksiin:
  private void TulostaSinneJaTanne(String ilmoitus) {
    System.out.println(ilmoitus); // Java-konsolille
    showStatus(ilmoitus);         // selaimen tilariville
  }

  public void init() {
   TulostaSinneJaTanne("init-tapahtuma");
  }

  public void paint(Graphics g) {
    TulostaSinneJaTanne("paint-tapahtuma");
    g.drawRect(0, 0, size().width - 1, size().height - 1);
    g.drawString("Katso konsolilta mitä tapahtuu!", 5, 20);
    g.drawString("Katso tilariviltä mitä tapahtuu!", 5, 40);
  }

  public void start() {
    TulostaSinneJaTanne("start-tapahtuma");
  }

  public void stop() {
    TulostaSinneJaTanne("stop-tapahtuma");
  }

  public void destroy() {
    TulostaSinneJaTanne("destroy-tapahtuma");
  }

  public void update(Graphics g) {
    TulostaSinneJaTanne("update-tapahtuma");
  }

  public boolean mouseUp(Event e, int x, int y) {
    TulostaSinneJaTanne("mouseUp-tapahtuma: ("+x+","+y+")");
    return false;
  }

  public boolean mouseDown(Event e, int x, int y) {
    TulostaSinneJaTanne("mouseDown-tapahtuma: ("+x+","+y+")");
    return false;
  }

  public boolean mouseDrag(Event e, int x, int y) {
    TulostaSinneJaTanne("mouseDrag-tapahtuma: ("+x+","+y+")");
    return false;
  }

  public boolean mouseMove(Event e, int x, int y) {
    TulostaSinneJaTanne("mouseMove-tapahtuma: ("+x+","+y+")");
    return false;
  } 

  public boolean mouseEnter(Event e, int x, int y) {
    TulostaSinneJaTanne("mouseEnter-tapahtuma: ("+x+","+y+")");
    return false;
  }

  public boolean mouseExit(Event e, int x, int y) {
    TulostaSinneJaTanne("mouseExit-tapahtuma: ("+x+","+y+")");
    return false;
  }

  public boolean keyDown(Event e, int x) {
    TulostaSinneJaTanne("keyDown-tapahtuma: merkki "
                        +(char)x+", merkkikoodi "+x);
    return false;
  }
}

Ylläolevassa esimerkissä ei update-tapahtumaa aiheudu lainkaan! Sen voivat muut tapahtumankäsittelijät halutessaan aikaansaada metodilla repaint. Update-tapahtumankäsittelijä saa selaimelta parametriksi saman "piirustusalustan" kuin paint-metodi, joka piirtää koko kuva.alan uudelleen. Update-metodin tehtävänä on täydentää olemassaolevaa näkymää.

Jo luvussa 2.1 tutustuimme sovelmaan SyoJuustoa (SyoJuustoa.html, SyoJuustoa.java) jonka avulla hiiri voi nappulaansa painaen syödä juustoa:

Selain ei ymmärrä Javaa!

Metodi mouseDown kutsuu metodia repaint, joka puolestaan aikaansaa tapahtuman update. mouseDown-metodi palauttaa arvon true, koska tapahtuma katsotaan loppuunkäsitellyksi. Sovelmaan on tarkoituksella otettu mukaan metodi mouseEnter, joka ei kutsu metodia repaint.

Varmista Java-konsolilta, että mouseEnter ei todellakaan johda update-tapahtumaan! Kokeile myös, mitä tapahtuu, kun sovelmaa peitetään ja paljastetaan.

Koodi:

import java.applet.Applet;
import java.awt.*;

public class SyoJuustoa extends Applet {

  private int x, y;

  // testitulostuksiin:
  private void TulostaSinneJaTanne(String ilmoitus) {
    System.out.println(ilmoitus); // konsolille
    showStatus(ilmoitus);         // selaimen tilariville
  }

  public void paint(Graphics g) {
    TulostaSinneJaTanne("paint-tapahtuma");
    setBackground(Color.yellow);
    g.drawRoundRect(0, 0, size().width - 1, size().height - 1,20,20);
  }

  public void update(Graphics g) {
    TulostaSinneJaTanne("update-tapahtuma");
    g.fillOval(x,y,10,10);
  }

  public boolean mouseDown(Event e, int x, int y) {
    TulostaSinneJaTanne("mouseDown-tapahtuma: ("+x+","+y+")");
    this.x = x;
    this.y = y;
    repaint();
    return true;
  }

  public boolean mouseEnter(Event e, int x, int y) {
    TulostaSinneJaTanne("mouseEnter-tapahtuma: ("+x+","+y+")");
    return true;
  }
} 


"Pikseligrafiikkaa" ja päivittelyä

Edellinen esimerkki oli oikeastaan editori, "juustoeditori". Editointi oli kuitenkin aika hetkellistä, koska paint-tapahtuma hävitti hiiren tekemät muutokset.

Alla on kolme kuvaeditoria UnohtavaPiirtaja, MuistavaPiirtaja ja AikaMuistavaPiirtaja (UnohtavaPiirtaja.html, UnohtavaPiirtaja.java, MuistavaPiirtaja.html, MuistavaPiirtaja.java AikaMuistavaPiirtaja.html, AikaMuistavaPiirtaja.java)

Nämä sovelmat ovat melko hitaita Netscapen versiosta 3.01 alkaen, vanhemmat olivat paljon nopeampia. Sovelmien algoritmit eivät ole välttämättä kovin nerokkaita!

Selain ei ymmärrä Javaa! Selain ei ymmärrä Javaa! Selain ei ymmärrä Javaa!

Nämäkin sovelmat tulostavat testi-ilmoituksia. (Testitulostuslauseet on poistettu tämän kappaleen esimerkeistä. Ohjelmatiedostoissa ne ovat.)

Piirrä jotakin kaikkiin ja seuraa tapahtumia Java-konsolilta. Poista tavalla jos toisellakin kuvat näkyvistä ja palauta ne takaisin. Eroja käyttäytymisessä?

UnohtavaPiirtaja toimii avan samoin kuin äskeinen SyoJuustoa-sovelma: tällä kertaa mouseDrag-metodi asettaa luokan kenttiin pisteen koordinaatit ja aiheuttaa update-tapahtuman kutsumalla metodia repaint.

UnohtavaPiirtaja-sovelma (UnohtavaPiirtaja.java) ei mitenkään itse säilytä tietoa piirretystä kuvasta. Niinpä kuvaa uudelleen näytettäessä ei paint-metodi voi tietää, millainen vanha kuva oli. Kuvan muistamiseen tarvitaan jonkinlainen tietorakenne.

Yksi mahdollisuus on tallettaa kaikista kuvapisteistä tieto, onko pisteeseen piirretty vai ei. Samaan tapaan voitaisiin säilyttää tieto vaikkapa pisteen väristä.

MuistavaPiirtaja-sovelmassa (MuistavaPiirtaja.java) on taulukko:

  private boolean[][] piste;
jonka init-metodi asettaa tyhjäksi:
  piste = new boolean[size().width][size().height];
               // oletusalkiot == false
paint-metodi maalaa kuvan pisteittäin:
  public void paint(Graphics g) {
    g.drawRect(0, 0, size().width-1,size().height-1);
    for (int i=0; i<size().width; ++i)
      for (int j=0; j<size().height; ++j)
        if (piste[i][j])
          g.drawRect(i,j,1,1);
  }

mouseDrag-metodi päivittää tietorakennetta. Se joutuu myös ottamaan huomioon, että sovelmalle voi aiheutua mouseDrag-tapahtumia kuva alan ulkopuolelta(!), siksi indeksivirheeseen varaudutaan:
  public boolean mouseDrag(Event e, int x, int y) {
    try {        // kokeile dragata ulos kuvasta!
      this.x = x;
      this.y = y;
      piste[x][y] = true;
      repaint();
    }
    catch (ArrayIndexOutOfBoundsException vikaa) {
    }
    return true;
  }

MuistavaPiirtaja-sovelmassa "pikselitaulukko" tyhjätään (so. luodaan uusi) vain init-metodissa. Siksi sovelmalla on hyvä muisti: kuva piirretään uudelleen peittymisen, kuvakkeena käynnin, sivulla muualle siirymisen, "Back-Forward"-toimen, ..., jälkeen.

Jos kuvan halutaan säilyvän vain peittymisten ja sivulla siirtyilyjen jälkeen, stop-metodi voidaan ohjelmoida tyhjäämään tietorakenne.

Sovelma AikaMuistavaPiirtaja (AikaMuistavaPiirtaja.java) on muuten kuin MuistavaPiirtaja, mutta sillä on metodi:

  public void stop() {
    piste = new boolean[size().width][size().height];
               // oletusalkiot == false
  }



"Vektorigrafiikkaa"

Kuva voidaan muistaa myös janoina, pistepareina. Sovelma VektoriPiirtaja (VektoriPiirtaja.html, VektoriPiirtaja.java) on toteuttu näin:

Selain ei ymmärrä Javaa!

Ohjelma on seuraavanlainen(testitulostusvälineet on poistettu, tiedostosta ne löytyvät) [ohjelmaa on korjattu lokakuussa 1998, drawPolygon on korvattu metodilla drawPolyline]:

import java.applet.Applet;
import java.awt.*;

public class VektoriPiirtaja extends Applet {

  private final int MAXLKM = 500;
  private int[] x = new int[MAXLKM];
  private int[] y = new int[MAXLKM];
  private int lkm;


// Tietorakenne on pisteparien joukko kahdessa
// taulukossa ja lukumäärämuuttuja.




  public void init() {
    lkm = 0;
  }

  public void start() {
    lkm = 0;
  }

// Säilytetään kuva vain peittymisen yli.




  public void paint(Graphics g) {
    g.drawRect(0, 0, size().width-1, size().height-1);
    g.drawPolyline(x, y, lkm);
  }

  public void update(Graphics g) {
    g.drawPolyline(x, y, lkm);
  } 

// Piirretään ja päivitetään drawPolygon-metodilla
// (jälkimmäisessä tehdään turhaa työtä, mutta
//  nyt kuva säilyy täsmälleen samanlaisena, vrt.
//  edelliset esimerkit).



  public boolean mouseDrag(Event e, int x, int y) {
    this.x[lkm] = x;
    this.y[lkm] = y;
    if (lkm < MAXLKM-1) {
      ++lkm;
      repaint();
    }
    return true;
  }

// Tietorakenne (ja kuva) kasvatetaan niin isoksi kuin
// resurssit sallivat. Sen jälkeen kuvan piirtely loppuu.



  public boolean mouseDown(Event e, int x, int y) {
    return mouseDrag(e, x, y);
  }

// Kuvaa voi piirtää "klikkaillenkin".
// Käytetään hyväksi jo ohjelmoitua metodia.

}




Otuksia kiinni ja irti

Seuraavat esimerkit ovat hieman edellisiä mutkikkaampia. Sovelmalla OtusLiikkuu (OtusLiikkuu.html, OtusLiikkuu.java) voi siirrellä hiirellä vetäen tai näpäyttäen pikku naamaa, jonka mielialaa saa muuteltua näppäimistön avulla:

Selain ei ymmärrä Javaa!

Sovelma OtusKarkaa (OtusKarkaa.html, OtusKarkaa.java) on tietokonepeli, jolla otusta yritetään kiinni. Tarkkaile otuksen mielialojen vaihteluita.

Selain ei ymmärrä Javaa!

Sovelman OtusKarkaa2 (OtusKarkaa2.html, OtusKarkaa2.java) otus ei karkaa niin kauas, kiusoittelee peijakas:

Selain ei ymmärrä Javaa!

Kehitelty versio karkaavasta otuksesta löytyy sivulta http://www.cs.Helsinki.FI/~wikla/JavaArt/CatchMe.html


Takaisin luvun 6 sisällysluetteloon.