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 Calculator0
kan 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
-
introducera
GridLayout
som placerar komponenterna i en matris samt -
använda den redan bekanta komponenten
JPanel
för att definiera delfönster.
En GridLayout
definierar en matris där alla komponenter får lika stort
utrymme. Uttrycket
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:
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.
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
-
Vi har introducerat två nya grafiska komponenter: knappar
(
JButton
) och textfält (JTextField
). Med hjälp av dessa kan användaren interagera med programmet. - Till knappar och textfält kan man koppla lyssnare. En lyssnare är en klass som har en metod som hanterar händelser av typen "knapp klickad" eller "text skriven".
-
För just dessa två komponenter behöver vi en metod
actionPerformed(ActionEvent e)
som anger vad som skall ske när knappen är klickad eller texten skriven. Klassen som har denna metod måste ha "implements ActionListener
" i klasshuvudet. - Händelsestyrda program: efter initieringen väntar programmet på att händelser skall inträffa. När händelsen är hanterad återgår programmet till att vänta på nästa händelse.
-
Ny layoutstrategi:
GridLayout
som definierar en "matris" med lika stor plats för alla komponenter. -
Paneler (
JPanel
) kan användas för att definiera delfönster. I dessa kan man lägga in andra komponenter och de har en layoustrategi.
Härifrån kan man fortsätta med lektionen om rörlig grafik