Föreläsning 7 Innehåll Grafiska användargränsnitt i Java Grafiska användargränsnitt i Java Komponenter (fönster, knappar, ) Layout Händelsehantering (Hur man får någonting att hända när användaren t.ex. klickar på en knapp.) Design Undervisningsmoment: föreläsning 7, lab 3 och 6 Avsnitt i läroboken: delar av appendix C Exempel på vad du ska kunna Implementera enkla användargränsnitt med hjälp av Swing och AWT. Beskriva och kunna tillämpa principerna för händelsehantering i Swing/AWT. Kunna separera modell och vy (grafiskt användargränssnitt) vid implementering av program med grafiska användargränssnitt. Grafiska användargränssnitt examineras inte i samband med tentamen utan enbart på laboration 3 och 6. De som väljer att lösa sudoku eller implementera en kalkylator som inlämningsuppgift får ytterligare träning på detta. PFK (Föreläsning 7) VT 2015 1 / 55 PFK (Föreläsning 7) VT 2015 2 / 55 Klasspaket i Java Använda klasspaketet Swing För att kunna använda Swing måste man känna till AWT (Abstract Window Toolkit) Ett äldre paket Delar av det används fortfarande (händelsehantering och layout). Klasserna som beskriver grafiska komponenter: fönster (JFrame), knappar (JButton) Swing Ett modernare paket Hur man placerar de grafiska komponenterna i ett fönster (layout). Hur man hanterar händelser. T.ex. att användaren trycker på en knapp. PFK (Föreläsning 7) VT 2015 3 / 55 PFK (Föreläsning 7) VT 2015 4 / 55
Grafiska komponenter Top-level -behållare Styrkomponenter Används för att interagera med användare. Ex: knappar, menyer, textrutor Reagerar på händelser (musklick, menyval,... ). Behållarkomponenter Kan innehålla andra komponenter och används för att organisera komponenter. Ex. fönster, paneler Det finns några top-level -behållare i Swing, t.ex: JFrame - ett fönster med ram och titel JDialog - ett fönster som används för dialog med användare Man måste använda en top-level -behållare för sina komponenter Komponenter som själva är behållare kan sedan innehålla andra komponenter... PFK (Föreläsning 7) VT 2015 5 / 55 PFK (Föreläsning 7) VT 2015 6 / 55 Exempel: Fönster med en knapp och en etikett import javax.swing.*; public class CounterView { public CounterView() { JFrame frame = new JFrame("Counter"); JPanel panel = new JPanel(); frame.add(panel); JButton button = new JButton("Yes!"); panel.add(button); JLabel label = new JLabel("Number yes: " + 0); panel.add(label); public static void main(string[] args) { new CounterView(); PFK (Föreläsning 7) VT 2015 7 / 55 Exempel: Fönster med en knapp och en etikett Förklaring av programkoden JFrame frame = new JFrame("Counter"); // Skapa ett nytt fönster med rubriken "Counter" // Exekveringen ska avslutas om användaren stänger fönstret JPanel panel = new JPanel(); // Skapa en panel frame.add(panel); // Låt fönstret frame innehålla panelen JButton button = new JButton("Yes!"); // Skapa en ny knapp panel.add(button); // Låt panelen panel innehålla knappen JLabel label = new JLabel("Number yes: " + 0); // Skapa en ny etikett panel.add(label); // Låt panelen panel innehålla etiketten // Gör fönstret lagom stort // Gör fönstret synligt PFK (Föreläsning 7) VT 2015 8 / 55
Layoutmanager En layoutmanager är ett objekt som bestämmer storlek och läge för komponenterna i en behållare. Den räknar också ut nya positioner och storlekar om fönstrets storlek ändras. Varje behållare har en egen layoutmanager kopplad till sig. Olika typer av layoutmanagers har olika strategier för placering av komponenterna. Ex: Borderlayout delar utrymmet i fem delar; norr, söder, öster, väster och mitten. Flowlayout komponenterna placeras i en rad efter varandra. Gridlayout rutnät. Borderlayout är standardlayout för JFrame och Flowlayout är standardlayout för JPanel. PFK (Föreläsning 7) VT 2015 9 / 55 Exempel: Flowlayout Exempel: BorderLayout import javax.swing.*; import java.awt.*; public class FrameWithBorderLayout { public FrameWithBorderLayout() { JFrame frame = new JFrame("A BorderLayout Frame"); frame.add(new JButton("North"), BorderLayout.NORTH); frame.add(new JButton("South"), BorderLayout.SOUTH); frame.add(new JButton("East"), BorderLayout.WEST); frame.add(new JButton("West"), BorderLayout.EAST); frame.add(new JButton("Center"), BorderLayout.CENTER); public static void main(string[] args) { new FrameWithBorderLayout(); PFK (Föreläsning 7) VT 2015 10 / 55 Flowlayout - ändring av layouten när fönstret ändrar storlek public FrameWithFlowLayout() { JFrame frame = new JFrame("A FlowLayout Frame"); frame.setlayout(new FlowLayout()); frame.add(new JButton("one")); frame.add(new JButton("two")); frame.add(new JButton("three")); frame.add(new JButton("four")); frame.add(new JButton("five")); PFK (Föreläsning 7) VT 2015 11 / 55 PFK (Föreläsning 7) VT 2015 12 / 55
Exempel: Gridlayout Exempel: Fönster två paneler public FrameWithPanels() { JFrame frame = new JFrame("A Frame with Two Panels"); public FrameWithGridLayout() { JFrame frame = new JFrame("A GridLayout Frame"); frame.setlayout(new GridLayout(2, 3)); frame.add(new JButton("one")); frame.add(new JButton("two")); frame.add(new JButton("three")); frame.add(new JButton("four")); frame.add(new JButton("five")); frame.add(new JButton("six")); PFK (Föreläsning 7) VT 2015 13 / 55 Händelsehantering JPanel northpanel = new JPanel(); northpanel.add(new JButton("one")); northpanel.add(new JButton("two")); frame.add(northpanel, BorderLayout.NORTH); JPanel southpanel = new JPanel(); southpanel.add(new JButton("three")); southpanel.add(new JButton("four")); southpanel.add(new JButton("five")); frame.add(southpanel, BorderLayout.SOUTH); PFK (Föreläsning 7) VT 2015 14 / 55 Lyssnarobjekt När användaren klickar på en knapp (eller väljer ett menyalternativ ) händer följande: 1 Ett händelseobjekt skapas. 2 Swingsystemet anropar en metod på de lyssnarobjekt som är knutna till komponenten. Det är i denna metod man skriver vad som ska hända när användaren klickar på knappen. 1 Ett lyssnarobjekt är ett objekt av en klass som implementerar ett lyssnar-interface. 2 Det finns flera sådana interface. T.ex. ActionListener, som används för att ta hand om knapptryckningar. 3 Olika lyssnarobjekt kan knytas till olika komponenter. 4 Alla klasser som har med händelser att göra finns i paketet java.awt.event. PFK (Föreläsning 7) VT 2015 15 / 55 PFK (Föreläsning 7) VT 2015 16 / 55
Interfacet ActionListener Händelsehantering Sammanfattning public interface ActionListener { void actionperformed(actionevent e); Vid knapptryckning skapas ett händelseobjekt av typen ActionEvent. Metoden actionpeformed anropas på den (de) lyssnare som är knutna till klassen. Parametern e refererar till händelseobjektet. Man knyter lyssnar-objektet till knappen genom att anropa metoden addactionlistener på knappen. För att få någonting att hända när användaren klickat på en knapp måste man: Låta någon klass implementera interfacet ActionListener. I metoden actionperformed skriver man det man vill ska hända när användaren klickar på knappen. Koppla lyssnar-objektet till knappen genom att anropa metoden addactionlistener. Jfr. tidningsprenumeration. Man kan göra liknelsen att lyssnar-objektet prenumererar på händelsen knappen trycks ned. PFK (Föreläsning 7) VT 2015 17 / 55 PFK (Föreläsning 7) VT 2015 18 / 55 Exempel på händelsehantering Exempel på händelsehantering Alternativ 1 klassen CounterView som lyssnare import javax.swing.*; import java.awt.event*; public class CounterView implements ActionListener { private int yescount; private JLabel label; public CounterView() { // Se nästa bild yescount++; label.settext("number yes: " + yescount); PFK (Föreläsning 7) VT 2015 19 / 55 public static void main(string[] args) { new CounterView(); PFK (Föreläsning 7) VT 2015 20 / 55
Exempel på händelsehantering Klassen CounterView som lyssnare, konstruktor Exempel på händelsehantering Alternativ 2 inre lyssnarklass public CounterView() { yescount = 0; JFrame frame = new JFrame("Counter"); JPanel panel = new JPanel(); frame.add(panel); JButton button = new JButton("Yes"); panel.add(button); button.addactionlistener(this); label = new JLabel("Number yes: " + yescount); panel.add(label); PFK (Föreläsning 7) VT 2015 21 / 55 Anonym klass (överkurs) Ett ytterligare alternativ är att låta lyssnarklassen vara en anonym klass (en klass utan namn): public class CounterView { private int yescount; private JLabel label; public CounterView() { button.addactionlistener(new ActionListener() { yescount++; label.settext("number yes: " + yescount); ); PFK (Föreläsning 7) VT 2015 23 / 55 public class CounterView { private int yescount; private JLabel label; public CounterView() { button.addactionlistener(new YesButtonListener()); private class YesButtonListener implements ActionListener { yescount++; label.settext("number yes: " + yescount); PFK (Föreläsning 7) VT 2015 22 / 55 Händelsehantering - flera komponenter Man kan ha flera komponenter som genererar händelser. T.ex. flera knappar, knappar och textfält, menyer,... Olika saker ska hända beroende på vilken av komponenterna som genererade händelsen. Detta kan man lösa på olika sätt: Låt ett lyssnarobjekt ta hand om alla händelserna. Då måste detta kunna identifiera vilken komponent händelsen är förknippad med (t ex med hjälp av ActionEvent-objektet). Låt varje komponent ha sitt eget lyssnarobjekt. PFK (Föreläsning 7) VT 2015 24 / 55
Exempel på händelsehantering flera knappar Lösning 1: en lyssnare för alla komponenter (använd ej) Undvik sådana här instance-of -lösningar: public class CountersView implements ActionListener { private int yescount, nocount; private JLabel yeslabel, nolabel; private JButton yesbutton, nobutton; if (e.getsource() == yesbutton){ yescount++; yeslabel.settext("number yes: " + yescount); if (e.getsource() == nobutton){ nocount++; nolabel.settext("number no: " + nocount); PFK (Föreläsning 7) VT 2015 25 / 55 Lösning 2: bättre alternativ, en lyssnare per komponent Inre klasser PFK (Föreläsning 7) VT 2015 26 / 55 Lösning 2: en lyssnare per komponent Anrop av addactionlistener Deklarera en lyssnar -klass per knapp. Dessa klasser deklareras som inre klasser i CountersView: private class YesButtonListener implements ActionListener { yescount++; yeslabel.settext("number yes: " + yescount); private class NoButtonListener implements ActionListener { nocount++; nolabel.settext("number no: " + nocount); I konstruktorn knyter vi lyssnare till knappar med: yesbutton.addactionlistener(new YesButtonListener()); nobutton.addactionlistener(new NoButtonListener()); PFK (Föreläsning 7) VT 2015 27 / 55 PFK (Föreläsning 7) VT 2015 28 / 55
Lösning 2: en lyssnare per komponent Kommentarer Lösning 3 - modell-vy I lösningen slipper vi nu undersöka vilken knapp som användaren klickat på. Det blir enklare att lägga till en komponent. Men men det blir många små lyssnarklasser om man har många komponenter. De lösningar vi visat separerar inte det grafiska användargränssnittet (vyn) från modellen. Det är önskvärt att separera dessa vyn kan man ofta vilja ändra medan modellen ligger fast. En tredje lösning, som löser problemen på föregående bild; Vi separerar modell och vy. Vi låter varje komponent lyssna på sig själv genom att skapa subklasser till motsvarande komponent i Swing. Detaljer finns på de följande bilderna. En ännu bättre lösning finns alltså (se följande bilder). PFK (Föreläsning 7) VT 2015 29 / 55 PFK (Föreläsning 7) VT 2015 30 / 55 Lösning 3: modellen Klassen Counters med de båda räknarna Lösning 3: vyn Klassen CountersView public class Counters { private int yescount; private int nocount; public Counters() { yescount = 0; nocount = 0; public void incrementyescount() { yescount++; public void incrementnocount() { nocount++; public class CountersView { private Counters counters; private JLabel yeslabel, nolabel; public CountersView(Counters counters) { this.counters = counters; skapa fönster, knappar och etiketter public void updateyeslabel() { yeslabel.settext("number yes: " + counters.getyescount()); public int getyescount() { return yescount; public int getnocount() { return nocount; public void updatenolabel() { nolabel.settext("number no: " + counters.getnocount()); PFK (Föreläsning 7) VT 2015 31 / 55 PFK (Föreläsning 7) VT 2015 32 / 55
Lösning 3: vyn Klassen CountersView, konstruktor Lösning 3: vyn Klassen CountersView, konstruktor, forts public CountersView(Counters counters) { this.counters = counters; JFrame frame = new JFrame("Counters"); JPanel yespanel = new JPanel(); frame.add(yespanel, BorderLayout.NORTH); YesButton yesbutton = new YesButton(counters, this); yespanel.add(yesbutton); yeslabel = new JLabel(); updateyeslabel(); yespanel.add(yeslabel); JPanel nopanel = new JPanel(); frame.add(nopanel, BorderLayout.SOUTH); NoButton nobutton = new NoButton(counters, this); nopanel.add(nobutton); nolabel = new JLabel(); updatenolabel(); nopanel.add(nolabel); PFK (Föreläsning 7) VT 2015 33 / 55 PFK (Föreläsning 7) VT 2015 34 / 55 Lösning 3: knappar som lyssnar på sig själva Klassen YesButton Lösning 3: huvudprogram Klassen Main public class YesButton extends JButton implements ActionListener { private Counters counters; private CountersView view; public YesButton(Counters counters, CountersView view) { super("yes!"); this.counters = counters; this.view = view; addactionlistener(this); counters.incrementyescount(); view.updateyeslabel(); Klassen NoButton ser ut på motsvarande sätt. PFK (Föreläsning 7) VT 2015 35 / 55 Modellen och vyn kopplas ihop i main-metoden: public class Main { public static void main(string[] args) { Counters counters = new Counters(); new CountersView(counters); PFK (Föreläsning 7) VT 2015 36 / 55
Ytterligare förbättringar Fler komponenter JMenuBar, JMenu, JMenuItem Knappklasserna, YesButton och NoButton, innehåller nästan likadan kod. Duplicerad kod är någonting man bör undvika. De båda knappklasserna skulle därför kunna slås ihop till en klass. Det behövs då en parameter till knapp-klassens konstruktor som beskriver vilken typ av knapp det är (Yes No, Neutral, ). För detta kan man använda en Enum-typ. Se gärna på ett exempel som finns att hämta på kursens hemsida. Menyer För att välja mellan alternativ som kan grupperas i en eller flera menyer. Händelsehantering analogt med knappar. Kommer att användas på laboration 3 och 6. Anm. Att ändra programmet utan att ändra programmets funktion kallas refaktorisering. Syftet med refaktoriseringen är att programkoden ska bli mer lättläst och lättare att underhålla. JMenu JMenuItem JMenuBar PFK (Föreläsning 7) VT 2015 37 / 55 PFK (Föreläsning 7) VT 2015 38 / 55 Fler komponenter JCheckBox, JRadioButton, JComboBox Fler komponenter JTextField, JTextArea JCheckBox För att välja ett eller flera alternativ. JRadioButton För att välja bland alternativ men bara ett alternativ för varje grupp av radioknappar kan vara valt. JComboBox Kombinerar en knapp (eller ett textfält) med en drop-down-lista. För att göra ett val bland en lista av alternativ JTextField Ett editerbart textfält en rad. När användare skriver in radslutstecken genereras ActionEvent. textfield.gettext() returnerar den sträng som skrivits i fältet. JTextArea Flera rader. Kan sättas uneditable och användas för att visa flera rader text. Kan vara editable och användas för att skriva in flera rader text. Radslut genererar ActionEvent. textarea.gettext() returnerar det som skrivits (i form av en sträng). PFK (Föreläsning 7) VT 2015 39 / 55 PFK (Föreläsning 7) VT 2015 40 / 55
Fler komponenter dialogrutor Exempel import javax.swing.joptionpane; public class SquareRoot { public static void main(string[] args) { String str = JOptionPane.showInputDialog("Enter a number"); double n = Double.valueOf(str); double sqrroot = Math.sqrt(n); JOptionPane.showMessageDialog(null, sqrroot); Fler komponenter dialogrutor Förbättrat exempel med kontroll av indata public class SquareRoot { public static void main(string[] args) { String s = JOptionPane.showInputDialog("Ange ett tal: "); if (s!= null) { double n = 0; try { n = Double.valueOf(s); double sqrroot = Math.sqrt(n); JOptionPane.showMessageDialog(null, sqrroot, "Kvadratrot", JOptionPane.PLAIN_MESSAGE); catch (NumberFormatException e) { JOptionPane.showMessageDialog(null, "Felaktiga tecken i indata.", "Kvadratrot", JOptionPane.ERROR_MESSAGE); PFK (Föreläsning 7) VT 2015 41 / 55 PFK (Föreläsning 7) VT 2015 42 / 55 Fler komponenter Listor ListModel och JList För att visa en lista i en vy finns klassen JList. Till en JList kopplas modellen av listan av typ ListModel. ListModel är ett interface. Det finns färdiga implementeringar av interfacet t.ex. DefaultListModel. Alla modifieringar av listan (modellen) görs via operationer på den modell som kopplas till vyn. Dessa modifieringar visas automatiskt i vyn av listan. Det finns operationer för att ta reda på vad som är markerat i vyn. Exempel: DefaultListModel listmodel = new DefaultListModel(); listmodel.addelement("ett"); listmodel.addelement("två"); listmodel.addelement("tre"); list = new JList(listModel); PFK (Föreläsning 7) VT 2015 43 / 55 Omritning av komponenter Komponenter ritas automatiskt om av systemen i vissa situationer. När de för första gången görs synliga. När storleken ändras (genom att användaren ändrar storlek på fönstret). Ibland vill man kunna begära omritning i programmet. När t.ex. applikationsprogrammet vill lägga till eller flytta på en komponent. Omritning begärs genom att man anropar metoden repaint(). PFK (Föreläsning 7) VT 2015 44 / 55
Grafik Grafik Man kan enkelt rita figurer som linjer, cirklar, rektanglar... En JPanel kan användas som rityta. Oftast inför man en subklass till JPanel. JPanel är en komponent (ärver från JComponent). I klassen JComponent finns metoden paint(graphics g). Det är denna metod som används av systemet för att rita om komponenten. paint anropar metoderna paintcomponent, paintborder och paintchildren. Genom att överskugga metoden paintcomponent i subklassen kan man bestämma vad som skall ritas. Parametern g till paintcomponent (och paint) är av typen Graphics. Ett Graphics-objekt kan liknas vid en penna. Med hjälp av pennan kan vi rita linjer, cirklar, rektanglar m.m.: g.drawline(20, 50, 20, 150); g.filloval(50, 100, 200, 200); Vi kan också ändra pennans tillstånd: g.setcolor(color.green); PFK (Föreläsning 7) VT 2015 45 / 55 PFK (Föreläsning 7) VT 2015 46 / 55 Grafik exempel Klassen Circle En grön cirkel på en panel flyttas när man trycker på en knapp. Grafik exempel Ritfönstret subklass till JPanel public class CirclePanel extends JPanel { Circle c; public CirclePanel(Circle c) { super(); setpreferredsize(new Dimension(400,400)); this.c = c; En cirkel representeras av en klass Circle med följande metoder: public Circle(int x, int y, int radius); public void move(int dx, int dy); // flyttar mittpunkten public int getx(); // mittpunktens x-koordinat public int gety(); // mittpunktens y-koordinat public int getradius(); PFK (Föreläsning 7) VT 2015 47 / 55 public void movebuttonpressed() { c.move(10, 10); repaint(); public void paintcomponent(graphics g) { super.paintcomponent(g); g.setcolor(color.green); g.filloval(c.getx(),c.gety(),c.getradius(),c.getradius()); PFK (Föreläsning 7) VT 2015 48 / 55
Grafik exempel Lyssnarklassen public class MoveButton extends JButton implements ActionListener { CirclePanel cp; public MoveButton(CirclePanel cp) { super("move"); this.cp = cp; addactionlistener(this); cp.movebuttonpressed(); PFK (Föreläsning 7) VT 2015 49 / 55 Grafik exempel Klassen GUI public class GUI { public GUI() { JFrame frame = new JFrame("Circle example"); Circle c = new Circle(50,50,100); CirclePanel cp = new CirclePanel(c); frame.add(borderlayout.center, cp); JPanel buttonpanel = new JPanel(); buttonpanel.add(new MoveButton(cp)); frame.add(borderlayout.south,buttonpanel); public static void main(string[] args) { new GUI(); PFK (Föreläsning 7) VT 2015 50 / 55 Grafik exempel Kommentarer Mushändelser Utan anropet av setpreferredsize blir fönstret mycket litet men kan naturligtvis göras större av användaren. Man bör inte anropa metoden paintcomponent direkt i sina program: paint (och därmed paintcomponent) anropas av systemet vid behov. Om applikationsprogrammet behöver göra omritning ska man anropa metoden repaint (som i sin tur anropar paint). När man skuggar paintcomponent bör man inleda med ett anrop super.paintcomponent(g). Annars ritas inte bakgrunden om. Då musen används genereras händelser av typen MouseEvent. Det inträffar då en musknapp trycks, ner, släpps, musen rör sig med en knapp nedtryckt eller rör sig utan att knapp är nertryckt.... Händelsen innehåller information om vad det var som gjordes. För att lyssna på mushändelser på en komponent kan man knyta olika typer av lyssnare till komponenten. En lyssnare av typen MouseListener lyssnar på knapphändelser. En lyssnare av typen MouseMotionListner lyssnar på händelser som orsakas av att man flyttar musen. PFK (Föreläsning 7) VT 2015 51 / 55 PFK (Föreläsning 7) VT 2015 52 / 55
Mushändelser Interfacen MouseListener, MouseMotionListener public interface MouseListener { /** Anropas då man klickat på musen. */ void mouseclicked(mouseevent e); Mushändelser Exempel Om man vill att en komponent (t.ex en panel) skall lyssna på musknappshändelser gör man så här: /** Anropas då man trycker ner en musknapp. */ void mousepressed(mouseevent e); /** Anropas då en nedtryckt musknapp släpps. */ void mousereleased(mouseevent e); public class DrawingPanel extends JPanel implements MouseListener { public DrawingPanel() { super(); addmouselistener(this); public interface MouseMotionListener { /** Anropas då man trycker på knapp och rör musen. */ void mousedragged(mouseevent e); /** Anropas man rör musen utan nedtryckt knapp. */ void mousemoved(mouseevent e); PFK (Föreläsning 7) VT 2015 53 / 55 // implementera här alla metoder i interfacet MouseListener // skugga paintcomponent för att rita till följd av // mushändelser PFK (Föreläsning 7) VT 2015 54 / 55 Mushändelser Om man inte använder alla händelser Ofta vill man bara lyssna på vissa typer av händelser. Då implementerar man de metoder i interfacet som motsvarar dessa händelser och låter övriga ha tomma metodkroppar. Man kan istället använda klasserna MouseAdapter och MouseMotionAdapter. Dessa klasser är abstrakta klasser som implementerar interfacen MouseListener och/eller MouseMotionListener genom att implementera metoder som ingenting gör. Man kan då skriva egna lyssnarklasser som ärver från MouseAdapter eller MouseMotionAdapter och bara skugga de metoder man är intresserad av. Exempel finns i läroboken. PFK (Föreläsning 7) VT 2015 55 / 55