Helsingin yliopisto / Tietojenkäsittelytieteen laitos / 581258-1 Johdatus ohjelmointiin
Copyright © 1997 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.5 Esimerkkisovelluksia (Java 1.1.3)

(Muutettu viimeksi 10.12.1997)

Tämän kappaleen sovelluksia on hyvä kokeilla itse: talleta, käännä ja suorita. Kokeile muutoksia.

Huom: Suorita ohjelma paikallisesti juuri siinä koneessa, jota käytät. Muuten sovellus ei ilman erikoistoimenpiteitä näy!

Graafinen sovellus ikkunaan

Sovellus on main-metodilla käynnistettävä Java-ohjelma. Graafiseen käyttöliittymään (GUI) perustuvan sovelluksen päämetodi rakentaa käyttöliittymän näkymän.

Pieni esimerkki:

import java.awt.*;

public class HoiGSovellus0 extends Frame {

  public HoiGSovellus0() { // konstruktori!
    TextField hoi = new TextField("Hoi maailma!");
    add(hoi); // lisätään kenttä luokan näkymään
  }

  public static void main(String[] args) {
    HoiGSovellus0 ikkuna = new HoiGSovellus0();
    ikkuna.setTitle("Hoi 0!");
    ikkuna.pack();
    ikkuna.setVisible(true);
  }
}

Graafista käyttöliittymää käyttävän sovelluksen luokka on luokan Frame aliluokka. Frame määrittelee ikkunan, jolla on reunat ja otsikkopalkki. Se tarjoaa yliluokkineen (Window, Container, Component) välineet mm. ikkunoiden käsittelyyn.

Pääohjelma luo ilmentymän omasta luokastaan HoiGSovellus0. Pääohjelma myös asettaa sovelluksen ikkunaan otsikkorivin (setTitle luokassa Frame), antaa luvan asettaa ikkunan komponentit oletuskokoonsa (pack luokassa Window) ja asettaa ikkunan näkyväksi (setVisible luokassa Component).

... ikkuna pitää voida sulkea ...

Kun ohjelma HoiGSovellus0 suoritetaan, havaitaan, ettei ikkunaa voi tavalliseen tapaan sulkea! Apu löytyy ikkunatapahtumaan reagoinnista. Lisätään sovellukseen "ikkunankuuntelija": Luokassa Window on metodi addWindowListener(WindowListener).

Toteutetaan kuuntelija luokkana HoiteleIkkunanSulkeminen. Luokka perii yliluokaltaan (java.awt.event.WindowAdapter) tyhjiä metodeita ikkunatapahtumiin reagoimiseen (windowActivated, windowClosed, windowClosing, windowDeactivated, windowDeiconified, windowIconified, windowOpened).

Korvataan metodi windowClosing sellaisella, joka lopettaa sovelluksen (luokat on tässä sijoitettu samaan käännösyksikköön):

import java.awt.*;
import java.awt.event.*;

public class HoiGSovellus1 extends Frame {

  public HoiGSovellus1() {
    TextField hoi = new TextField("Hoi maailma!");
    add(hoi);
    addWindowListener(new HoiteleIkkunanSulkeminen());
  }

  public static void main(String[] args) {
    HoiGSovellus1 ikkuna = new HoiGSovellus1();
    ikkuna.setTitle("Hoi 1!");
    ikkuna.pack();
    ikkuna.setVisible(true);
  }
}

class HoiteleIkkunanSulkeminen extends WindowAdapter {
  public void windowClosing(WindowEvent tapahtuma) {
    System.exit(0);  // sovelluksen lopetus!
  }
} 


Nyt havaitaan, että ikkuna voidaan sulkea!

... ja ikkunassa tapahtuu ...

Graafisen käyttöliittymän tapahtumiin reagointi voidaan ohjelmoida useilla erilaisilla tavoilla. Seuraavassa esimerkissä asia hoidetaan hyvin yksinkertaisesti eikä kovin "olio-ohjelmointihenkisesti"!

import java.awt.*;
import java.awt.event.*;

public class HoiGSovellus2 extends Frame
                           implements ActionListener {

  private TextField hoi = new TextField("Paina enter!");
  private int elkm = 0;

  public HoiGSovellus2() {
    add(hoi);
    hoi.addActionListener(this);
    addWindowListener(new HoiteleIkkunanSulkeminen());
  }

  public void actionPerformed(ActionEvent tapahtuma) {
    Object aiheuttaja = tapahtuma.getSource();
    if (aiheuttaja == hoi) {
       ++elkm;
       hoi.setText(elkm+". enter");
    }
  }

  public static void main(String[] args) {
    HoiGSovellus2 ikkuna = new HoiGSovellus2();
    ikkuna.setTitle("Hoi 2!");
    ikkuna.pack();
    ikkuna.setVisible(true);
  }
}

class HoiteleIkkunanSulkeminen extends WindowAdapter {
  public void windowClosing(WindowEvent tapahtuma) {
    System.exit(0);  // ikkunan sulkeminen
  }
}

(Tuo HoiteleIkkunanSulkeminen on tässä mukana täydellisyyden vuoksi. Jos se on jo valmiiksi sovelluksen pakkauksessa, sitä ei tarvitse käännösyksikössä kerrata.)

Komponentteja

Laaditaan muutamia graafisen käyttöliittymän komponentteja (luokat Button, TextField, TextArea) käyttäen sovellus, joka osaa reagoida nappuloiden painamiseen ja tekstikenttään kirjoittamiseen.

Komponentteja on monia muitakin, esimerkiksi luokat: Canvas, Checkbox, CheckboxGroup, Choice, Image, Label, Panel, Scrollbar, ... .

Näihin luokkiin sekä esimerkkiin tutustuminen jätetään oman harrastuksen varaan: Suorita ohjelmaa, tutki luokkien kuvailuja, kokeile ohjelman muuttamista, ...

import java.awt.*;
import java.awt.event.*;

public class Nappuloita extends Frame
                        implements ActionListener {

  // muuttujia ikkunan komponenteille:

  private Button nappula, tyhjaa, loppu;
  private TextField tekstiKentta1, tekstiKentta2;
  private TextArea tekstiAlue;

  public Nappuloita() {  // konstruktori

    setLayout(new FlowLayout()); // "kun rivi loppuu,
                                 //  uusi alkaa"
    // luodaan komponenttioliot:

    nappula = new Button("nappula");
    tyhjaa  = new Button("tyhjää");
    loppu   = new Button("loppu");

    tekstiKentta1 = new TextField(10);

    tekstiKentta2 = new TextField(15);
    tekstiKentta2.setEditable(false);

    tekstiAlue = new TextArea(5,14);

    // lisätään komponenttioliot ikkunaan:

    add(nappula);
    add(tyhjaa);
    add(loppu);
    add(tekstiKentta1);
    add(tekstiKentta2);
    add(tekstiAlue);

    // asetetaan kuuntelijat:

    nappula.addActionListener(this);
    tyhjaa.addActionListener(this);
    loppu.addActionListener(this);
    tekstiKentta1.addActionListener(this);


    addWindowListener(new HoiteleIkkunanSulkeminen());
  }

  // tapahtumien käsittely:

  public void actionPerformed(ActionEvent tapahtuma) {

     Object aiheuttaja = tapahtuma.getSource();

     if (aiheuttaja == nappula) {
       tekstiKentta2.setText("Painoit nappulaa!");
       tekstiAlue.append("Painoit nappulaa!\n");
     }
     else if (aiheuttaja == tyhjaa) {
       tekstiKentta1.setText("");
       tekstiKentta2.setText("");
       tekstiAlue.setText("");
     }
     else if (aiheuttaja == loppu)
       System.exit(0); // lopetetaan

     else if (aiheuttaja == tekstiKentta1)
       tekstiAlue.append(tekstiKentta1.getText()+"\n");

  }

  public static void main(String[] args) {
      Nappuloita ikkuna = new Nappuloita();
      ikkuna.setSize(300, 200);
      ikkuna.setTitle("Nappuloita.java");
      ikkuna.setVisible(true);
  }
} 

class HoiteleIkkunanSulkeminen extends WindowAdapter {
  public void windowClosing(WindowEvent tapahtuma) {
    System.exit(0);  // ikkunan sulkeminen
  }
}

Huom: Jos haluat nähdä Javan 1.1-version mukaisesti tehdyn ylläolevaa sovellusta vastaavan sovelman, voit tutustua ohjelmaan SovelmaNappuloita.java ja sivuun SovelmaNappuloita.html. Jos menet sivulle SovelmaNappuloita.html esimerkiksi selaimella Netscape® Communicator 4.04, huomaat, ettei sovelma käynnisty. Voit kuitenkin katsella sovelmaa ohjelmalla appletviewer. Unix/Linux-järjestelmissä komento:
   appletviewer SovelmaNappuloita.html


Esimerkki: pikku laskin

Laaditaan järkevä esimerkki kahden luvun yhteen-, vähennys-, kerto- ja jakolaskusovellukseksi.

Myös tämän esimerkin yksityiskohdat jäävät itse tutkittaviksi. Joitakin huomautuksia silti:

Tutki ohjelmaa ja kokeile sen muuttamista!

import java.awt.*;                        // Arto Wikla 8.12.1997
import java.awt.event.*;

public class Aritmetiikkaa extends Frame
                           implements ActionListener {

  private TextField luku1, luku2;
  private Button summa, erotus, tulo, osamaara, tyhjenna, loppu;
  private TextField tulos;
  private double dluku1, dluku2;

  public Aritmetiikkaa() {

    luku1 = new TextField(15);
    luku2 = new TextField(15);

    luku1.addActionListener(this);
    luku2.addActionListener(this);

    Panel lahtoluvut = new Panel();
    lahtoluvut.add(new Label("1. laskettava:"));
    lahtoluvut.add(luku1);
    lahtoluvut.add(new Label("2. laskettava:"));
    lahtoluvut.add(luku2);
    add("North", lahtoluvut);

    summa    = new Button("+");
    erotus   = new Button("-");
    tulo     = new Button("*");
    osamaara = new Button("/");
    tyhjenna = new Button("Tyhjennä kaikki kentät!");
    loppu    = new Button("Lopeta");

    summa.addActionListener(this);
    erotus.addActionListener(this);
    tulo.addActionListener(this);
    osamaara.addActionListener(this);
    tyhjenna.addActionListener(this);
    loppu.addActionListener(this);

    Panel operaatiot = new Panel();
    operaatiot.add(new Label("Operaatio:"));
    operaatiot.add(summa);
    operaatiot.add(erotus);
    operaatiot.add(tulo);
    operaatiot.add(osamaara);
    operaatiot.add(tyhjenna);
    operaatiot.add(loppu);
    add("Center", operaatiot);

    tulos = new TextField(25);
    tulos.setEditable(false);

    Panel lopputulos = new Panel();
    lopputulos.add(new Label("Tulos:"));
    lopputulos.add(tulos);
    add("South", lopputulos);

    addWindowListener(new HoiteleIkkunanSulkeminen());
  }

  public void actionPerformed(ActionEvent tapahtuma) {

    Object aiheuttaja = tapahtuma.getSource();

    if (aiheuttaja == loppu)
       System.exit(0);

     else if (aiheuttaja == tyhjenna) {
       tulos.setText("");
       luku1.setText("");
       luku2.setText("");
     }

     else if (luvutKunnossa()) {

        if (aiheuttaja == summa)
          tulos.setText(""+(dluku1+dluku2));
        else if (aiheuttaja == erotus)
          tulos.setText(""+(dluku1-dluku2));
        else if (aiheuttaja == tulo)
          tulos.setText(""+(dluku1*dluku2));
        else if (aiheuttaja == osamaara) 
          tulos.setText(""+(dluku1/dluku2));
      }
    }

  private boolean luvutKunnossa() {
    boolean ok = true;
    try {
      dluku1 = new Double(luku1.getText()).doubleValue();
    }
    catch (Exception e) {
      luku1.setText("VIRHE!");
      ok = false;
    }
    try {
      dluku2 = new Double(luku2.getText()).doubleValue();
    }
    catch (Exception e) {
      luku2.setText("VIRHE!");
      ok = false;
    }  
    if (!ok)
      tulos.setText("");
    return ok;
  }

  public static void main(String[] args) {
    Aritmetiikkaa ikkuna = new Aritmetiikkaa();
    ikkuna.setTitle("Aritmetiikkaa");
    ikkuna.pack();
    ikkuna.setVisible(true);
  }
} 

class HoiteleIkkunanSulkeminen extends WindowAdapter {
  public void windowClosing(WindowEvent tapahtuma) {
    System.exit(0);  // ikkunan sulkeminen
  }
}


Tapahtumien kuuntelua

Laaditaan väline, jolla voi kokeilla sovelluksen reagointeja rajapintaluokkien ComponentListener, MouseMotionListener, MouseListener, KeyListener ja FocusListener määrittelemiin tapahtumiin.

Koska luokka Reagointeja lupaa toteuttaa kaikki mainitut rajapintaluokat, sen on annettava toteutus niiden kaikille metodeille! Metodit vain tulostavat ilmoituksen itsestään siihen ikkunaan, josta sovellus on käynnistetty - tuttuun tapaan metodilla System.out.println(...)!

import java.awt.*;
import java.awt.event.*;

public class Reagointeja
               extends Frame
               implements ComponentListener, MouseMotionListener,
                          MouseListener, KeyListener, FocusListener {

   private TextArea alue =
     new TextArea ("\n    Reagoiva alue:\n\n" + 
                   "   Kokeile erilaisia\n" +
                   "   tapahtumia ja seuraa\n" +
                   "   systeemin ikkunaa!",
                   10, 20, TextArea.SCROLLBARS_NONE);

   private int laskuri = 0;
   private Label laskuriOtsikko = new Label("", Label.CENTER);

   public Reagointeja() {

      alue.setEditable(false);

      add ("Center", alue);
      add ("South", laskuriOtsikko);

      addComponentListener(this);

      alue.addFocusListener(this);
      alue.addKeyListener(this);
      alue.addMouseListener(this);
      alue.addMouseMotionListener(this);

      addWindowListener(new HoiteleIkkunanSulkeminen()); // Lopetus
   }

   private void kirjaaTapahtuma(AWTEvent tapahtuma) {
      laskuri++;
      laskuriOtsikko.setText("Tapahtumia: " + laskuri + " kpl");
      System.out.println(tapahtuma.toString().substring(15));
      // ei tulosteta kaikille yhteistä alkua "java.awt.event."!
   }

   public void componentMoved(ComponentEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void componentHidden(ComponentEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void componentResized(ComponentEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   } 

   public void componentShown(ComponentEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void mouseDragged(MouseEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void mouseMoved(MouseEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void mousePressed(MouseEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void mouseReleased(MouseEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void mouseEntered(MouseEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }
   public void mouseExited(MouseEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void mouseClicked(MouseEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void keyPressed(KeyEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void keyReleased(KeyEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void keyTyped(KeyEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void focusGained(FocusEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

   public void focusLost(FocusEvent tapahtuma) {
      kirjaaTapahtuma(tapahtuma);
   }

 public static void main(String[] args) {
        Reagointeja ikkuna = new Reagointeja();
        ikkuna.setTitle("Tapahtuu ...");
        ikkuna.pack();
        ikkuna.setVisible(true);
    }
}

class HoiteleIkkunanSulkeminen extends WindowAdapter {
  public void windowClosing(WindowEvent tapahtuma) {
    System.exit(0);  // lopetus
  }
} 


Layoutista ja paneeleista

Sovelluksen (ja sovelman!) käyttöliittymäelementtien sijoittelua ikkunaan ohjataan ns. layout-määrittelyillä. "Layoutin" yksi suomennos on "asemointi". Käytän itse molempia.

Käytössä on seuraavat asemointitavat:

  1. BorderLayout: elementtejä voi sijoitella alueen "ilmansuuntiin" ja keskelle:
         setLayout(new BorderLayout());
         add("North",  nappula1);
         add("South",  nappula2);
         add("East",   nappula3);
         add("West",   nappula4);
         add("Center", nappula5);
    

  2. CardLayout: elementit muodostavat "korttipakan", jonka päällimmäinen elementti on näkyvissä. Metodeilla voi vaihtaa päällimmäistä.

  3. FlowLayout: elementtejä näytetään "vasemmalta oikealle ja ylhäältä alaspäin" alueen koon mukaan. Kun alueen kokoa tai muotoa muutetaan, elementtien paikat voivat vaihtua.

  4. GridBagLayout: joustava ja samalla työläskäyttöinen asemointi.

  5. GridLayout: elementit sijoitetaan "riveihin ja sarakkeisiin":
         setLayout(new GridLayout(2,3)); // rivejä, sarakkeita
         add(nappula1);
         add(nappula2);
         add(nappula3);
         add(nappula4);
         add(nappula5);
         add(nappula6);
    
    

Sovelluksen ikkuna voidaan rakentaa "ali-ikkunoista", paneeleista, joilla jokaisella voi olla oma asemointitapansa. Ja koko ikkunalla omansa.

Ja toki paneeleilla voi olla omia alipaneeleitaan...

Seuraava esimerkki esittelee paneelien ja muutamien layout-asetusten käyttöä. Esimerkkiin perehtyminen jää oman harrastuksen varaan.

import java.awt.*;
import java.awt.event.*;

public class Paneeleja extends Frame {

  private Button b111 = new Button("Nappula 111");
  private TextField t112 = new TextField("Kentta 112");
  private Label l121 = new Label("Teksti 121");
  private Button b122 = new Button("Nappula 122");

  private Button b21 = new Button("Nappula 21");
  private TextField t22 = new TextField("Kentta 22");
  private Button b23 = new Button("Nappula 23");

  private Button b31 = new Button("31");
  private Button b32 = new Button("32");
  private Button b33 = new Button("33");
  private Button b34 = new Button("34");
  private Button b35 = new Button("35");
  private Button b36 = new Button("36");
  private Button b37 = new Button("37");
  private Button b38 = new Button("38");

  private Button b41 = new Button("41");
  private Button b42 = new Button("42");

  public Paneeleja() {

    Panel p11 = new Panel(new BorderLayout());
    p11.add("North", b111);
    p11.add("South", t112);

    Panel p12 = new Panel(new BorderLayout());
    p12.add("North", l121);
    p12.add("South", b122);

    Panel p1 = new Panel(new FlowLayout());
    p1.add(p11); p1.add(p12);

    Panel p2 = new Panel(new FlowLayout());
    p2.add(b21); p2.add(t22); p2.add(b23);

    Panel p3 = new Panel(new GridLayout(2,4));
    p3.add(b31); p3.add(b32); p3.add(b33); p3.add(b34);
    p3.add(b35); p3.add(b36); p3.add(b37); p3.add(b38);

    Panel pp1 = new Panel(new BorderLayout());
    pp1.add("North", p1);
    pp1.add("Center", p2);
    pp1.add("South", p3);

    Panel pp2 = new Panel(new GridLayout(2,1));
    pp2.add(b41); pp2.add(b42);

    this.setLayout(new BorderLayout());
    this.add("Center", pp1);
    this.add("East", pp2);

    addWindowListener(new HoiteleIkkunanSulkeminen());
  }

  public static void main(String[] args) {
    Paneeleja ikkuna = new Paneeleja();
    ikkuna.setTitle("Paneeleja");
    ikkuna.pack();
    ikkuna.setVisible(true);
  }
}

class HoiteleIkkunanSulkeminen extends WindowAdapter {
  public void windowClosing(WindowEvent tapahtuma) {
     System.exit(0);  // ikkunan sulkeminen
  }
}



Takaisin luvun 6 sisällysluetteloon.