Introduzione a Java ed Eclipse - 3

Esempio di JPanel clorato

  • Veniamo al codice. Questa volta ci serviranno tre classi: quella con il main, quella con derivata da JFrame e quella derivata da JPanel.

Esempio di JPanel clorato

  • Cominciamo da quella nuova. Inserire nel file MyEmptyPanel.java il seguente codice:
    package framepaneltest;

    import java.awt.Color;
    import java.awt.Dimension;

    import javax.swing.JPanel;

    public class MyEmptyPanel extends JPanel {
    private static final int WIDTH=400;
    private static final int HEIGHT=300;

    private static final long serialVersionUID = 1L;

    public MyEmptyPanel() {
    setPreferredSize(new Dimension(WIDTH, HEIGHT));
    setBackground(Color.yellow);
    }
    }

Esempio di JPanel clorato

  • Passiamo poi alla finestra. Nel file MyFramePanel.java inserire:
    package framepaneltest;

    import java.awt.BorderLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;

    import javax.swing.JButton;
    import javax.swing.JFrame;

    public class MyFramePanel extends JFrame implements ActionListener {
    private JButton fine;
    private MyEmptyPanel disegno;

    private static final long serialVersionUID = 1L;

    public MyFramePanel () {
    setTitle("Finestra con area con disegno");

    setLayout(new BorderLayout());

    fine = new JButton("Fine");
    disegno = new MyEmptyPanel();

    fine.addActionListener(this);

    add(disegno, BorderLayout.CENTER);

    add(fine, BorderLayout.SOUTH);

    pack();
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
    if (evt.getSource() == fine) {
    dispose();
    }
    }
    }

Esempio di JPanel clorato

  • Anche questa volta non riporto il main, in quanto basta cambiare il nome della classe.

Per disegnare utilizziamo un JPanel

  • Possiamo disegnare ovunque nella finestra, se associamo l disegno ad un qualunque componente
  • Normalmente però vorremo che il nostro disegno compaia in un pannello, attorno al quale ci saranno i comandi.
  • Il componente Java che implementa un pannello, e che abbiamo già utilizzato per disporre i vari componenti dell'interfaccia, è JPanel. Dovremo derivare una calsse da JPanel ed in quella classe scrivere il codice per il disegno.

Per disegnare utilizziamo un JPanel

  • Per assegnare la dimensione al pannello dovremo usare il metodo void setPreferredSize(Dimension d); al quale passeremo un'istanza di Dimension creata con le dimensioni che vogliamo.
  • Naturalmente un JPanel vuoto ha dimensione nulla. Dovremo assegnargli una dimensione in modo che la pack(); non lo riduca ad una dimensione minima.
  • Per caratterizzare il pannello gli daremo poi un colore di sfondo con il metodo void setBackgroundColor(Color c); al quale potremo passare uno dei colori predefiniti della clase Color oppure un nuovo Color creato con le quantità di rosso, verde e blu che vogliamo.

Per disegnare utilizziamo un JPanel

  • Ovviamente poi dovremo creare un JFrame nel quale inserire la nostra nuova classe

Disegno all'interno di un JPanel al lavoro

Disegno figure e testo in azione - 3

Esempio di JPanel clorato a lavoro

Figure e testo

  • In un JPanel oltre a delle linee colorate possono essere disegnate vari tipi di figure, sia vuote che piene. Prenderemo in esame solo:
    • Rettangoli
    • Ovali/cerchi o spicchi di ovali/cerchi
    • Poligoni
  • Per ognuna di queste figure, possiamo disegnarne il contorno con i metodi drawRect, drawArc o drawPoligon rispettivamente.
  • Possiamo anche disegnarle piene con i metodi fillRect, fillArc o fillPoligon rispettivamente.

Figure e testo

  • I metodi drawRect e fillRect hanno gli stessi paramentri: int x, int y, int larghezza e int altezza.
  • I metodi drawArc e fillArc, oltre agli argomenti di drawRect ci sono l'angolo iniziale e l'angolo dell'arco, entrambi int, espressi in gradi
  • Per quanto riguarda drawPolygon e fillPolygon, dovremo passare due array di int contente uno le x di tutti i vertici e l'altro tutte le y, oltre ad un int con il numero di vertici.

Figure e testo

  • Per scrivere del testo potremo utilizzare il metodo void drawString(String s, int x, int y); dove s è la stringa da stampare e la x e la y sono riferite all'inizio della linea su cui è scritto il testo, e non all'angolo in alto a sinistra.

Disegno figure e testo in azione - 2

Esempio di Disegno figure e testo - il pannello

  • In questo caso nel panello disegneremo una sola figura alla volta. Il pannello presenterà due metodi chiamati public void eseguiAzione1(); e public void eseguiAzione2(); che rispettivamente, passeranno alla prossima o alla precedente figura.
  • Naturalmente ogni volta che si cambia figura occorre richiedere il ridisegno del pannello. Questo si ottiene invocando il metodo public void repaint();

Esempio di Disegno figure e testo - il pannello

  • Nel file MyPanelForme.java scriveremo:
    package frameformetest;

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;

    import javax.swing.JPanel;

    public class MyPanelForme extends JPanel {
    private static final int WIDTH=400;
    private static final int HEIGHT=300;
    private static final int NUMFIORME=7;

    private static final long serialVersionUID = 1L;

    private int forma;

    public MyPanelForme() {
    setPreferredSize(new Dimension(WIDTH, HEIGHT));
    setBackground(new Color(0xff, 0xff, 0xcc));
    forma = 0;
    }

    @Override
    protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    switch(forma) {
    case 0:
    rettangoloVuoto(g);
    break;
    case 1:
    rettangoloPieno(g);
    break;
    case 2:
    tortaVuota(g);
    break;
    case 3:
    tortaPiena(g);
    break;
    case 4:
    poligonoVuoto(g);
    break;
    case 5:
    poligonoPieno(g);
    break;
    case 6:
    testo(g);
    break;
    }
    }

    private void testo(Graphics g) {
    g.drawString("Testo", WIDTH/2, HEIGHT/2);

    }

    private void poligonoPieno(Graphics g) {
    int[] xPoints = {WIDTH/3, 2*WIDTH/3,WIDTH/3 };
    int[] yPoints = {HEIGHT/3, 2*HEIGHT/3, 2*HEIGHT/3 };
    g.fillPolygon(xPoints, yPoints,3);
    }

    private void poligonoVuoto(Graphics g) {
    int[] xPoints = {WIDTH/3, 2*WIDTH/3,WIDTH/3 };
    int[] yPoints = {HEIGHT/3, 2*HEIGHT/3, 2*HEIGHT/3 };
    g.drawPolygon(xPoints, yPoints,3);
    }

    private void tortaPiena(Graphics g) {
    g.fillArc(WIDTH/3, HEIGHT/3, WIDTH/3, HEIGHT/3, 0, 270);
    }

    private void tortaVuota(Graphics g) {
    g.drawArc(WIDTH/3, HEIGHT/3, WIDTH/3, HEIGHT/3, 0, 270);
    }

    private void rettangoloPieno(Graphics g) {
    g.fillRect(WIDTH/3, HEIGHT/3, WIDTH/3, HEIGHT/3);
    }

    private void rettangoloVuoto(Graphics g) {
    g.drawRect(WIDTH/3, HEIGHT/3, WIDTH/3, HEIGHT/3);
    }

    public void eseguiAzione1() {
    if (forma < NUMFIORME - 1) {
    forma++;
    } else {
    forma = 0;
    }
    repaint();
    }

    public void eseguiAzione2() {
    if (forma > 0) {
    forma--;
    } else {
    forma = NUMFIORME - 1;
    }
    repaint();
    }

    }

Disegno all'interno di JPanel

  • Come sempre nel paradigma ad oggetti, non sarà il mondo esterno a disegnare sul pannello, ma un metodo del pannello stesso.
  • Il metodo che disegna nel JPanel è protected void paintComponent(Graphics g);
  • Questo metodo viene chiamato dal sistema ogni volta che è necessario ridisegnare del tutto o in parte la superficie del pannello
  • Per essere sicuri che lo sfondo venga colorato, la prima cosa che dovremo fare dentro a paintComponete sarà richiamare il metodo del padre, con super.paintComponent(g);

Disegno all'interno di JPanel

  • A questo punto potremo scegliere il colore del disegno, utilizzando uno dei colori predefiniti della classe Color, con il metodo p.setColor(Color.red);
  • Potremo alla fine disegnare una linea con il metodo p.drawLine(int x1, int y1, int x2, int y2); che disegna una linea dal punto (x1,y1) al punto (x2,y2)

Disegno figure e testo in azione - 1

Esempio di Disegno figure e testo - La finestra

  • La finestra va modificata per aggiungere due bottoni, per le due azioni. A b1 assceremo il testo Prossimo e a b2 il testo Precedente.

Esempio di Disegno figure e testo - La finestra

  • Nel file MyFrameForme,java scriveremo:
    package frameformetest;

    import java.awt.BorderLayout;
    import java.awt.FlowLayout;
    import java.awt.event.ActionEvent
    import java.awt.event.ActionListener;

    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;

    public class MyFrameForme extends JFrame implements ActionListener {
    private JButton fine;
    private JButton b1;
    private JButton b2;
    private MyPanelForme disegno;

    private static final long serialVersionUID = 1L;

    public MyFrameForme () {
    setTitle("Finestra con area con disegno");

    setLayout(new BorderLayout());

    fine = new JButton("Fine");
    b1 = new JButton("Prossima");
    b2 = new JButton("Precedente");
    disegno = new MyPanelForme();

    fine.addActionListener(this);
    b1.addActionListener(this);
    b2.addActionListener(this);

    add(disegno, BorderLayout.CENTER);

    JPanel bottoni = new JPanel();
    bottoni.setLayout(new FlowLayout());
    bottoni.add(b1);
    bottoni.add(b2);
    bottoni.add(fine);
    add(bottoni, BorderLayout.SOUTH);

    pack();
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
    if (evt.getSource() == b1) {
    disegno.eseguiAzione1();
    } else if (evt.getSource() == b2) {
    disegno.eseguiAzione2();
    } else if (evt.getSource() == fine) {
    dispose();
    }
    }
    }

Esempio di Disegno figure e testo - La finestra

  • Come sempre, nel main c'è solo da modificare il nome della classe caricata, quindi non lo riporto.

Disegno di immagini

  • Dobbiamo passare il nostro pannello ai metodi immagine.getWidth(this); e immagine.getHeight(this); perché l'immagine viene letta in background, quindi viene letto un nuovo pezzo di immagine viene richiamato il metodo public boolean imageUpdate(Image arg0, int infoFlags, int x, int y, int width, int heignt); che noi dobbiamo scrivere.
  • Nel metodo imageUpdate richiameremo il metodo repaint(); per visualizzare l'immagine caricata.
  • Per evitare che il metodo imageUpdate venga richiamato quando l'immagine è completamente caricata, dovremo terminarlo con return (infoFlags & ALLBITS) != ALLBITS;

Disegno di immagini

  • Per finire, nel metodo paintComponent(Graphics g); richiameremo il metodo g.drawImage(immagine, x, y, this); per disegnare l'immagine con l'angolo in alto a sinistra alle coordinate x ed y
  • Utilizzeremo i due metodi public void eseguiAzione1(); e void eseguiAzione2(); per spostare l'immagine in diagonale verso destra o verso sinistra fino a che non incontra il bordo del pannello.

Disegno figure e testo in azione - 4

Caricamento e disegno di immagini

  • Quindi dovremo dichiarare un attributo private Image immagine;, per contenere la nostra immagine
  • Per prima cosa dovremo posizionare nella directory del progetto un'immagine come palla.png che decora questa pagina.
  • Nel costruttore dovremo caricare dentro all'attributo immagine l'immagine che abbiamo preparato, con il metodo statico Image Toolkit.getDefaultToolkit().getImage("palla.png");
  • Per terminare gli oggetti grafici, proviamo a caricare un'immagine nel nostro pannello.

Caricamento e disegno di immagini

  • Potremo scoprire la larghezza dell'immagine traite il metodo immagine.getWidth(this); e la sua altezza con il metodo immagine.getHeight(this);
  • A questi due metodi dobbiamo passare il nostro pannello, del quale abbiamo dichiarato implements ImageObserver.

Disegno figure e testo in azione - 6

Caricamento e disegno Immagini - in azione 2

Disegno all'interno di JPanel

  • Doveremo scrivere nel file MyPanelLine.java il seguente codice:
    package framelinetest;

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;

    import javax.swing.JPanel;

    public class MyPanelLine extends JPanel {
    private static final int WIDTH=400;
    private static final int HEIGHT=300;

    private static final long serialVersionUID = 1L;

    public MyPanelLine() {
    setPreferredSize(new Dimension(WIDTH, HEIGHT));
    setBackground(new Color(0xff, 0xff, 0xcc));
    }

    @Override
    protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(Color.red);
    g.drawLine(0, 0, WIDTH, HEIGHT);
    g.setColor(Color.blue);
    g.drawLine(WIDTH, 0, 0, HEIGHT);
    }

    }

Disegno all'interno di JPanel

  • Nella classe derivata da JFrame dovremo solo cambiare la classe derivata da JPanel, quindi non la riporto qui.
  • Come sempre il main non cambia se non per il nome della classe creata, quindi non riporto nemmeno quello.

Caricamento e disegno Immagini - il codice

  • Nel file MyPanelImmagine.java scriveremo:
    package frameimmagine;

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Image;
    import java.awt.Toolkit;
    import java.awt.image.ImageObserver;

    import javax.swing.JPanel;

    public class MyPanelImmagine extends JPanel implements ImageObserver {
    private static final int WIDTH=400;
    private static final int HEIGHT=300;

    private static final long serialVersionUID = 1L;

    private Image immagine;
    private int x;
    private int y;

    public MyPanelImmagine() {
    setPreferredSize(new Dimension(WIDTH, HEIGHT));
    setBackground(new Color(0xff, 0xff, 0xcc));
    x = WIDTH / 2;
    y = HEIGHT / 2;
    immagine = Toolkit.getDefaultToolkit().getImage("palla.png");
    }

    @Override
    protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawImage(immagine, x - immagine.getWidth(this) / 2,
    y - immagine.getHeight(this) / 2, this);
    }

    public void eseguiAzione1() {
    if (x > immagine.getWidth(this) / 2 &&
    y > immagine.getHeight(this) / 2) {
    x--;
    y--;
    }
    repaint();
    }

    public void eseguiAzione2() {
    if (x < WIDTH - immagine.getWidth(this) / 2 &&
    y < HEIGHT - immagine.getHeight(this) / 2) {
    x++;
    y++;
    }
    repaint();
    }

    @Override
    public boolean imageUpdate(Image arg0, int infoFlags, int x, int y,
    int width, int heignt) {
    repaint();
    return (infoFlags & ALLBITS) != ALLBITS;
    }

    }

Caricamento e disegno Immagini - il codice

  • Nel file MyFrameImmagine.java copieremo il contenuto di MyFrameForme, cambiando solo il package, la classe del pannello e le etichette di b1 (sinistra) e b2 (destra)
  • Nel main come al solito cabieremo solo il nome della classe della finestra.

Caricamento e disegno Immagini - in azione 1

Disegno figure e testo in azione - 5

Disegno figure e testo in azione - 7

Caricamento e disegno Immagini - in azione 3

Animare la palla - in funzione 2

Animare la palla - Il metodo run e stop

  • Dovremo poi scrivere il metodo public void run(); che esegua il movimento.
  • Questo metodo dovrà fare un ciclo che modifichi le coordinate della palla, ad una velocità costante (diciamo 20 volte al secondo) e calcoli il rimbalzo sui bordi.
  • Occorre notare che il metodo public void run(); non viene invocato solo dal thread, ma anche dal processo principale.
  • Inoltre occorre prevedere di interrompere il ciclo quando premiamo fine nella finestra, altrimenti il programma non termina.

Animare la palla - Il metodo run e stop

  • Per questo motivo quindi, il nostro ciclo dovrà verificare che il thread corrente corrisponda ad animazione: while (Thread.currentThread() == animazione)
  • Per interrompere il ciclo quindi, nel metodo public void stop(); non ci servirà altro che mettere a null l'attributo animazione e il ciclo terminerà.
  • Per introdurre una pausa che regoli la velocità del ciclo utilizzeremo il metodo statico Thread.sleep(long millis);

Animare la palla - Il metodo run e stop

  • Questo metodo può inviare un'eccezione se la pausa viene interrotta anzitemtpo. Inseriremo quindi l'invocazione in un blocco try/catch dove la catch non farà alcuna azione.

Animare la palla

  • Per animare la palla occorre modificare le sue coordinate ogni tot.
  • Se lo facessimo nella paintComponent, i pulsanti non funzionerebbero più perché vengono controllati tra una invocazione paintComponent e la successiva
  • La soluzione è fare queste operazioni in background, utilizzando quindi un thread.
  • Per creare un thread dovremo per prima cosa definire un attributo private Thread animazione;

Animare la palla

  • Dovremo aggiungere implements Runnable alla classe in modo che il thread possa richiamare il metodo public void run(); della nostra classe in background.
  • Nel costruttore, per prima cosa dovremo creare un nuovo thread, associato alla nostra istanza ed assegnarlo all'attributo animanzione: animazione = new Thread(this);
  • Dovremo poi, sempre nel costruttore, avviare il thread: animazione.start();

Animare la palla - in funzione 1

Animare la palla - il sorgente

  • Nel file MyPanelAnimazione.java scriveremo:
    package frameanimazione;

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Image;
    import java.awt.Toolkit;

    import javax.swing.JPanel;

    public class MyPanelAnimazione extends JPanel implements Runnable {
    private static final int WIDTH=400;
    private static final int HEIGHT=300;

    private static final long serialVersionUID = 1L;

    private Image immagine;
    private int x;
    private int y;
    private int dx;
    private int dy;
    private Thread animazione;

    public MyPanelAnimazione() {
    setPreferredSize(new Dimension(WIDTH, HEIGHT));
    setBackground(new Color(0xff, 0xff, 0xcc));
    x = WIDTH / 2;
    y = HEIGHT / 2;
    dx = 1;
    dy = 1;
    immagine = Toolkit.getDefaultToolkit().getImage("palla.png");
    animazione = new Thread(this);
    animazione.start();
    }

    @Override
    protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawImage(immagine, x - immagine.getWidth(this) / 2,
    y - immagine.getHeight(this) / 2, this);
    }

    public void stop() {
    animazione = null;
    }

    public void eseguiAzione1() {
    dx *= -1;
    }

    public void eseguiAzione2() {
    dy *= -1;
    }

    @Override
    public boolean imageUpdate(Image arg0, int infoFlags, int x, int y,
    int width, int heignt) {
    repaint();
    return (infoFlags & ALLBITS) != ALLBITS;
    }

    @Override
    public void run() {
    while (Thread.currentThread() == animazione) {
    x += dx;
    y += dy;
    if (x < immagine.getWidth(this) / 2) {
    dx = 1;
    } else if (x > WIDTH - immagine.getWidth(this) / 2) {
    dx = -1;
    }
    if (y < immagine.getHeight(this) / 2) {
    dy = 1;
    } else if (y > HEIGHT - immagine.getHeight(this) / 2) {
    dy = -1;
    }
    repaint();
    try {
    Thread.sleep(20);
    } catch (Exception e) {
    }
    }
    }

    }

Animare la palla - il sorgente

  • Nel file MyFrameAnimazione.java copieremo il contenuto di MyFrameImmagine, cambiando solo il package, la classe del pannello e le etichette di b1 (Rimbalza Verticale) e b2 (Rimbalza Orizzontale) ed aggiungere, nella gestione del bottone fine una invocazione del metodo disegno.stop();
  • Nel main come al solito cabieremo solo il nome della classe della finestra.
[any material that should appear in print but not on the slide]