Java: Föreläsning 11

Av Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se). Senaste ändring 1 december 2003.

Innehåll i föreläsning 11

Ännu en Swing-komponent: JTabbedPane

Första fliken   Andra fliken   Tredje fliken

Man skapar en instans av JTabbedDemo, och lägger till flikar med metoden addTab.

Programmet JTabbedDemo.java:

// Efter Skansholm: Java direkt med Swing

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

public class JTabbedDemo extends JFrame {
    private JButton b1 = new JButton("1");
    private JButton b2 = new JButton("2");
    private JButton b3 = new JButton("3");
    private JButton b4 = new JButton("4");
    private JTextArea a = new JTextArea("En arbetsyta");
    private JLabel bl = new JLabel(new ImageIcon("blommor.jpg"));

    public JTabbedDemo() {
        super("JTabbedDemo");
        Container cp = getContentPane();
        JTabbedPane tp = new JTabbedPane();
        cp.add(tp);

        // Kort 1
        JPanel kort1 = new JPanel();
        tp.addTab("Spel", kort1);
        kort1.setLayout(new GridLayout(2, 2));
        kort1.add(b1);
        kort1.add(b2);
        kort1.add(b3);
        kort1.add(b4);

        // Kort 2
        JScrollPane kort2 = new JScrollPane(a);
        tp.addTab("Arbete", new ImageIcon("hammer.gif"), kort2);

        // Kort 3
        JScrollPane kort3 = new JScrollPane(bl);
        tp.addTab("Blomma", null, kort3, "Visar påskliljor");

        setSize(225, 250);
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main (String[] arg) {
        JTabbedDemo j = new JTabbedDemo();
    }
}

Mer om ritande: mushändelser

Tips om läsning hos Sun: How to Write a Mouse-Motion Listener ("http://java.sun.com/docs/books/tutorial/uiswing/events/mousemotionlistener.html")

Musexempel1: att ta hand om musflyttningar

Jämför med JButton och gränssnittet ActionListener:

För musflyttningar har vi gränssnittet MouseMotionListener: (Musklick tar vi senare.)

Programmet Musexempel1.java:

// Musexempel1.java

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

class Muspanel extends JPanel {
    public Muspanel() {
        super(new FlowLayout());

        JPanel dumpanel1 = new JPanel();
        dumpanel1.setBackground(new Color(0.98f, 0.97f, 0.85f));
        Dimension size = new Dimension(100, 100);
        dumpanel1.setPreferredSize(size);
        dumpanel1.setMinimumSize(size);
        dumpanel1.setBorder(new LineBorder(Color.red, 2));
        add(dumpanel1);

        BlankArea dumpanel2 = new BlankArea();
        add(dumpanel2);

        JTextArea textarea = new JTextArea();
        textarea.setEditable(false);
        JScrollPane scrollPane = new JScrollPane(textarea,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scrollPane.setPreferredSize(new Dimension(300, 75));
        add(scrollPane);

        Muslyssnare lyssnare = new Muslyssnare(textarea);
        dumpanel1.addMouseMotionListener(lyssnare);
        dumpanel2.addMouseMotionListener(lyssnare);

        setPreferredSize(new Dimension(400, 200));
    } // Muspanel

    private class Muslyssnare implements MouseMotionListener {
        private JTextArea textarea;

        public Muslyssnare(JTextArea textarea) {
            this.textarea = textarea;
        }

        public void mouseMoved(MouseEvent e) {
            log("Flyttning", e);
        }

        public void mouseDragged(MouseEvent e) {
            log("Dragning", e);
        }

        private void log(String eventDescription, MouseEvent e) {
            textarea.append(eventDescription +
                            " (" + e.getX() + "," + e.getY() + ")" +
                            " på " +
                            e.getComponent().getClass().getName() + "\n");
            textarea.setCaretPosition(textarea.getDocument().getLength());
        }
    } // class Muslyssnare
} // class Muspanel

class Musfönster extends JFrame {
    public Musfönster() {
        super("Musfönster");
        getContentPane().add(new Muspanel());
        pack();
    }
} // class Musfönster

public class Musexempel1 {
    public static void main(String[] args) {
        Musfönster m = new Musfönster();
        m.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        m.setVisible(true);
    }
} // class Musexempel1

Musklick

Tips om läsning hos Sun: How to Write a Mouse Listener ("http://java.sun.com/docs/books/tutorial/uiswing/events/mouselistener.html")

För musklick har vi gränssnittet MouseListener:

Musexempel2: att ta hand om musklick

Ur programmet Musexempel2.java:

        Muslyssnare lyssnare = new Muslyssnare(textarea);
        dumpanel1.addMouseListener(lyssnare);
        dumpanel2.addMouseListener(lyssnare);
Mer ur programmet Musexempel2.java:
    private class Muslyssnare implements MouseListener {
        private JTextArea textarea;

        public Muslyssnare(JTextArea textarea) {
            this.textarea = textarea;
        }

        public void mousePressed(MouseEvent e) {
            log("Musen nertryckt; antal klick = " +
                e.getClickCount(), e);
        }

        public void mouseReleased(MouseEvent e) {
            log("Musen släppt; antal klick = " +
                + e.getClickCount(), e);
        }

        public void mouseEntered(MouseEvent e) {
            log("Musen kom in", e);
        }

        public void mouseExited(MouseEvent e) {
            log("Musen lämnade", e);
        }

        public void mouseClicked(MouseEvent e) {
            log("Musklick; antal klick = " +
                e.getClickCount() + ")", e);
        }

        private void log(String eventDescription, MouseEvent e) {
            textarea.append(eventDescription +
                            " (" + e.getX() + "," + e.getY() + ")" +
                            " på " +
                            e.getComponent().getClass().getName() + "\n");
            textarea.setCaretPosition(textarea.getDocument().getLength());
        }
    } // class Muslyssnare

Adapterklasser

Man måste alltid implementera samtliga metoder i ett gränssnitt.
Jobbigt, om det till exempel bara är musklick (metoden mouseClicked) vi är intresserade av.

Därför finns en adapterklass som heter MouseAdapter.
Den har alla metoderna i gränssnittet, men de är tomma och gör inget:

    public void mousePressed(MouseEvent e) {

    }
Ur programmet Musexempel3.java:
    private class Muslyssnare extends MouseAdapter implements MouseListener {
        private JTextArea textarea;

        public Muslyssnare(JTextArea textarea) {
            this.textarea = textarea;
        }

        public void mouseClicked(MouseEvent e) {
            log("Musklick; antal klick = " +
                e.getClickCount() + ")", e);
        }

        private void log(String eventDescription, MouseEvent e) {
            textarea.append(eventDescription +
                            " (" + e.getX() + "," + e.getY() + ")" +
                            " på " +
                            e.getComponent().getClass().getName() + "\n");
            textarea.setCaretPosition(textarea.getDocument().getLength());
        }
    } // class Muslyssnare

Enklare än trådar: timerklassen java.util.Timer

Thread programming can be tricky. Whenever possible, you should use high-level thread API such as the java.util.Timer class introduced in version 1.3 of the Java platform. Timer and its companion class, TimerTask, are useful when your program must perform a task repeatedly or after a delay.

Tips om läsning hos Sun - om användning av timerklassen java.util.Timer i stället för trådar: Using the Timer and TimerTask Classes ("http://java.sun.com/docs/books/tutorial/essential/threads/timer.html")

Kom ihåg klassen Presenter.java från föreläsning 4.

Man kan göra samma sak med en timer, i stället för att skapa egna trådar. Klassen PresenterTask.java:

Här var det fel version av programmet tidigare.

import java.util.Timer;
import java.util.TimerTask;

public class PresenterTask extends TimerTask {
    private String message;
    private double sleepTime;
    private int steps;

    public PresenterTask(String message, double sleepTime) {
        this.message = message;
        this.sleepTime = sleepTime;
        this.steps = 0;
        Timer timer = new Timer();
        timer.schedule(this, 0, (int)(sleepTime * 1000));
    }

    public void run() {
        for (int i = 0; i < this.steps; ++i)
            System.out.print(" ");
        ++this.steps;
        System.out.println(this.message);
    } // run

    public static void main(String[] args) {
        PresenterTask t1 = new PresenterTask("Turbo", 0.1);
        PresenterTask t2 = new PresenterTask("Svensson", 0.2);
        PresenterTask t3 = new PresenterTask("Skalman", 0.3);
    } // main
} // class PresenterTask
Men tänk på att timern internt har sin egen tråd:

Man kan också ange en tidpunkt. Från http://java.sun.com/docs/books/tutorial/essential/threads/timer.html:

//Get the Date corresponding to 11:01:00 pm today.
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 1);
calendar.set(Calendar.SECOND, 0);
Date time = calendar.getTime();

timer = new Timer();
timer.schedule(new RemindTask(), time);
En timer kan tas bort med metoden cancel.

GUI-timers: javax.swing.Timer

Om man använder Swing-timerklassen javax.swing.Timer, kommer dess kod att köras i Swing-tråden. Man slipper invokeLater.

Exempel på användning: tooltips.

Tips om läsning hos Sun:

Rörlig grafik

Kom ihåg klassen Cirkeldiagram från föreläsning 10. Vi ska göra ett animerat cirkeldiagram genom att ha en tråd som hela tiden anropar setVärde.

Ett animerat cirkeldiagram

(Jaja, bilden står still. Men prova den här i stället.)

Programmet Grafikdemo6.java:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
 
class Cirkeldiagram extends JPanel {
    private int värde;
    private int max;

    public Cirkeldiagram(int värde, int max) {
        if (max <= 0)
            throw new IllegalArgumentException("max = " + max +
                                               ", ska vara > 0");
        if (värde < 0 || värde > max)
            throw new IllegalArgumentException("värde = " + värde +
                                               ", ska vara 0.." + max);
        this.värde = värde;
        this.max = max;
        setBackground(Color.white);
    }

    public Cirkeldiagram(int värde) { this(värde, 100); }
    public Cirkeldiagram() { this(0, 100); }

    public void setVärde(int värde) {
        if (värde < 0 || värde > max)
            throw new IllegalArgumentException("värde = " + värde +
                                               ", ska vara 0.." + max);
        this.värde = värde;
        repaint();
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.blue);
        Insets i = getInsets();
        int bredd = getWidth() - i.left - i.right;
        int höjd = getHeight() - i.top - i.bottom;
        int diameter = Math.min(bredd, höjd);
        int x = i.left + (bredd - diameter) / 2;
        int y = i.top + (höjd - diameter) / 2;
        g.drawOval(x, y, diameter, diameter);
        double andel = (double)värde / max;
        int vinkelandel = (int)(andel * 360 + 0.5);
        g.fillArc(x, y, diameter, diameter, 90, -vinkelandel);
    }
} // class Cirkeldiagram

class Grafikfönster extends JFrame {
    private Cirkeldiagram cd = new Cirkeldiagram(0, 100);
    JSlider s = new JSlider(0, 100, 0);

    public Grafikfönster(String titel) {
        super(titel);
        Container cp = getContentPane();
        cp.add(cd);
    }

    public Cirkeldiagram cirkeldiagram() {
        return cd;
    }
} // class Grafikfönster

class Animeringstråd extends Thread {
    Cirkeldiagram cirkeldiagram;
    int värde = 0;

    public Animeringstråd(Cirkeldiagram cd) {
        this.cirkeldiagram = cd;
        start();
    }

    public void run() {
        while (true) {
            ++värde;
            if (värde == 100)
                värde = 0;
            // cirkeldiagram.setVärde(värde);
            SwingUtilities.invokeLater(new Runnable() {
                    public void run() {             
                        cirkeldiagram.setVärde(värde);
                    }
                });
            try {
                Thread.sleep(100);
            }
            catch (InterruptedException e) {
                System.out.println("Oj! Sömnen avbruten!");
            }
        }
    }
} // class Animeringstråd

public class Grafikdemo6 {
    public static void main(String[] args) {
        Grafikfönster g = new Grafikfönster("Grafikdemo6");
        g.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        g.setSize(300, 300);
        g.setVisible(true);
        Animeringstråd t = new Animeringstråd(g.cirkeldiagram());
    } // main
} // class Grafikdemo6

Rörlig grafik med javax.swing.Timer

Man kan byta ut tråden mot en Swing-timer. Ur programmet Grafikdemo7.java:
class TimerHandler implements ActionListener {
    private Cirkeldiagram cirkeldiagram;
    private int värde = 0;

    public TimerHandler(Cirkeldiagram cd) {
        cirkeldiagram = cd;
    }

    public void actionPerformed(ActionEvent e) {
        ++värde;
        if (värde == 100)
            värde = 0;
        cirkeldiagram.setVärde(värde);
    } // run
} // class TimerHandler

public class Grafikdemo7 {
    public static void main(String[] args) {
        Grafikfönster g = new Grafikfönster("Grafikdemo7");
        g.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        g.setSize(300, 300);
        g.setVisible(true);
        TimerHandler th = new TimerHandler(g.cirkeldiagram());
        javax.swing.Timer timer = new javax.swing.Timer(100, th);
        timer.start();
    } // main
} // class Grafikdemo7

Några tutorial-tips

(Nej, alla de här ingår inte i tentan. De är bara med som tips för den som vill läsa mer.)

java.sun.com finns, förutom API:specen och andra manualer, också en stor mängd tutorials ("handledningar" på svenska), med kodexempel.

Här är några om Swing:

  1. The Swing Tutorial. Hela Swing från början.
  2. Implementing a Custom Component. Ungefär som vår klass Cirkeldiagram.
  3. How to Write a Mouse-Motion Listener. Det har vi gjort ovan.
  4. How to Write a Mouse-Wheel Listener
  5. Lesson: Laying Out Components Within a Container. Om layout-managers.
  6. A Visual Guide to Layout Managers. Exempel, med bilder, på användning av layout-managers.
  7. How to Use Scroll Panes. Vi har sett några sådana exempel, bland annat i Musexempel1 idag.
  8. How to Use Drag and Drop and Data Transfer
Här är några andra:
  1. Threads: Doing Two or More Tasks At Once
  2. Using the Timer and TimerTask Classes
  3. Writing the Server Side of a Socket
  4. JDBC(TM) Database Access
  5. Full-Screen Exclusive Mode API
  6. Sound
  7. Providing Your Own Security Manager