Forrige avsnitt Neste avsnitt Start på kapittel om grafiske brukergrensesnitt

Panel brukt som tegneflate

Uttegning av grafikk, inklusive visning av bilder, bør generelt gjøres på en egen flate som er reservert for nettopp dette. For dersom vi tegner ut grafikk på en flate der vi også plasserer grafiske skjermkomponenter (knapper, tekstfelter, etc.), så kan det lett føre til konflikter ved at skjermkomponentene blir tegnet oppå grafikken, eller omvendt. Egne tegneflater får vi definert i form av subklasser til JPanel. Det er da i de fleste tilfeller nødvendig å redefinere metoden

  public void paintComponent( Graphics g )

som arves (indirekte) fra klassen JComponent.

NB! Første instruksjon i den redefinerte metoden være

    super.paintComponent( g );

Følger vi ikke denne regelen, risikerer vi at uttegning på skjermen ikke blir riktig. Lar vi være å redefinere paintComponent-metoden, kan vi imidlertid likevel kunne få tegnet ut grafikk på panelet, forutsatt at vi får tak i panelets såkalte grafikk-kontekst. Denne teknikken er brukt i eksempel 3 nedenfor.

Merknad 1

Klassene JFrame og JApplet er ikke subklasser til JComponent. (JFrame er subklasse til Frame og JApplet er subklasse til Applet.) Disse klassene arver derfor ikke noen metode paintComponent, men derimot metoden paint, som vi kjenner fra tidligere. Når vi definerer subklasser til JFrame og JApplet, er det derfor paint-metoden vi må redefinere for å få tegnet ut direkte på vindus- eller appletflata det vi ønsker av grafikk.

Merknad 2

Det skal aldri gjøres direkte kall på verken paint eller paintComponent. Det blir automatisk gjort kall på metodene når komponenten som de tilhører blir tegnet ut på skjermen. Dersom vi ønsker at det skal foretas ny uttegning, gjør vi kall på metoden repaint. Den vil i sin tur gjøre kall på paint eller paintComponent, avhengig av hvilken type komponent kallet på repaint skjer ifra.

Størrelse på JPanel-objekter

Et JPanel-objekt har i utgangspunktet en utstrekning på bare 10 piksler hver vei. Når vi bruker det som container, vil det automatisk bli gjort akkurat stort nok til å romme de komponentene vi legger inn i det. Men når vi bare bruker det som en tegneflate, legges ingen komponenter inn. Vi må derfor på annen måte sørge for at det får ønsket størrelse. Én måte er å bruke en layout-manager som strekker det ut slik at det fyller opp et bestemt felt. Dette vil for eksempel skje når vi plasserer det i en container ved bruk av BorderLayout. En annen mulighet er å redefinere metoden getPreferredSize på følgende måte:

  public Dimension getPreferredSize()
  {
    return new Dimension( b, h );
  }

der b og h angir bredde og høyde i det antall piksler som vi ønsker for hver av dem. (Metoden blir arvet fra klassen java.awt.Component.) Eventuelt kan vi også redefinere metodene getMinimumSize og getMaximumSize på tilsvarende måte.

En tredje mulighet for å sette størrelse er å gjøre kall på metoden setPreferredSize for panelet, eventuelt også på metodene setMinimumSize og setMaximumSize. Alle disse skal som parameter ha et objekt av type Dimension som angir ønsket dimensjon. Det opprettes som vist i metoden getPreferredSize ovenfor.

Eksempel 1

Klassen Blink som er gjengitt nedenfor definerer et tegnepanel der det blir tegnet ut en figur som ser ut som en blink, som vist på følgende bilde:

I driverklassen Blinkframviser for programmet blir det opprettet et panel av den definerte typen. Det blir lagt inn som eneste komponent i vinduet som er vist på bildet ovenfor. (Eksemplet er tilpasset fra læreboka til Lewis & Loftus: Java Software Solutions.)

 1 import javax.swing.JPanel;
 2 import java.awt.*;
 3
 4 public class Blink extends JPanel
 5 {
 6    private final int MAXBREDDE = 300, ANTRINGER = 5, RINGBREDDE = 25;
 7
 8    //  Setter bakgrunnsfarge og størrelse.
 9    public Blink()
10    {
11       setBackground (Color.cyan);
12       setPreferredSize (new Dimension(300,300));
13    }
14
15    //  Tegner blinken.
16    public void paintComponent(Graphics tegneflate)
17    {
18       super.paintComponent (tegneflate);
19
20       int x = 0, y = 0, diameter = MAXBREDDE;
21
22       tegneflate.setColor (Color.white);
23
24       for (int teller = 0; teller < ANTRINGER; teller++)
25       {
26         // veksler mellom farger 
27         if (tegneflate.getColor() == Color.black)
28             tegneflate.setColor (Color.white);
29          else
30             tegneflate.setColor (Color.black);
31
32          tegneflate.fillOval (x, y, diameter, diameter);
33
34          diameter -= (2 * RINGBREDDE);
35          x += RINGBREDDE;
36          y += RINGBREDDE;
37       }
38
39       // Tegner den røde disken i sentrum.
40       tegneflate.setColor (Color.red);
41       tegneflate.fillOval (x, y, diameter, diameter);
42    }
43 }

Eksempel 2

Klassen Kastepanel er en modifikasjon av Blink som ble brukt i forrige program. Klassen blir brukt i programmet Kasteprogram.java som gir et vindu som vist på følgende bilde:

Programmet er ment å skulle simulere kast på blinken. Det blir gjort ved at det for hvert nytt kast blir beregnet en tilfeldig posisjon på panelet. I denne posisjonen blir det tegnet en liten sirkel som er avgrenset av svart strek og fylt med gul farge innenfor den svarte streken. Kode for dette ligger i metoden kastPåBlink. Fargene for sirkelen er valgt slik at det skal oppnås kontrast mot underlaget uavhengig av hvor på panelet sirkelen blir tegnet. Sirkelen må imidlertid tegnes først etter at selve blinken er tegnet opp, ellers vil den bli overtegnet av blinken. Hver gang det skal foretas et nytt kast blir hele panelet tegnet ut på nytt, slik at eventuelt tidligere treffpunkt blir fjernet. Koden for det nye panelet er gjengitt nedenfor.

 1 import javax.swing.JPanel;
 2 import java.awt.*;
 3
 4 public class Kastepanel extends JPanel
 5 {
 6   private final int MAXBREDDE = 300, ANTRINGER = 5, RINGBREDDE = 25;
 7   private final int BALLDIAMETER = 10;
 8   private boolean kast = false;
 9
10   //  Setter bakgrunnsfarge og størrelse for tegnepanelet.
11   public Kastepanel()
12   {
13     setBackground(Color.cyan);
14     setPreferredSize(new Dimension(300,300));
15   }
16
17   //  Tegner blinken.
18   public void paintComponent(Graphics tegneflate)
19   {
20     super.paintComponent(tegneflate);
21
22     int x = 0, y = 0, diameter = MAXBREDDE;
23
24     tegneflate.setColor(Color.white);
25
26     for (int count = 0; count < ANTRINGER; count++)
27     {
28       // veksler mellom farger 
29       if (tegneflate.getColor() == Color.black)
30         tegneflate.setColor (Color.white);
31       else
32         tegneflate.setColor (Color.black);
33
34       tegneflate.fillOval (x, y, diameter, diameter);
35
36       diameter -= (2 * RINGBREDDE);
37       x += RINGBREDDE;
38       y += RINGBREDDE;
39     }
40
41     // Tegner den røde disken i sentrum.
42     tegneflate.setColor (Color.red);
43     tegneflate.fillOval (x, y, diameter, diameter);
44
45     if ( kast )
46     {
47       kastPåBlink( tegneflate );
48       kast = false;
49     }
50   }
51
52   public void nyttKast()
53   {
54     kast = true;
55     repaint();
56   }
57
58   public void kastPåBlink( Graphics g )
59   {
60     int x = (int) (Math.random() * (MAXBREDDE - BALLDIAMETER) );
61     int y = (int) (Math.random() * (MAXBREDDE - BALLDIAMETER) );
62     g.setColor( Color.BLACK );
63     g.drawOval( x, y, BALLDIAMETER, BALLDIAMETER );
64     g.setColor( Color.YELLOW );
65     g.fillOval( x+1, y+1, BALLDIAMETER-2, BALLDIAMETER-2 );
66   }
67 }

Koden for vinduet Kastevindu er gjengitt nedenfor. Det blir der opprettet et panel av den definerte typen. Panelet blir lagt inn i senter-posisjon på vinduets contentPane. (Vi bruker default-layouten for dette, som er BorderLayout.) I sør-posisjon blir det lagt inn en knapp som kan brukes til å foreta nye kast. Knappens lytteobjekt gjør kall på panelets metode nyttKast som sørger for at panelet blir tegnet ut på nytt med et simulert treff i en ny posisjon (som imidlertid også kan være en bom!).

 1 import javax.swing.*;
 2 import java.awt.*;
 3 import java.awt.event.*;
 4
 5 public class Kastevindu extends JFrame
 6 {
 7   private Kastepanel blink;
 8   private JButton kasteknapp;
 9
10   public Kastevindu()
11   {
12     super( "Kaste på blink" );
13     blink = new Kastepanel();
14     kasteknapp = new JButton( "Kast på blinken" );
15     kasteknapp.addActionListener( new Knappelytter() );
16     Container c = getContentPane();
17     c.add( blink, BorderLayout.CENTER );
18     c.add( kasteknapp, BorderLayout.PAGE_END );
19   }
20
21   private class Knappelytter implements ActionListener
22   {
23     public void actionPerformed( ActionEvent e )
24     {
25       if ( e.getSource() == kasteknapp )
26         blink.nyttKast();
27     }
28   }
29 }

Flere eksempler på bruk av tegnepanel finnes i avsnittet Musehendelser og lytteobjekter for mus.

Copyright © Kjetil Grønning, revidert av Eva Hadler Vihovde 2014

Forrige avsnitt Neste avsnitt Start på kapittel om grafiske brukergrensesnitt