javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen

9 Grafiske programmer

Indhold:

Kapitlet forudsættes i kapitel 10, Appletter, kapitel 11, Grafiske komponenter og kapitel 12, Interfaces.

Forudsætter kapitel 3, Objekter (4, Definition af klasser og 5, Nedarvning er en fordel). Den større opgave forudsætter kapitel 5, Nedarvning.

Vi kan tegne grafik på skærmen ved at skrive en klasse, der arver fra klassen JPanel og definere metoden paintComponent(). Dette metode vil systemet kalde for at få grafikken tegnet på skærmen. Systemet overfører et Graphics-objekt (beskrevet i afsnit 9.1), som vi kan bruge til at tegne med.

I eksemplet nedenfor tegner vi en linje, en fyldt oval og noget tekst med grøn skrift.

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

public class Grafikpanel extends JPanel
{
  public void paintComponent(Graphics g)
  {
    // Herunder referer g til et Graphics-objekt man kan tegne med

    super.paintComponent(g);                // tegn først baggrunden på panelet

    g.drawLine(0,0,50,50);

    g.fillOval(5,10,300,30);

    g.setColor(Color.GREEN);

    g.drawString("Hej grafiske verden!",100,30);

    System.out.println("Der blev tegnet!!");
  }
}

For at følge med i hvornår der bliver tegnet skriver vi Der blev tegnet!! ud når metoden bliver kaldt. For at se grafikken skal vi definere en main()-metode, der opretter et Grafikpanel-objekt og et vindue (JFrame), som viser panelet på skærmen:

import javax.swing.JFrame;

public class BenytGrafikpanel
{
  public static void main(String[] arg)
  {
    Grafikpanel panel = new Grafikpanel();        // opret panelet

    JFrame vindue = new JFrame("Grafikpanel");    // opret et vindue på skærmen
    vindue.add( panel );                          // vis panelet i vinduet

    vindue.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // reagér på luk
    vindue.setSize(350,70);                       // sæt vinduets størrelse
    vindue.setVisible(true);                      // åbn vinduet
  }
}

Der blev tegnet!!

Her ses, hvordan vinduet ser ud på skærmen (under Linux):




Vinduets øverste venstre hjørne er i (0,0) og koordinaterne regnes mod højre og nedad.

9.1 Klassen Graphics

Graphics er beregnet til at tegne grafik (på skærm eller printer). Man skal ikke selv oprette Graphics-objekter med new, i stedet får man givet et "i hånden" af styresystemet. Herunder gengives kun nogle af metoderne – se Javadokumentationen for en komplet liste.

java.awt.Graphics – til tegning af grafik

Metoder

void drawString(String tekst, int x, int y)
tegner en tekst med bundlinjen startende i (x,y).

void drawImage(Image billede, int x, int y, ImageObserver observatør)
tegner et billede med øverste venstre hjørne i (x,y); observatør bør være objektet selv (this).

void drawLine(int x1, int y1, int x2, int y2)
tegner en linje mellem punkterne (x1, y1) og (x2, y2).

void drawRect(int x, int y, int bredde, int højde)
tegner omridset af et rektangel.

void drawRoundRect(int x, int y, int bredde, int højde, int buebredde, int buehøjde)
tegner omridset af et rektangel, der er afrundet i hjørnerne
.

void drawOval(int x, int y, int bredde, int højde)
tegner en oval med øverste venstre hjørne i (x,y). Er bredde==højde, tegnes en cirkel.

void drawArc(int x, int y, int bredde, int højde, int startvinkel, int vinkel)
tegner en del af en oval, men kun buen fra
startvinkel og vinkel grader rundt (mellem 0 og 360).

void drawPolygon(Polygon p)
tegner en polygon
(mangekant) ud fra et Polygon-objekt.

Tilsvarende findes fillRect, fillRoundRect, fillOval, fillArc og fillPolygon.

void clearRect(int x, int y, int bredde, int højde)
udfylder et rektangel med baggrundsfarven.

Rectangle getClipBounds()
giver klipnings-omridset. Kun punkter inden for dette omrids bliver faktisk tegnet, ting uden for omridset bliver beskåret til den del, der er inden for omridset.

void translate(int x, int y)
forskyder koordinatsystemet, sådan at (x,y) bliver (0,0)

void setColor(Color nyFarve)
sætter tegningsfarven til nyFarve. Alt bliver herefter tegnet med denne farve.

Color getColor()
aflæser tegningsfarven.

void setFont(Font nySkrifttype)
sætter skrifttypen til nySkrifttype. Dette påvirker tekster skrevet med drawString() herefter.

Font getFont()
aflæser skrifttypen.

Har man brug for flere faciliteter til tegning af grafik end ovenstående giver, kan man gå over til at bruge Java2D (se avanceret-afsnittet i slutningen af kapitlet).

9.1.1 Eksempel: Grafikdemo

Her følger et eksempel, der viser mange af mulighederne, der er med Graphics-objektet.

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

public class Grafikdemo extends JPanel
{
  public void paintComponent(Graphics g)
  {
    super.paintComponent(g);                // tegn først baggrunden på panelet

    g.drawRoundRect(10,10,80,80,25,25);     // tegn rektangel med runde hjørner

    g.drawArc(110,10,80,80,20,320);         // tegn buestykke

    g.fillArc(210,10,80,80,20,320);         // tegn lagkagestykke (udfyldt)

    Polygon p = new Polygon();              // lav polygon, der viser en pil:
    p.addPoint(0,13);  p.addPoint(45,13);                    // frem
    p.addPoint(45,0);  p.addPoint(60,15); p.addPoint(45,30); // spidsen
    p.addPoint(45,17); p.addPoint(0,17);                     // tilbage

    p.translate(300,10);                    // flyt polygonen
    g.drawPolygon(p);                       // tegn polygonen

    p.translate(0,50);                      // flyt polygonen mere
    g.fillPolygon(p);                       // tegn polygonen udfyldt

    for (int i=0; i<4; i++)                 // tegn forskellige skriftstørrelser
    {
      int størrelse = 10+i*4;
      Font skrifttype = new Font("Serif", Font.ITALIC, størrelse);
      g.setFont(skrifttype);
      g.drawString("Skrift "+størrelse, 400, 15+25*i);
    }

    // Indlæs billede. Forudsætter at "bog.gif" er der, hvor programmet køres.
    // Bemærk: I en applet, skriv i stedet getImage(getCodeBase(), "bog.gif")
    // Bemærk: Billedformatet skal være platformsneutralt, f.eks GIF, JPG, PNG.
    Image billede = Toolkit.getDefaultToolkit().getImage("bog.gif");

    g.drawImage(billede, 110, 100, this);         // tegn billedet

    g.drawImage(billede, 0, 100, 100, 160, this); // tegn billedet skaleret
  }
}

Og main()-metode:

import javax.swing.*;

public class BenytGrafikdemo
{
  public static void main(String[] arg)
  {
    JFrame vindue = new JFrame("Grafikdemo");     // opret et vindue på skærmen
    vindue.add( new Grafikdemo() );               // vis panelet i vinduet
    vindue.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // reagér på luk
    vindue.setSize(500,200);                      // sæt vinduets størrelse
    vindue.setVisible(true);                      // åbn vinduet
  }
}

9.1.2 Eksempel: Kurvetegning

Her er en klasse, der tegner en farvet kurve over sinus-funktionen.

I konstruktøren bestemmer vi farverne (opretter Color-objekter) for punkterne, der skal tegnes (vi bruger heltalsdivision med %, se afsnit 2.11.4, for at få nogle gode farveværdier).

Her opretter vi også et vindue og viser panelet på skærmen (bemærk, at fordi vi gør det inden i panel-objektet, skal vi skrive vindue.add(this) i stedet for vindue.add(panel)).

Vi tegner punkterne i paintComponent(), der er gjort så lille og hurtig som muligt (bl.a. ved ikke at oprette objekter i denne metode) – den kaldes jo hver gang skærmen skal opdateres.

Farverne huskes i listen farver, der er defineret som et felt (sådan at den er kendt, så længe Kurvetegning-objektet findes). På den måde får vi data overført fra konstruktør til paintComponent().

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

public class Kurvetegning extends JPanel
{
  ArrayList<Color> farver;//felt kendt både i konstruktør og paintComponent
  int forskydning = 50;  // en forskydning i farvevalget (bruges i afsnit 9.4.1)

  public Kurvetegning()  // forbered punkterne i konstruktøren
  {
    farver = new ArrayList<Color>();
    for (int i=0; i<400; i++)
    {
      Color farve = new Color(i%256, (i*2)%256, (i*4)%256);
      farver.add(farve);
    }

    JFrame vindue = new JFrame("Kurvetegning");   // opret et vindue på skærmen
    vindue.add( this );                           // vis dette panel i vinduet
    vindue.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // reagér på luk
    vindue.setSize(400,300);                      // sæt vinduets størrelse
    vindue.setVisible(true);                      // åbn vinduet
  }

  public void paintComponent(Graphics g) // tegn punkterne
  {
    super.paintComponent(g);            // tegn først baggrunden på panelet

    g.drawString("Kurvetegning", forskydning%400, forskydning%300);
    for (int x=0; x<farver.size(); x++)
    {
      int y = 140 - (int) (130*Math.sin(0.05*x));
      int i = (x+forskydning)%400;
      Color farve = farver.get(i);
      g.setColor(farve);
      g.fillRect(x, y, 5, 5);
    }
  }
}

Her er klassen, der opretter Kurvetegning-objektet:

public class BenytKurvetegning
{
  public static void main(String[] arg)
  {
    Kurvetegning kt = new Kurvetegning();
  }
}

9.2 Metoder du kan kalde

Ud over at tegne grafik har man også ofte brug for at påvirke selve vinduet eller panelet, f.eks sætte vinduets størrelse eller titel eller bede systemet om at gentegne skærmen.

JFrame-klassens metoder – (generel) betyder, at metoden også findes i andre grafiske klasser.

repaint() (generel)
forårsager, at systemet kalder paintComponent() lidt senere, hvorved vinduet/panelet bliver gentegnet.

void setSize(int bredde, int højde) (generel)
sætter bredden og højden.

void setLocation(int x, int y) (kun JFrame)
sætter vinduets position på skærmen.

void setTitle(String titel) (kun JFrame)
sætter vinduets titel.

void setVisible(boolean synlig) (generel)
bestemmer, om vinduet/panelet/komponenten er synlig.
Kald setVisible(true) for at åbne et vindue og setVisible(false) for at lukke det.

void setCursor(Cursor museudseende) (generel)
bestemmer musens udseende (muligheder er bl.a.: Cursor.DEFAULT_CURSOR,
Cursor.HAND_CURSOR, Cursor.CROSSHAIR_CURSOR, Cursor.MOVE_CURSOR)

void setForeground(Color forgrundsfarve) (generel)
sætter forgrundfarven, som er den farve, Graphics-objektet normalt tegner med.

void setBackground(Color baggrundsfarve) (generel)
sætter baggrundfarven, som er den farve, baggrunden bliver udfyldt med.

void setFont(Font nySkrifttype) (generel)
sætter skrifttypen (som er den skrifttype, Graphics-objektet normalt tegner med).

Dimension getSize() (generel)
returnerer vinduets/panelets størrelse som et Dimension-objekt med bredde og højde.

Tilsvarende findes getLocation, getTitle, getCursor, getForeground, getBackground og getFont.

Metoder, der er markeret med (generel), findes i alle grafiske objekter, dvs. også JPanel, JButton,..

Bemærk at det er systemet og ikke dig, der kalder paintComponent().

Metoden paintComponent() kaldes af systemet når vinduet skal gentegnes
Det er næsten altid en fejl at kalde paintComponent() fra sin egen kode
Ønsker man at vinduet skal gentegnes, kalder man repaint()

I afsnit 9.4.4, Passiv versus aktiv visning, vil dette blive uddybet.

Bemærk også, at der er forskel på om en metode på et objekt kaldes inde fra objektet, eller udefra. Udefra kalder man metoden med en variabel, der peger på objektet, det gør man ikke indefra (man kan dog bruge this). Dette er forklaret i kapitel 4, Definition af klasser.

Sådan kaldes en metode inde fra panelet:

public class Grafikpanel extends JPanel
{
  public void paintComponent(Graphics g)
  {
    Dimension d = this.getSize();                   // eller bare: getSize()
    System.out.println("Jeg har størrelsen: "+d);

Sådan kaldes en metode udefra:

public class BenytGrafikpanel
{
  public static void main(String[] arg)
  {
    Grafikpanel panel = new Grafikpanel();

    ...

    Dimension d = panel.getSize();
    System.out.println("Panelet har størrelsen: "+d);
  }
}

9.3 Opgaver

  1. Ændr Grafikpanel til at tegne nogle andre figurer.

  2. Skriv noget ud når paintComponent() bliver kaldt (med System.out.println()) og se hvornår paintComponent() bliver kaldt (prøv f.eks. at minimere og gendanne vinduet eller dække det halvt over).

  3. Lav et program, der viser et digitalur som tekst (vink: Brug et Date-objekt).
    Sørg for at uret opdateres hvert sekund (vink: se 9.4.1, Simple animationer)).

  4. Lav et program, der viser et analogt ur.
    Vink: Du kan benytte følgende formler til at beregne viserens længde i de to retninger:
    x = r*Math.sin(2*Math.PI*s/60); y = r*Math.cos(2*Math.PI*s/60)
    Lad urets størrelse afhænge af panelets størrelse (vink: Brug getSize()).

  5. Ændr Grafikpanel, så den tegner et skakbræt med sorte og hvide felter.
    Et tårn og en bonde tegnes derefter på brættet.

  6. Ændr programmet, sådan at det er nemt at ændre brikkernes koordinater.
    Koordinaterne gemmes i to Point-objekter:
    Point tårn = new Point(100,200);
    Point bonde = new Point(200,200);

9.3.1 Opgave: Grafisk Matador-spil

Udvid matadorspillet fra kapitel 5, Nedarvning, til at kunne vises grafisk i et vindue.

Vink

Når du skal programmere, så vær systematisk og del opgaven op i små delopgaver. Løs en delopgave ad gangen og afprøv om det fungerer, før du går videre.

  1. Hent kildeteksten til matadorspillet (version 2: Felt.java, Gade2.java, Grund2.java, Helle.java, Rederi2.java, Start.java, Spiller.java, BenytMatadorspil.java og Matadorspil.java ændret til at bruge Gade2 og Rederi2) og prøv det.

  2. Genbrug Grafikpanel ovenfor. Lad initialisering ske i konstruktøren. Variabler, der skal deles mellem flere metoder, skal være felter (lokale variabler eksisterer jo kun i den metode, de er defineret i). Tjek om spillet stadig kan køre.

  3. Udbyg derefter Grafikpanel til at tegne felternes navne og evt også spillerne og bilerne.

  4. Felterne bør tegnes specielt afhængigt af deres type, sådan at f.eks. en gade også kan vise hvor mange huse, der er på den. Definér tegn(Graphics g, int x, int y) på Felt.

Flere vink og løsningsforslag

Det er bedst, at du bruger hovedet og kun ser på vinkene, hvis du er gået i stå.

  1. Prøve programmet.
    Har du ikke allerede prøvet matadorspillet, så prøv at køre programmet og forstå hvordan det virker. Herefter er det naturligvis meget lettere, at lave en grafisk udgave! Brug trinvis gennemgang (trace into/step over), indtil du føler, du har forstået programkoden. Først da er du klar til at prøve grafisk.


  2. Struktur i et grafisk program.
    Lav en reference i Grafikpanel til Matadorspil (evt. overført i konstruktøren):

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

public class MatadorGrafikpanel extends JPanel
{
  Matadorspil spil;

Husk, at vinduet først tegnes, når initialiseringen er færdig, så hvis du f.eks. kører 20 runder i konstruktøren, tager det lang tid, før systemet kalder paintComponent()!

  1. Definér panelets paintComponent()-metode til at tegne felternes navne:

  public void paintComponent(Graphics g) 
  {
    super.paintComponent(g);    // tegn først baggrunden på panelet

    for (int i = 0; i < felter.size(); i++) {
      Felt felt = felter.get(i);

      double v = Math.PI * 2 * i / felter.size(); // vinkel i radianer
      int x = 100 + (int) (100 * Math.cos(v));
      int y = 110 + (int) (100 * Math.sin(v));

      g.setColor(Color.BLACK);
      g.drawString(felt.navn, x, y);              // tegn feltets navn
  1. Nu ændrer du sådan, at felterne har deres egen tegnemetode (der tegner navnet):

public class Felt
{
  String navn;

  public void tegn(Graphics g, int x, int y) 
  {
    g.drawString(f.navn, x, y);
  }

... og i Grafikpanel definerer lader vi paintComponent()-metoden kalde tegn():

      g.setColor(Color.BLACK);
      felt.tegn(g, x, y);

Grund2 skal også tegne en ejer, så den får sin egen tegn()-metode

  public void tegn(Graphics g, int x, int y) 
  {
    super.tegn(g, x, y);  // kald Felts tegn() for at tegne navnet
    if (ejer != null) g.drawString(ejer.navn, x, y+15);
  }

Ligeledes med Gade2, der også skal vise et antal huse:

  public void tegn(Graphics g, int x, int y) 
  {
    super.tegn(g, x, y);  // kald Grund2s tegn() for at tegne navnet og ejeren
    if (antalHuse > 0) g.drawString(antalHuse + " huse", x, y+30);
  }

9.4 Avanceret

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

9.4.1 Simple animationer

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

9.4.2 Animationer med en separat tråd

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

9.4.3 Java2D - avanceret grafiktegning

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

9.4.4 Passiv versus aktiv visning

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

9.4.5 Fuldskærmstegning

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

9.4.6 Dobbeltbuffer

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

1Det er dog muligt at lave en blanding af aktiv og passiv visning. Havde tegn() kun optegnet billedet, dvs ikke også ventet og opdateret koordinater, kunne vi kalde tegn() fra paintComponent().

2Bliver grafikken vist i et vindue vil det billede blive kopieret hen til det rigtige sted i hukommelsen. I fuldskærmstilstand vil grafikkortet i stedet blive dirigeret til at vise det nye hukommelsesområde (hvis grafikkortet og styresystemet understøtter det), hvilket er meget hurtigere,

javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen
http://javabog.dk/ - Forord af Jacob Nordfalk.
Licens og kopiering under Åben Dokumentlicens (ÅDL) hvor intet andet er nævnt (79% af værket).

Ønsker du at se de sidste 21% af dette værk (267325 tegn) skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.