Grafiska användargränssnitt

Denna lektion ger en introduktion till hur man skapar ett grafiskt användargränssnitt ("GUI") dvs ett fönster med t.ex. klickbara knappar och textrutor som programmet kan läsa av. Lektionen bygger på introduktionslektionen om grafik.

Java har många klasser för att bygga grafiska användargränssnitt och denna lektion skrapar bara lite på ytan. Se t.ex. "The Swing Tutorial" för en mer fullständig beskrivning.

JTextField - ett skrivbart textfält

Vi introducerar här den grafiska komponenten JTextField. Ytligt sett ser den ungefär ut som en etikett (objekt av typen JLabel). En skillnad är emellertid att användaren kan skriva i rutan och programmet kan läsa vad som står där. I en JLabel är det ju alltid programmet som sätter texten.

Vi skall demonstrera JTextField genom att skriva en klass som visar upp ett fönster med två textfält. När användaren har skrivit ett tal i varje ruta summerar programmet talen och skriver ut resultatet som text i en JLabel.

Vi kommer att klara detta program med en enda klass. Eftersom programmet skall skapa ett fönster med några komponenter gör vi det som en subklass till JFrame.

Vi börjar så här (kan laddas ner som helhet fast med namnet Adder0 från Adder0.java. ):

import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.Scanner; Paket som behövs
public class Adder extends JFrame { Klassen baseras på (ärver från) klassen JFrame
private JTextField a = new JTextField(10); private JTextField b = new JTextField(10); private JLabel result = new JLabel(" Result "); Skapar tre komponenter (två skrivbara textfält och en etikett). Talet 10 är en breddangivelse.
public Adder() { super("Adder"); Konstruktor som anger att basklassens konstruktor som tar emot en sträng skall användas. Strängen sätter en rubrik på fönstret.
this.setLayout(new FlowLayout()); Anger vilken layout-strategi som skall användas. FlowLayout är inte den mest passande i detta fall men det är den enklaste och den enda vi hittills har beskrivit.
this.add(a); this.add(b); this.add(result); Lägger in de tre komponenterna i fönstret
this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } Beräknar storlekar, gör fönstret synbart samt anger vad som skall hända om fönstrets stängs. Denna kod bör alltid finnas med och ligga sist i konstruktorn.
public static void main(String[] arg) { Adder ma = new Adder(); } } En main-metod som skapar fönstret.

Det enda nya i denna klass är de två textfälten.

Kopiera, kompilera och testkör!

Observera att det går att skriva i de två första rutorna (textfälten) men inte i den tredje (etiketten). Det vi skriver har dock ingen effekt.

Vi behöver se till att programmet reagerar på händelser av typ "någon knapp klickad" eller "något textfält färdigskrivet". Just dessa händelser beskrivs av en klass med namnet ActionEvent. (Andra händelser som t.ex. tangenttryckning beskrivs av andra händelseklasser.)

Vi gör detta genom att förse textfälten med en lyssnare. En lyssnare (för textfält och knappar) är en klass (vilken som helst) som innehåller en metod actionPerformed(ActionEvent e). Lyssnaren kopplas sedan till de komponenter man vill att den ska reagera på händelser från (i detta fall till textfälten a och b). Metoden actionPerformed ser till att det som skall hända utförs.

Förutom att ha metoden actionPerformed måste man i klasshuvudet lägga till texten implements ActionListener. Man säger att "klassen implementerar interfacet ActionListener". Det innebär att man kommer att förse klassen med metoder som ActionListener anger. (ActionListener är inte en klass utan ett Interface vilket kommer att diskuteras mer senare i kursen.)

Vi börjar med en variant som inte utför någon räkning utan bara kopierar texten från textfälten a och b till etiketten result (kan laddas ner som helhet fast med namnet Adder1 från Adder1.java.): :

import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.Scanner; public class Adder extends JFrame Som tidigare.
implements ActionListener Anger att klassen är beredd att lyssna på händelser.
{ JTextField a = new JTextField(10); JTextField b = new JTextField(10); JLabel result = new JLabel(" Result "); public Adder() { super("Adder"); this.setLayout(new FlowLayout()); Som tidigare
a.addActionListener(this); b.addActionListener(this); Anger att denna klass skall lyssna på händelser från textfälten a och b.
this.add(a); this.add(b);// this.add(result); this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } Som tidigare
public void actionPerformed(ActionEvent e) { String s1 = a.getText(); // Text contents in a String s2 = b.getText(); // Text contents in b result.setText(s1 + s2); // Sets the result } Definierar vad som skall ske när händelser som klassen lyssnar efter inträffar. Den händelse som man här lyssnar på skapas när någon trycker på Enter - alltså inte på enskilda tangenttryckningar. Texten hämtas texten från från a och b och läggs ut etiketten result.
public static void main(String[] arg) { Adder ma = new Adder(); } } Som tidigare

Provkör detta! Skriv i textfälten och se vad som händer när du trycker på Enter. Ändra i textfälten och se hur resultatet uppdateras så fort du trycker på Enter.

Vi vill emellertid att programmet skall tolka textfälten som tal, lägga ihop dessa och lägga ut resultatet i resultatetiketten. Därför ändrar vi metoden actionPerfomed. (Hela klassen med denna metod kan laddas i sin helhetladdas ner som helhet från Adder.java.)

public void actionPerformed(ActionEvent e) { Som tidigare
Scanner sca = new Scanner(a.getText()); Scanner scb = new Scanner(b.getText()); if (sca.hasNextDouble() && scb.hasNextDouble()) { double x = sca.nextDouble(); double y = scb.nextDouble(); double sum = x + y; result.setText("" + sum ); } else { result.setText("*** ERROR ***"); } Kopplar ett Scanner-objekt till vardera textfältsinnehåll Om båda innehåller en double läses dessa in, summeras och placeras i etiketten för resultatet. Om inte båda textfälten går att tolka som tal läggs en felutskrift in.
}

Observera att vi nu har ett händelsestyrt program. Det enda main-metoden gör är att sätta upp fönstret och sedan väntar programmet på händelser (i detta fall att vi trycker på Enter i något av textfälten). När händelsen är hanterad väntar programmet på nästa händelse. Byt ut innehållen i textfälten och så att du ser att programmet reagerar när du trycker på Enter!

Klickbara knappar

I ovanstående exempel introducerade vi textfält (JTextField) och kopplade en lyssnare till dessa. Om textfälten innehöll giltiga double-värden utfördes en addition.

Programmet skulle vara mer användbart om vi kunde välja vilken operation som skall utföras - addition, subtraktion, ... För att göra detta introducerar vi den grafiska komponenten knapp (JButton).

Vi förser programmet med en knapp för vardera av operationerna addition, subtraktion, multiplikation och division. (Programmet med namnet Calculator0kan laddas ner från Calculator0.java)

import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.Scanner; public class Calculator extends JFrame implements ActionListener { JTextField arg1 = new JTextField(10); JTextField arg2 = new JTextField(10); JLabel result = new JLabel(" RESULTAT "); Som tidigare men med nytt namn på klassen och bättre namn på argumenten.
JButton addB = new JButton("+"); JButton subB = new JButton("-"); JButton mulB = new JButton("*"); JButton divB = new JButton("/"); Definierar fyra knappar som attribut i klassen
public Calculator() { super("Calculator"); this.setLayout(new FlowLayout());
// a.addActionListener(this); tas bort // b.addActionListener(this); tas bort addB.addActionListener(this); subB.addActionListener(this); mulB.addActionListener(this); divB.addActionListener(this); this.add(addB); this.add(subB); this.add(mulB); this.add(divB); Nu skall programmet inte längre lyssna på textfälten
utan på de nya knapparna
 
 
som också skall läggas in.
this.add(arg1); this.add(arg2); this.add(result); this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } Som tidigare.
public void actionPerformed(ActionEvent e) { Scanner sca = new Scanner(arg1.getText()); Scanner scb = new Scanner(arg2.getText()); if (sca.hasNextDouble() && scb.hasNextDouble()) { double x = sca.nextDouble(); double y = scb.nextDouble(); Som tidigare.
double r = 0; if (e.getSource() == addB) { r = x + y; } else if (e.getSource() == subB) { r = x - y; } else if (e.getSource() == mulB) { r = x*y; } else if (e.getSource()== divB) { r = x/y; } result.setText("" + r); Undersöker vilken vilken händelse som inträffat (vilken av knapparna som klickats) och utför önskad operation.
} else { result.setText("*** ERROR ***"); } } Som tidigare.

Det är nu ganska uppenbart att vi behöver bättre kontroll över fönstrets layout.

Något mer om layout

Hittills har vi bara använt FlowLayout som lägger in komponenterna radvis från vänster till höger. Antalet komponenter som ryms på en rad beror såväl på komponenternas storlek som fönstrets bredd. Om man drar i fönstret så flyttas komponenterna omkring.

För att få det lite snyggare skall vi

En GridLayout definierar en matris där alla komponenter får lika stort utrymme. Uttrycket

new GridLayout(3, 2)
definierar en 3x2-matris.

Byt ut FlowLayout() mot GridLayout(3,2) i konstruktorn och testkör! Se vad som händer när du ändrar storlek och form på fönstret!

Med hjälp av komponenten JPanel (som vi i annan lektion använde för att rita i) kan vi definiera delfönster. Det går att lägga in (med add) komponenter (knappar, textfält, ...) i en panel och ange vilken layout som skall användas. Layouten anges när panelen skapas. Ex:

JPanel buttons = new JPanel(new GridLayout(2,2));

definierar ett delfönster med komponenter i en 2x2-matris. Detta delfönster läggs sedan in i fönstret med add på vanligt sätt.

Vår kalkylator skulle kunna bestå av ett delfönster med textfälten och etiketten i en 3x1-matris och ett delfönster med knappar i en 2x2-matris. Layouten definieras i konstruktorn som då får nedanstående utseende. Programmet kan hämtas i sin helhet från Calculator.java.
public Calculator() { super("Calculator"); addB.addActionListener(this); subB.addActionListener(this); mulB.addActionListener(this); divB.addActionListener(this); Som tidigare.
result.setBackground(Color.white); result.setOpaque(true); result.setPreferredSize(new Dimension(200,40)); result.setBorder(new LineBorder(Color.red, 5, true)); Passar på tillfället att demonstrerar hur man kan "snygga till" resultat-knappen med bakgrundsfärg, storlek och ram. Klassen LineBorder finns i javax.swing.border.* som alltså måste importeras.
JPanel dataPart = new JPanel(new GridLayout(3, 1)); dataPart.add(arg1); dataPart.add(arg2); dataPart.add(result); Definierar delfönstret för data-delen som en stapel av de tre komponenterna.
JPanel operatorPart = new JPanel(new GridLayout(2, 2)); operatorPart.add(addB); operatorPart.add(subB); operatorPart.add(mulB); operatorPart.add(divB); Placerar operatortionsknapparna i en 4x4-matris.
this.setLayout(new FlowLayout()); this.add(dataPart); this.add(operatorPart); Lägger in de två delfönstren i huvudfönstret. Behåller FlowLayout.
this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } Wie gewöhnlich.

Övning: Gör om kalkylatorn så att den har ett inmatningsfält och ett resultatfält. Operationerna skall då utföras på inmatningsfältet och resultatfältet så att man kan fortsätta att räkna med resultatet från föregående operation. Lägg till knappar några matematiska funktioner som exp, sin, cos, ...

Sammanfattning

Härifrån kan man fortsätta med lektionen om rörlig grafik


Tillbaka

Valid CSS!