9. Käyttöliittymät

Käyttöliittymät

Kaikki ohjelmamme ovat olleet tähän mennessä tekstipohjaisia. Syynä tähän on se, että tekstipohjaiset ohjelmat soveltuvat hyvin ohjelmoinnin perusasioiden opetteluun niiden yksinkertaisuuden ansiosta. Nyt on kuitenkin tullut aika tutustua graafisen käyttöliittymän toteuttamiseen.

Käyttöliittymän ohjelmoinnissa ei ole sinänsä mitään erityisen vaikeaa, mutta siihen liittyy paljon asioita, jotka pitää vain tietää. Luokkia ja metodeita on valtava määrä, minkä vuoksi käyttöliittymän ohjelmointi voi tuntua aluksi sekavalta. Käyttöliittymät tulevat paremmin tutuksi ohjelmoinnin harjoitustyössä.

Ikkunan luonti

Seuraava ohjelma luo ikkunan ja piirtää siihen tekstiä:

import java.awt.*;
import javax.swing.*;

public class Tervehtija extends JPanel {

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
	// tähän tulee grafiikan tuottava koodi
        g.drawString("Hei hei!", 80, 30);
    }

    public static void main(String[] args) {
        JFrame ikkuna = new JFrame();
        ikkuna.setTitle("Tervehtijä"); // ikkunan otsikko
        ikkuna.setSize(320, 200);      // ikkunan koko
        ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        ikkuna.setVisible(true);
	Tervehtija ohjelma = new Tervehtija();
        ikkuna.getContentPane().add(ohjelma);
    }
}

Ohjelman suoritus näyttää seuraavalta:

Käyttöliittymät tuovat aina mukanaan jonkin verran "ylimääräistä" koodia, josta ei tarvitse käytännössä välittää. Tärkeät kohdat main-metodissa ovat rivit, jotka määrittävät ikkunan otsikon (Tervehtijä) sekä ikkunan koon (320 x 200 pikseliä). Lisäksi toiseksi viimeisellä rivillä esiintyy luokan nimi.

Ikkunaan piirtäminen tapahtuu metodissa paint. Tämä metodi aktivoituu aina silloin, kun ikkunan sisältö täytyy piirtää uudestaan. Metodin ensimmäinen rivi super.paint(g) käytännössä tyhjentää ikkunan piirtämistä varten. Tämän jälkeen tulevat omat piirtokomentomme. Tässä tapauksessa komento drawString piirtää halutun merkkijonon haluttuun paikkaan.

Piirtäminen

Seuraava ohjelma piirtää ikkunaan erilaisia kuvioita:

import java.awt.*;
import javax.swing.*;

public class Piirtaja extends JPanel {

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.RED);
        g.drawLine(20, 30, 200, 140);
        g.drawRect(60, 40, 140, 90);
        g.setColor(Color.BLUE);
        g.drawOval(160, 10, 90, 90);
        g.setColor(Color.GREEN);
        g.fillRect(30, 70, 70, 80);
        g.fillOval(220, 100, 40, 30);
    }

    public static void main(String[] args) {
        JFrame ikkuna = new JFrame();
        ikkuna.setTitle("Piirtäjä");
        ikkuna.setSize(320, 200);
        ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        ikkuna.setVisible(true);
	Piirtaja ohjelma = new Piirtaja();
        ikkuna.getContentPane().add(ohjelma);
    }
}

Ohjelman suoritus näyttää seuraavalta:

Tässä ovat käytössä seuraavat komennot:

Näppäimistön käsittely

Jos ohjelman on tarkoitus tunnistaa, kun käyttäjä painaa näppäimistön nappia, sen täytyy toteuttaa rajapinta KeyListener. Tämän jälkeen metodi keyPressed aktivoituu aina, kun käyttäjä painaa nappia. Seuraava ohjelma tulostaa komentoriville käyttäjän painamat napit.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Nappaimisto extends JPanel implements KeyListener {
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
    }

    public void keyTyped(KeyEvent e) {
    }

    public void keyPressed(KeyEvent e) {
        int nappi = e.getKeyCode();
        System.out.println("Painoit nappia " + nappi);
    }

    public void keyReleased(KeyEvent e) {
    }

    public static void main(String[] args) {
	JFrame ikkuna = new JFrame();
        ikkuna.setTitle("Näppäimistön käsittely");
        ikkuna.setSize(320, 200);
        ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        ikkuna.setVisible(true);
        Nappaimisto ohjelma = new Nappaimisto();
        ikkuna.getContentPane().add(ohjelma);
        ikkuna.addKeyListener(ohjelma); // uusi rivi!
    }
}

Esimerkiksi jos käyttäjä painaa ensin nappia A, sitten nappia Shift ja lopuksi nappia Esc, ohjelma tulostaa seuraavasti:

Painoit nappia 65
Painoit nappia 16
Painoit nappia 27

Huomaa ohjelman tulostus "tavanomaisessa" paikassa NetBeans-ikkunan alareunassa.

Siis esimerkiksi napin A koodi on 65. Helpoin tapa selvittää eri nappien koodit on käyttää yllä olevaa ohjelmaa.

Esimerkki: Näppäimistöohjaus

Seuraavassa esimerkissä käyttäjä voi ohjata näppäimistöllä ikkunassa liikkuvaa neliötä.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Ohjaus extends JPanel implements KeyListener {
    int nelionX = 100;
    int nelionY = 100;

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawRect(nelionX, nelionY, 50, 50);
    }

    public void keyPressed(KeyEvent e) {
        int nappi = e.getKeyCode();
        if (nappi == 37) { // vasemmalle
            nelionX = nelionX - 10;
        }
        if (nappi == 38) { // ylös
            nelionY = nelionY - 10;
        }
        if (nappi == 39) { // oikealle
            nelionX = nelionX + 10;
        }
        if (nappi == 40) { // alas
            nelionY = nelionY + 10;
        }
        repaint();
    }

    public void keyTyped(KeyEvent e) {
    }

    public void keyReleased(KeyEvent e) {
    }

    public static void main(String[] args) {
	JFrame ikkuna = new JFrame();
        ikkuna.setTitle("Näppäimistöohjaus");
        ikkuna.setSize(320, 200);
        ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        ikkuna.setVisible(true);
        Ohjaus ohjelma = new Ohjaus();
        ikkuna.getContentPane().add(ohjelma);
        ikkuna.addKeyListener(ohjelma);
    }
}

Muuttujissa nelionX ja nelionY on neliön sijainti ruudulla. Neliö piirretään muuttujien perusteella oikeaan paikkaan metodissa paint. Näppäimistön käsittelyssä sijainti muuttuu, jos käyttäjä painaa nuolinäppäimiä. Huomaa komento repaint näppäimistön käsittelyn lopussa: se piirtää ikkunan uudestaan.

Hiiren käsittely

Hiiren käsitteleminen tapahtuu hyvin samaan tapaan kuin näppäimistön käsitteleminen. Tällä kertaa toteutettava rajapinta on MouseListener. Seuraava ohjelma tulostaa komentoriville, mistä kohdasta ikkunaa ja millä napilla käyttäjä painaa hiirellä.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Hiiri extends JPanel implements MouseListener {

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
    }

    public void mouseClicked(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
        int nappi = e.getButton();
        System.out.println("Kohta: (" + x + ", " + y + ")");
        System.out.println("Nappi: " + nappi);
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public static void main(String[] args) {
        JFrame ikkuna = new JFrame();
        ikkuna.setTitle("Hiiren käsittely");
        ikkuna.setSize(320, 200);
        ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        ikkuna.setVisible(true);
        Hiiri ohjelma = new Hiiri();
        ikkuna.getContentPane().add(ohjelma);
        ikkuna.addMouseListener(ohjelma);
    }
}

Esimerkiksi jos käyttäjä painaa kahdesti vasemmalla näppäimellä ja kerran oikealla näppäimellä, tulostus voi olla seuraava:

Kohta: (45, 70)
Nappi: 1
Kohta: (272, 146)
Nappi: 1
Kohta: (98, 156)
Nappi: 3

Viesti-ikkunat

Seuraavat lyhyet komennot ovat hyödyllisiä:

JOptionPane.showMessageDialog(this, "Hei hei!");

Tämä komento näyttää halutun viestin pikkuikkunassa:

String nimi = JOptionPane.showInputDialog("Anna nimesi:");

Tämä komento kysyy käyttäjältä jonkin asian muuttujaan:

Ohjelman sulkeminen

Joskus ohjelman pitäisi sulkeutua tietyn asian tapahtuessa. Tähän riittää seuraava komento:

System.exit(0);

Ajastin

Ajastin (Timer) aktivoi metodin actionPerformed säännöllisin väliajoin. Ajastimen määrittelyssä ilmoitetaan millisekunnissa, kuinka usein metodi tulee aktivoida. Nyt ohjelman tulee toteuttaa rajapinta ActionListener. Seuraava ohjelma tulostaa komentoriville viestin sekunnin välein.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Ajastin extends JPanel implements ActionListener {

    public Ajastin() {
        Timer ajastin = new Timer(1000, this);
        ajastin.start();
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
    }

    public void actionPerformed(ActionEvent e) {
        System.out.println("Aikaa kului sekunti");
    }

    public static void main(String[] args) {
        JFrame ikkuna = new JFrame();
        ikkuna.setTitle("Ajastin");
        ikkuna.setSize(320, 200);
        ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        ikkuna.setVisible(true);
        Ajastin ohjelma = new Ajastin();
        ikkuna.getContentPane().add(ohjelma);
    }
}

Esimerkiksi kolmen sekunnin kuluttua tulostus on ollut seuraava:

Aikaa kului sekunti
Aikaa kului sekunti
Aikaa kului sekunti

Tässä ajastin määritellään luokan konstruktorissa. Konstruktoriin voi yleensäkin laittaa koodia, joka suoritetaan ohjelman käynnistyessä.

Esimerkki: Liikkuva neliö

Seuraava ohjelma toteuttaa ajastimen avulla animaation, jossa neliö liikkuu ikkunan halki.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class LiikkuvaNelio extends JPanel implements ActionListener {
    int nelionX = 100;
    int nelionY = 100;

    public LiikkuvaNelio() {
        Timer ajastin = new Timer(50, this);
        ajastin.start();
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawRect(nelionX, nelionY, 50, 50);
    }

    public void actionPerformed(ActionEvent e) {
        nelionX = nelionX + 1;
        repaint();
    }

    public static void main(String[] args) {
        JFrame ikkuna = new JFrame();
        ikkuna.setTitle("Liikkuva neliö");
        ikkuna.setSize(320, 200);
        ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        ikkuna.setVisible(true);
        LiikkuvaNelio ohjelma = new LiikkuvaNelio();
        ikkuna.getContentPane().add(ohjelma);
    }
}

Esimerkki: Pomppiva pallo

Seuraava ohjelma toteuttaa ajastimen avulla animaation, jossa ikkunassa pomppii ikkunan reunoista kimpoileva pallo.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class PomppivaPallo extends JPanel implements ActionListener {
    int palloX = 100;
    int palloY = 100;
    int muutosX = 20;
    int muutosY = 20;

    public PomppivaPallo() {
        Timer ajastin = new Timer(50, this);
        ajastin.start();
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.fillOval(palloX, palloY, 20, 20);
    }

    public void actionPerformed(ActionEvent e) {
        palloX = palloX + muutosX;
        palloY = palloY + muutosY;
        if (palloX <= 0 || palloX >= 280) {
            muutosX = -muutosX;
        }
        if (palloY <= 0 || palloY >= 160) {
            muutosY = -muutosY;
        }
        repaint();
    }

    public static void main(String[] args) {
        JFrame ikkuna = new JFrame();
        ikkuna.setTitle("Pomppiva pallo");
        ikkuna.setSize(320, 200);
        ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        ikkuna.setVisible(true);
        PomppivaPallo ohjelma = new PomppivaPallo();
        ikkuna.getContentPane().add(ohjelma);
    }
}