Interaktion med många knappar

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

Många knappar?

Jag visar på några olika sätt att lösa samma problem. Programmen Three_<n>.java, där n=1,2,3,4 eller 5 gör samma sak. I samtliga fall har vi tre knappar, och när du klickar på en av dem visas motsvarande utskrift. Det som skiljer de olika programmen är hur vi hanterar användarens aktioner och avgör vilken knapp som klickades.

Här ger jag exempel på användning av inre klasser och anonyma inre klasser.

Fallanalys

I det första exemplet program/swing-2/Three_1.java har vi en händelselyssnare för alla knappar. Med en enkel fallanalys avgörs vilken knapp användaren klickade på.

public void actionPerformed(ActionEvent event) {
    if (event.getSource()==knappar[0]) {
        answer.setText("Ein");
    }
    else if (event.getSource()==knappar[1]) {
        answer.setText("Dva");
    }
    else if (event.getSource()==knappar[2]) {
        answer.setText("Trois");
    }
    else {
        answer.setText("???");
    }
}

En klass för varje knapp

I det andra exemplet program/swing-2/Three_2.java definierar vi tre klasser för händelselyssnare. Varje klass får sin egen händelselyssnare. I det här exemplet är operationerna för de olika knapparna mycket enkla, så det känns lite överdrivet att definiera en klass för varje operation.

Koden som lägger in händelselyssnarna:

knappar[0].addActionListener(new Ein(answer));
knappar[1].addActionListener(new Dva(answer));
knappar[2].addActionListener(new Trois(answer));
Koden för en av lyssnarklasserna:
class Ein implements ActionListener {

    JTextField answer;

    Ein (JTextField a) {
	answer = a;
    }

    public void actionPerformed(ActionEvent event) {
	    answer.setText("Ein");
    }
}

Notera att konstruktorn tar textfältet som argument. Det är så lyssnaren får veta var den ska placera den text som ska visas.

Separata lyssnare, samma klass

Notera att de olika lyssnarklasserna blev mycket lika. Det enda som skiljer är den text som visas i textfältet. Vi provar att ersätta dem med en enda klass.

Vi kallar klassen Numerator och låter dess konstruktor ta strängen som ska visas som argument:

class Numerator implements ActionListener {

    JTextField answer;

    String number;

    Numerator (JTextField a, String n) {
	answer = a;
	number = n;
    }

    public void actionPerformed(ActionEvent event) {
	    answer.setText(number);
    }
}

Notera att vi nu kommer att skapa tre instanser av klassen. Så här ser det ut när vi lägger in lyssnarna:

knappar[0].addActionListener(new Numerator(answer, "Ein"));
knappar[1].addActionListener(new Numerator(answer, "Dva"));
knappar[2].addActionListener(new Numerator(answer, "Trois"));

Koden för det tredje exemplet hittar du här: program/swing-2/Three_3.java.

Inre klasser

Java tillåter att en klass definieras inuti en annan klass. Vi talar om inre klasser. Det här är nåt som jag inte kommer att ägna mycket tid åt på kursen, men det kan vara värt att titta på ett par exempel. Så här börjar det fjärde exemplet (program/swing-2/Three_4.java):

public class Three_4 extends JFrame  {
    JButton[] knappar;
    JTextField answer;

    class Numerator implements ActionListener {

        String number;

        Numerator (String n) {
            number = n;
        }

        public void actionPerformed(ActionEvent event) {
            answer.setText(number);
        }
    }

Så klassen Numerator som definierar händelselyssnaren är definierad inuti klassen som definierar fönstret. En inre klass kan komma åt den omgivande klassens instansvariabler—i exemplet har vi en referens till instansvariabeln answer. Notera att detta fungerar även om answer är deklarerad privat (för att göra koden mer lättläst tog jag inte med private och public-deklarationer).

Anonyma inre klasser

Jovisst, man kan definiera en klass utan att ge den ett namn.

I tredje exemplet hade vi tre klasser där varje klass endast användes på en plats i programmet. Om du har en sådan klass (och klassen är ganska enkel) kan det vara idé att göra den anonym.

Om klassen har en superklass S kan man skriva

S x = new S () { ... }

för att definiera en subklass till S och skapa ett objekt i den klassen. Inuti { ... } finns plats för att deklarationer av instansvariabler och metoddefinitioner.

Det fullständiga exemplet hittar du här: program/swing-2/Three_5.java. Så här ser det ut när man lägger in en händelselyssnare för en knapp:

knappar[0].addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
            answer.setText("Ein");
        }
    });

Som du ser blir koden mycket kompakt med en anonym klass. Nackdelen är att koden blir svårläst om den anonyma klassen är mer komplicerad.

Jag ser helst att ni undviker inre klasser (och särskilt anonyma inre klasser) i lösningar i kursen. Undantaget är möjligtvis mycket enkla klasser.