Interaktion i Swing

Sven-Olof Nyström
OOP med Java våren -25
Informationsteknologi
Uppsala Universitet

Skansholm: Kapitel 6-8

Interaktion i ett GUI

(GUI = grafiskt användargränssnitt)

Hur går det till när en användare interagerar med ett GUI?

Ett enkelt scenario:

Hur styr användaren kontrollflödet?

Händelser

Användaren kan utföra olika typer av aktioner (till exempel musklick, musrörelser, tangentnedtryckningar). I Swing kallas sådana aktioner för händelser (engelska: events). Varje händelse representeras med ett objekt. Några exempel på klasser som används för att representera händelser:

Typiska klasser och gränssnitt för lyssnare

När användaren gör något, tex klickar med musen på ett fönster, skapas ett händelseobjekt. Swing-systemet letar efter en händelselyssnare som kan hantera händelsen.

Händelselyssnare kan kopplas till olika komponenter. Om användaren klickar på en komponent kollar systemet först om den komponenten har en händelselyssnare, sen om den behållare som komponenten ingår i har en lyssnare etc. Till sist kollar systemet om fönstret har en händelselyssnare.

När systemet har hittat en lyssnare anropas händeselyssnaren med ett meddelande, tex actionPerformed(),

Några olika typer av händelselyssnare.

Notera att ActionListener och MouseListener är gränssnitt (interface). Det innebär att i princip vilken klass som helst kan deklareras att implementera ActionListener eller MouseListener. Därmed kan instanserna av den klassen agera händelselyssnare. Man kan till exempel låta en klass som implementerar ett fönster vara sin egen händelselyssnare.

Princip för händelsehantering

Så om vi vill hantera händelser gör vi så här:

  1. Definiera en händelselyssnare—till exempel något som implementerar gränssnittet ActionListener.
  2. I händelselyssnaren måste du definiera gränssnittets metoder. För ActionListener är det endast en metod som måste definieras: actionPerformed. Metoder av denna typ kallas ibland callbacks.
  3. Addera händelselyssnaren till det Swing-objekt du vill bevaka.

Händelsehantering, exempel

I programmet: program/swing-2/Inmatning2.java låter vi klassen Inmatning2 ärva av JFrame och implementera ActionListener.

Klassen Inmatning2 har två instansvariabler, en knapp pressme och ett textfält answer.

Konstruktorn för Inmatning2 placerar knappen och textfältet på fönstret. Inget konstigt här.

Sen har vi något nytt. Vi anger att fönstret självt, "this", ska vara knappens händelselyssnare: pressme.addActionListener(this)

För att detta ska vara kompilerbart krävs att fönstret implementerar gränssnittet ActionListener. Detta kräver i sin tur att det finns en metod actionPerformed(ActionEvent e) definierad för fönstret.

Så vi definierar en metod actionPerformed(ActionEvent event). Metoden ändrar texten i textfältet och slänger upp en dialog. Inte så spännande. Notera också att metoden tar fram den Swing-komponent där händelsen inträffade med event.getSource() och jämför den med knappen. I en större applikation kan man ha många knappar. Om man låter en händelselyssnare ta hand om flera knappar kan man använda getSource() för att avgöra vilken komponent användaren klickade på.

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

public class Inmatning2 extends JFrame implements ActionListener {

    JButton pressme;
    JTextField answer;

    Inmatning2() {
	super("Inmatning 2");

	JPanel pane = new JPanel();
	JLabel prompt = new JLabel(" Enter a number: ");
	answer = new JTextField ("", 5);
	pressme = new JButton("Press Me");

	answer.requestFocus();

	pressme.addActionListener(this);

	pane.add(prompt);
	pane.add(answer);
	pane.add(pressme);
	add(pane);

	setBounds(100,100,300,200);
	setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

	setVisible(true);
    }

    public void actionPerformed(ActionEvent event) {
	if (event.getSource() == pressme) {
	    answer.setText("Button pressed!");
	    JOptionPane.showMessageDialog(null,"I hear you!");
	    setVisible(true);
	}
    }

    public static void main(String[] args) {
	new Inmatning2();
    }
}

Mer om händelser

Några typiska attribut hos händelser:

Vi har en händelse e (exempelvis av klassen MouseEvent)

KeyEvent

En tangenttryckning e av klassen KeyEvent. Typiska attribut:

Händelser: ett enkelt exempel

Låt oss undersöka hur olika händelser registreras. Programmet program/swing-2/HandelseTest.java listas längre ned.

Här definierar vi händelselysnarna som separata klasser. Klassen Muslyssnare ärver av MouseAdapter och definierar en metod mouseClicked. Metoden skriver ut positionen för klicket.

På samma sätt ärver ärver klassen Tangenlyssnare av KeyAdapter. Här definieras en metod keyTyped som talar om vilken tangent det var.

Och det är väl nästan hela historien. Konstruktorn för klassen Handelsetest skapar en instans av klassen Muslyssnare och adderar den med anropet addMouseListener och skapar sen en instans av klassen TangentLyssnare och adderar den med addKeyListener.

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

class Muslyssnare extends MouseAdapter {
    public void mouseClicked(MouseEvent e) {
	System.out.println("Klick vid ("+e.getX() + "," + e.getY()+")");
    }
}

class Tangentlyssnare extends KeyAdapter {
    public void keyTyped(KeyEvent e) {
	System.out.println("Tangenenten "+ e.getKeyChar());
    }
}

class HandelseTest extends JFrame {

    MouseListener l1 = new Muslyssnare();
    KeyListener l2 = new Tangentlyssnare();

    HandelseTest (){
	addMouseListener(l1);
	addKeyListener(l2);
	setSize(400,400);
	setVisible(true);
	setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main (String[] args ){
	HandelseTest h = new HandelseTest();
    }
}

Om du testkör programmet ser det inte så spännande ut. Prova att klicka på fönstret och observera utskriften. Så här kan det se ut:

$ javac HandelseTest.java
$ java HandelseTest
Klick vid (9,37)
Klick vid (373,42)
Klick vid (359,363)
Klick vid (24,362)
Klick vid (152,192)
Tangenenten h
Tangenenten e
Tangenenten j
$

Mer om musinteraktion

Javas API definierar två gränssnitt för muslyssnare; MouseListener och MouseMotionListener.

Gränssnittet MouseListener deklarerar följande sex metoder:

På samma sätt definierar gränssnittet MouseMotionListener dessa två metoder:

jag kräver inte att ni ska hålla alla dessa metoder i minnet. Jag rekommenderar dock att ni tar er tid att reflektera lite över de olika metoderna.

Jag har skrivit ett litet testprogram program/swing-2/MouseTest.java för att göra det lättare att få grepp om musinteraktionen.

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

class MouseTestListener implements MouseListener, MouseMotionListener {

    // Metoder ur MouseListener

    public void mouseClicked(MouseEvent e) {
	System.out.println("Clicked "+e.getPoint());
    }

    public void mouseEntered(MouseEvent e) {
	System.out.println("Entered "+e.getPoint());
    }

    public void mouseExited(MouseEvent e) {
	System.out.println("Exited "+e.getPoint());
    }

    public void mousePressed(MouseEvent e) {
	System.out.println("Pressed "+e.getPoint());
    }

    public void mouseReleased(MouseEvent e) {
	System.out.println("Released "+e.getPoint());
    }

    // Metoder ur MouseMotionListener

    public void mouseDragged(MouseEvent e) {
	System.out.println("Dragged "+e.getPoint());
    }

    public void mouseMoved(MouseEvent e) {
	System.out.println("Moved "+e.getPoint());
    }
}

public class MouseTest extends JFrame {

  public MouseTest() {
    setTitle("Mouse test");
    JPanel canvas = new JPanel();
    MouseTestListener listener = new MouseTestListener();
    canvas.addMouseListener(listener);
    canvas.addMouseMotionListener(listener);

    canvas.setBackground(Color.green);
    getContentPane().add(canvas);

    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }

  public static void main(String[] args) {
    int width = 600;
    int height = 400;
    JFrame frame = new MouseTest();
    frame.setSize(width, height);
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    frame.setLocation(screenSize.width/2 - width/2,
		      screenSize.height/2 - height/2);
    frame.setVisible(true);
  }
}

Som ni ser är programmet mycket enkelt; vi skapar en klass som både implementerar MouseListener och MouseMotionListener. Varje typ av händelse ger oss en utskrift; tex

System.out.println("Clicked "+e.getPoint());

om användaren klickar med musen på fönstret.

När man testkör programmet måste man vara lite försiktig; en snabb rörelse över fönstret kan ge ett hundratal utskrifter.

Entered java.awt.Point[x=0,y=329]
Moved java.awt.Point[x=1,y=329]
Moved java.awt.Point[x=2,y=329]
Moved java.awt.Point[x=3,y=329]
Moved java.awt.Point[x=4,y=329]
Moved java.awt.Point[x=4,y=330]
Moved java.awt.Point[x=4,y=331]
Moved java.awt.Point[x=5,y=331]
Moved java.awt.Point[x=5,y=332]
Pressed java.awt.Point[x=5,y=332]
Dragged java.awt.Point[x=6,y=332]
...
Dragged java.awt.Point[x=11,y=336]
Dragged java.awt.Point[x=12,y=336]
Released java.awt.Point[x=12,y=336]
Moved java.awt.Point[x=11,y=336]
Moved java.awt.Point[x=10,y=336]
Moved java.awt.Point[x=11,y=336]
Moved java.awt.Point[x=12,y=336]
Moved java.awt.Point[x=13,y=336]
Moved java.awt.Point[x=14,y=336]
Pressed java.awt.Point[x=14,y=336]
Released java.awt.Point[x=14,y=336]
Clicked java.awt.Point[x=14,y=336]
Pressed java.awt.Point[x=14,y=336]
Released java.awt.Point[x=14,y=336]
Clicked java.awt.Point[x=14,y=336]
Moved java.awt.Point[x=13,y=336]
Moved java.awt.Point[x=12,y=336]
...
Moved java.awt.Point[x=1,y=341]
Moved java.awt.Point[x=0,y=341]
Exited java.awt.Point[x=-1,y=341]

Här rörde jag musen försiktigt över nedre vänstra hörnet, ändå blev utskriften lång. Prova själv! Om du vill kan du ta bort utskriften av mouseMoved(), det blir mycket utskrift ändå.