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

9 Grafiske programmer

Indhold:

Kapitlet forudsættes i kapitel 10, Appletter, kapitel 11, Grafiske standardkomponenter 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 få et vindue op på skærmen ved at skrive en klasse, der arver fra klassen Frame og definere metoden paint(), som systemet vil kalde når vinduet skal tegnes op på skærmen. paint() får et Graphics-objekt (beskrevet i afsnit 9.1) overført, som vi kan tegne med.

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

import java.awt.*;

public class GrafiskVindue extends Frame
{
  public void paint(Graphics g)
  {
    // Herunder referer g til et Graphics-objekt man kan tegne med
    g.drawLine(0,0,50,50);

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

    g.setColor(Color.GREEN);

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

For at vise det grafiske vindue på skærmen skal vi definere en main()-metode, der opretter et GrafiskVindue-objekt, sætter vinduets størrelse og gør det synligt på skærmen:

public class VisGrafiskVindue
{
  public static void main(String[] arg)
  {
    GrafiskVindue vindue = new GrafiskVindue();   // opret vinduet
    vindue.setSize(350,60);                       // sæt vinduets størrelse
    vindue.setTitle("GrafiskVindue");             // sæt vinduets titel
    vindue.setVisible(true);                      // åbn vinduet
  }
}

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.

Netop i en Frame regnes koordinaterne inklusiv vinduets dekoration, så (0,0) er dækket af titellinjen på vinduet. Ønskes det, kan nulpunktet forskydes i paint() med kommandoerne

    Insets in = getInsets();                // kun Frame: forskyd nulpunktet
    g.translate(in.left,in.top);            // (0,0) til under titellinjen

Hvis man kører programmet, opdager man, at vinduet ikke reagerer, når man forsøger at lukke det. Man kan i stedet stoppe programmet (f.eks. vælge 'Stop' i udviklingsværktøjet eller trykke Ctrl-C fra kommandolinjen). Man kan også bruge klassen LukProgram, defineret i afsnit 13.4.1, og tilføje følgende linje i main()-metoden:

    vindue.addWindowListener( new LukProgram() );

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 øverste venstre hjørne 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 vinduet (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 2D-grafik end ovenstående giver, kan man gå over til at bruge Java2D (se avanceret-afsnittet i slutningen af kapitlet).

9.1.1 Eksempel

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

import java.awt.*;
public class Grafikdemo extends Frame
{
  public void paint(Graphics g)
  {
    Insets in = getInsets();                // kun Frame: forskyd nulpunktet
    g.translate(in.left,in.top);            // (0,0) til under titellinjen

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

    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
  }
}



Herunder er en klasse med en main()-metode, der åbner vinduet.

public class VisGrafikdemo
{
  public static void main(String[] arg)
  {
    Grafikdemo vindue = new Grafikdemo();
    vindue.setSize(500,200);
    vindue.setTitle("Grafikdemo");
    // vindue.addWindowListener( new LukProgram() ); // defineret i kapitel 13
    vindue.setVisible(true);
  }
}

9.2 Metoder i vinduer, som du kan kalde

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

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

repaint() (generel)
forårsager, at systemet kalder paint() lidt senere, hvorved skærmen bliver gentegnet.

void setSize(int bredde, int højde)
sætter vinduets bredde og højde.

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

void setVisible(boolean synlig)
bestemmer, om vinduet er synligt.
Kald setVisible(true) for at åbne vinduet og setVisible(false) for at lukke det.

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

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 vinduets forgrundfarve, som er den farve, Graphics-objektet normalt tegner med.

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

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

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

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

Insets getInsets()
for netop en Frame regnes koordinaterne inklusiv vinduets dekoration, så (0,0) er normalt dækket af titellinjen på vinduet. Denne metode giver startkoordinaterne for indholdet af vinduet.

De af ovenstående metoder, der også findes i andre grafiske objekter, er markeret med (generel)


Bemærk, 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 vinduet:

public class Grafikdemo extends Frame
{
  public void paint(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 VisGrafikdemo
{
  public static void main(String[] arg)
  {
    Grafikdemo vindue = new Grafikdemo();
    Dimension d = vindue.getSize();
    System.out.println("Vinduet har størrelsen: "+d);

9.2.1 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. Her kalder vi også setSize() og setVisible(true) for at åbne vinduet.

Vi tegner punkterne i paint(), der er gjort så lille og hurtig som muligt (bl.a. ved ikke at oprette objekter i dene metode) - den kaldes jo hver gang vinduet bliver gentegnet.

Farverne huskes i en liste, der er defineret som objektvariabel (sådan at den er kendt, så længe Kurvetegning-objektet findes). På den måde får vi data fra konstruktør til paint().

import java.util.*;
import java.awt.*;
public class Kurvetegning extends Frame
{
  ArrayList<Color> farver;//objektvariabel kendt i både konstruktøren og paint()
  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);
    }

    this.setSize(400,300);  // eller fra main(): vindue.setSize(400,300)
    this.setVisible(true);  // eller fra main(): vindue.setVisible(true)
  }

  public void paint(Graphics g) // tegn punkterne
  {
    g.drawString("Kurvetegning", forskydning%400, forskydning%300);
    for (int x=0; x<farver.size(); x++)
    {
      int y = 150 - (int) (120*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 VisKurvetegning
{
  public static void main(String[] arg)
  {
    Kurvetegning vindue = new Kurvetegning();
  }
}

9.3 Opgaver

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

  2. Skiv noget ud når paint() bliver kaldt (med System.out.println()) og se hvornår paint() 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 vinduets størrelse (vink: Brug getSize()).

  5. Ændr GrafiskVindue, så den tegner et skakbræt med sorte og hvide felter.
    En springer 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.

Ændr SpilMatador sådan, at den også opretter et grafisk vindue, der viser brættet.
Lad programmet spille en tur hvert sekund, eller lad brugeren styre (se f.eks afsnit 2.12.3).

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 og SpilMatador.java ændret til at bruge Gade2 og Rederi2) og prøv det.

  2. Genbrug GrafiskVindue ovenfor. Lad initialisering ske i konstruktøren. Variabler, der skal deles mellem flere metoder, skal være objektvariabler (de lokale eksisterer jo kun i den metode, de er defineret i).

  3. Føj en metode til Felt, der tegner feltet. Hvert felt skal også have en position (den er en del af initialiseringen, så sæt den fra konstruktøren).

  4. Løb igennem alle felter og tegn dem i paint().

  5. Udbyg derefter spillet efter egen smag. Tegn f.eks. også spillerne og deres biler.

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.

  1. Struktur i et grafisk vindue.
    Flyt initialiseringen fra SpilMatador.java til konstruktøren af GrafiskVindue. Husk at importere java.util.* øverst for at få adgang til ArrayList-klassen. Variablerne felter, sp1, sp2 skal nu være objektvariabler (før var de lokale variabler), for at de kan ses i resten af objektet:

import java.awt.*;
import java.util.*;
public class GrafiskVindue extends Frame
{
  // objektvariabler:
  Spiller sp1=new Spiller("Søren",50000,Color.GREEN);    // opret spiller 1 
  Spiller sp2=new Spiller("Gitte",50000,Color.YELLOW);   // opret spiller 2 
  ArrayList felter=new ArrayList(); 

  public GrafiskVindue()
  { 
    felter.add(new Start(5000)); 
    felter.add(new Gade2("Gade 1",10000, 400,1000)); 
    felter.add(new Gade2("Gade 2",10000, 400,1000)); 
    //... osv. 

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

  1. Hvert felt skal have en position. Føj en position (af typen Point) til Felt-klassen:

import java.awt.*; 
public class Felt 
{ 
  String navn; 
  Point position = new Point();

og definér metoden tegn(Graphics g) på Felt, der tegner feltets navn på positionen:

  public void tegn(Graphics g) 
  { 
    g.setColor(Color.BLACK); 
    g.drawString(navn,position.x,position.y); 
  }

Husk at importere java.awt.* øverst for at få adgang til Point- og Graphics-klassen.

Løb alle felterne igennem i konstruktøren og sæt koordinaterne på felterne:

    felter.add(new Gade2("Gade 8",20000,1100,2000)); 
    felter.add(new Gade2("Gade 9",30000,1500,2200)); 
    for (int i=0; i<felter.size(); i++)
    {
      double v = Math.PI*2*i/felter.size(); // vinkel i radianer
      Felt f = felter.get(i); 
      f.position = new Point( 
        100 + (int) (100*Math.cos(v)),  
        110 + (int) (100*Math.sin(v)) 
      ); 
    }
  1. Definér vinduets paint()-metode til at kalde felternes tegn() for at tegne brættet:

  public void paint(Graphics g) 
  {
    for (int i=0; i<felter.size(); i++) 
    {
      Felt f = felter.get(i); 
      f.tegn(g); 
    }

En grund skal også have tegnet ejeren nedenunder, så den skal have en anderledes tegn(). Definér tegn() i Grund2. En gade skal også vise antallet af huse. Definér også tegn() i Gade2.

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 Undgå blinkeri ved ikke at slette gammel grafik

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 Undgå blinkeri ved kun at slette det nødvendige

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 Grafikbuffer (double buffering)

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

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