Forrige avsnitt Neste avsnitt  Start på kapittel om grafiske brukergrensesnitt

Musehendelser og lytteobjekter for mus

Det som her blir kalt musehendelser, er i java-sammenheng objekter av type MouseEvent. Slike blir generert hver gang musa brukes på en eller annen måte mens musepekeren peker på en skjermkomponent. Det finnes flere forskjellige typer lytteobjekt som kan brukes til å fange opp musehendelser. Nedenfor er det satt opp en oversikt over de forskjellige typer interface som kan implementeres for å definere lytteobjekt for mus og hvilke metoder som tilhører hvert interface. Som du ser, indikerer navnene på metodene hvilken type museaktivitet vedkommende metode kan bli aktivert av. Når det i beskrivelsen nevnes 'komponenten', så menes en skjermkomponent som det er registrert lytteobjekt for av den type som vedkommende interface spesifiserer.

Metoder i interface MouseListener:

public void mousePressed( MouseEvent e ) { ... }
Kalles opp når en museknapp blir trykket ned mens musepekeren er på komponenten.
public void mouseReleased( MouseEvent e ) { ... }
Kalles opp når en museknapp blir sluppet opp igjen (etter først å ha blitt trykket ned mens musepekeren stod på komponenten).
public void mouseClicked( MouseEvent e ) { ... }
Kalles opp når en museknapp trykkes ned og slippes opp igjen uten at musa i mellomtida er blitt flyttet.
public void mouseEntered( MouseEvent e ) { ... }
Kalles opp når musepekeren kommer inn utenfra inn på komponentens område.
public void mouseExited( MouseEvent e ) { ... }
Kalles opp når musepekeren forlater komponentens område.

Metoder i interface MouseMotionListener:

public void mouseMoved( MouseEvent e ) { ... }
Kalles opp når musepekeren blir flyttet innenfor komponentens område, samtidig som ingen museknapp er trykket ned.
public void mouseDragged( MouseEvent e ) { ... }
Kalles opp når musepekeren blir flyttet innenfor komponentens område, samtidig som en museknapp holdes nede.

Begge interface MouseListener og MouseMotionListener befinner seg i pakken java.awt.event. Fra og med java-versjon 1.4 er det i pakken javax.swing.event definert et interface MouseInputListener som omfatter alle metodene i de to nevnte interface. Som alternativ til å implementere de to nevnte interface hver for seg (i tilfelle man har bruk for metoder fra begge), kan man derfor implementere dette siste. Men dette vil da ikke bli kompatibelt bakover til eldre kjøresystemer for java-programmer.

De nevnte typer muselyttere kan registreres for alle typer swing-komponenter. For å knytte muselyttere til en komponent, gjør man kall på komponentens metoder addMouseListener og addMouseMotionListener for å knytte til lytteobjekt av type henholdsvis MouseListener og MouseMotionListener. Det finnes imidlertid ingen tilsvarende metode addMouseInputListener. Isteden må man gjøre kall på begge de nevnte metodene for å knytte til et lytteobjekt av type MouseInputListener. (Interface MouseInputListener arver fra både MouseListener og MouseInputListener. Et objekt av type MouseInputListener kan derfor oppfattes både som en MouseListener og som en MouseMotionListener.)

I java-versjon 1.4 er det også definert et interface MouseWheelListener med metoden

  public void mouseWheelMoved( MouseWheelEvent e ) { ... }

Denne vil bli aktivert hver gang musehjulet blir rotert (forutsatt at det er registrert lytteobjekt av denne type for komponenten). Vi trenger imidlertid vanligvis ikke implementere noen musehjulslytter. Musehjulet brukes hovedsakelig for skrolling, og komponenter med skrollefelter registrerer automatisk musehjulslyttere som vil reagere riktig på bruk av musehjulet.

I beskrivelsen ovenfor er det brukt uttrykket 'en museknapp'. Vanligvis er det venstre museknapp det da er snakk om. Dersom vi trenger å sjekke om det dreier seg om høyre museknapp, eller den midterste museknappen for en mus med tre knapper på, kan vi bruke vedkommende metodes MouseEvent-parameter e til å gjøre kall på metoden isMetaDown, som returnerer true i tilfelle høyre museknapp er trykket ned, eller metoden isAltDown, som returnerer true i tilfelle den midterste museknappen er trykket ned.

Adapterklasser

Som vi har sett, inneholder de interface som spesifiserer lytteobjekter for mus mange metoder. Ofte er det bare én eller noen få av metodene vi har behov for å implementere. Men for å implementere vedkommende interface er vi da nødt til å implementere de øvrige metodene med tomt innhold. For at vi skal slippe å gjøre dette, er det i javas klassebibliotek definert klasser som implementerer hvert interface med metoder som har tomt innhold. Disse klassene blir med et felles navn kalt for adapterklasser. Når vi skal definere et lytteobjekt der vi ikke trenger å implementere alle metodene i vedkommende interface, kan vi derfor velge å definere en subklasse til vedkommende adapterklasse der vi redefinerer den eller de metodene som vi har behov for å implementere. Dermed slipper vi å implementere metoder med tomt innhold. (Vi kjenner for øvrig til dette allerede i forbindelse med programmering av lukkeknappen til et vindu. Vi har da definert en subklasse til adapterklassen WindowAdapter der vi har redefinert metoden windowClosing.)

Adapterklassene til MouseListener, MouseMotionListener og MouseInputListener heter henholdsvis MouseAdapter, MouseMotionAdapter (begge i pakke java.awt.event) og MouseInputAdapter (i pakke javax.swing.event).

Hvor skjedde musehendelsen?

Når vi skal programmere en muselytter, er det som regel behov for å finne ut hvor på den aktuelle komponenten musehendelsen fant sted. Opplysninger om dette kan vi hente ut av MouseEvent-objektet som er parameter til vedkommende metode. Heter denne parameteren e, så vil e.getX() og e.getY() gi oss koordinatene i antall piksler relativt til øverste venstre hjørne i vedkommende komponent.

Hvordan kunne tegne grafikk uten å bruke paint- eller paintComponent-metode?

Noen ganger kan vi ønske oss å kunne tegne grafikk (inkludert tekst) på skjermen uten å måtte gjøre bruk av paint- eller paintComponent-metoden. Som vi husker, har disse metodene en parameter av type Graphics som vi bruker for å få tegnet ut grafikk (ved kall på metodene drawString, drawLine, etc.). Hver grafisk komponent har det som blir kalt en grafikk-kontekst. Det vil si at det til komponenten er knyttet et Graphics-objekt. Når dette objektet blir brukt til å gjøre kall på metodene drawString, drawLine, etc., så vil grafikken bli tegnet ut på nettopp denne komponenten. Det vi trenger er derfor å få tak i det Graphics-objektet som representerer grafikk-konteksten til vedkommende komponent. Dette får vi tak i ved å gjøre kall på komponentens metode getGraphics. Retur-objektet fra denne metoden må vi selvsagt fange opp i en variabel av type Graphics. Det er imidlertid viktig å være klar over at grafikk-konteksten ikke vil eksistere før komponenten er såkalt realisert. I praksis vil dette si at
setVisible(true) eller pack() er utført på komponenten, eller dersom komponenten er add'et til en container som er blitt realisert. Før denne tid vil metoden getGraphics returnere null. I følgende programeksempel blir denne teknikken brukt for å få tegnet ut grafikk på et panel uten å gjøre bruk av panelets paintComponent-metode.

Programeksempel: Musehendelser

Koden for tegnepanelet Musepanel er gjengitt nedenfor. For panelet er det definert og registrert et lytteobjekt av type MouseListener. Lytteobjektet er definert av den indre klassen Muselytter i panelklassen. Når en museknapp blir trykket ned (samme hvilken av dem), blir det gjort kall på metoden mousePressed. Denne er programmert til å registrere posisjonen for dette og skrive den ut på panelet. Starten for utskriften er i vedkommende posisjon. Utskriften gjøres uten bruk av paintComponent-metoden, slik det er forklart ovenfor. Vi ønsker dessuten at det nederst i vinduet som panelet skal ligge i samtidig blir skrevet ut en melding om at museknapp ble trykket ned. For å få til dette, trenger vi en kommunikasjonsvei tilbake til vindusklassen som har opprettet panelet og plassert det på sin vindusflate. Denne kommunikasjonen får vi til ved at det i panelklassen er et datafelt av den typen som vindusklassen definerer. Datafeltet blir i muselytteren brukt til å gjøre kall på metoden visHendelse i vindusklassen. Denne metoden setter tekst for en label som vises nederst i vinduet. Vindusklassen Testvindu er gjengitt etter tegnepanelet Musepanel nedenfor.

Muselytteren virker videre på den måten at dersom museknappen slippes opp igjen på samme sted som den er trykket ned, så blir metoden mouseClicked kalt opp. Denne skriver på det stedet dette skjedde melding om at musa ble klikket, samt posisjonen. Dersom musa derimot flyttes mens knappen holdes nede, så vil det på det stedet der knappen slippes opp igjen bli gjort kall på metoden mouseReleased. Denne skriver ut melding om at museknappen ble sluppet, samt posisjonen for dette. I tillegg blir det tegnet en rett linje mellom det sted museknappen ble trykket ned (posisjonen for dette ble i sin tid registrert) og der den ble sluppet opp igjen.

Når musepekeren blir flyttet inn på panelet utenfra, blir det gjort kall på metoden mouseEntered i det øyeblikk musepekeren kommer inn på panelet. Metoden registrerer i hvilken posisjon dette skjedde. Dette blir kommunisert til vinduet ved kall på dets metode visHendelse. Dessuten settes panelets bakgrunnsfarge til hvit. Når musepekeren går ut av panelet, er det metoden mouseExited som blir kalt opp. Denne er gitt tilsvarende virkning som mouseEntered, men nå settes bakgrunnsfargen til rød. Noe annet som er programmert inn i dette eksemplet, er at hver gang en museknapp trykkes ned, så blir panelet blanket ut før ny tekst skrives ut. Dette oppnås ved kall på metoden update der panelets tilknyttede Graphics-objekt blir brukt som aktuell parameter. Driverklasse for programmet finnes i fila Muselytterdemo.java. På følgende bilde er en museknapp blitt trykket ned på ett sted i panelet, så er den holdt nede mens musa er flyttet til et annet sted, der knappen er sluppet opp igjen. Nederst i vinduet er det skrevet ut i hvilken posisjon musepekeren kom inn på panelet.

 1 import javax.swing.*;
 2 import java.awt.*;
 3 import java.awt.event.*;
 4
 5 public class Musepanel extends JPanel
 6 {
 7   private int xPos, yPos; //posisjon for klikk på museknapp
 8   private Testvindu vindu;
 9
10   public Musepanel()
11   {
12     setBackground( Color.red );
13     addMouseListener( new Muselytter() );
14   }
15
16   public void setVindu(Testvindu v)
17   {
18     vindu = v;
19   }
20
21   public Dimension getPreferredSize()
22   {
23     return new Dimension( 400, 400 );
24   }
25
26   private class Muselytter implements MouseListener
27   {
28     public void mouseClicked( MouseEvent e )
29     {
30       Graphics panelgrafikk = getGraphics();
31       update( panelgrafikk ); //blanker skjermen
32       panelgrafikk.drawString( "Mus klikket i posisjon [" +
33           e.getX() + ", " + e.getY() + "]", e.getX(), e.getY() );
34     }
35
36     public void mouseEntered( MouseEvent e )
37     {
38       setBackground( Color.white );
39       vindu.visHendelse( "Musepeker entret i posisjon [" +
40                       e.getX() + ", " + e.getY() + "]" );
41     }
42
43     public void mouseExited( MouseEvent e )
44     {
45       setBackground( Color.red );
46       int x = Math.max( 0, e.getX() );
47       int y = Math.max( 0, e.getY() );
48       vindu.visHendelse( "Musepeker gått ut av panel i posisjon [" +
49                       x + ", " + y + "]" );
50     }
51
52     public void mousePressed( MouseEvent e )
53     {
54       Graphics panelgrafikk = getGraphics();
55       update( panelgrafikk );
56       xPos = e.getX();
57       yPos = e.getY();
58       panelgrafikk.drawString( "Museknapp trykket i posisjon [" +
59           e.getX() + ", " + e.getY() + "]", xPos, yPos );
60     }
61
62     public void mouseReleased( MouseEvent e )
63     {
64       Graphics panelgrafikk = getGraphics();
65       panelgrafikk.drawString( "Museknapp sluppet i posisjon [" +
66           e.getX() + ", " + e.getY() + "]", e.getX(), e.getY() );
67       panelgrafikk.drawLine( xPos, yPos, e.getX(), e.getY() );
68     }
69   } //class Muselytter
70 } //class Musepanel

 1 import javax.swing.*;
 2 import java.awt.*;
 3
 4 public class Testvindu extends JFrame
 5 {
 6   private Musepanel tegnepanel;
 7   private JLabel rapport;
 8
 9   public Testvindu()
10   {
11     super( "Musehendelser" );
12     tegnepanel = new Musepanel();
13     rapport = new JLabel();
14     Container c = getContentPane();
15     c.setLayout( new FlowLayout() );
16     c.add( tegnepanel );
17     c.add( rapport );
18     setVisible( true );
19     tegnepanel.setVindu(this);
20   }
21
22   public void visHendelse( String beskrivelse )
23   {
24     rapport.setText( beskrivelse );
25   }
26 }

Merknad Tegnepanelet kalt Musepanel skal kommunisere med vinduet som det ligger i for å skrive ut museposisjoner i et tekstfelt i vinduet. For å opprette forbindelse til vinduet er det i panelet definert en metode setVindu. Denne blir kalt opp på slutten av vindusklassens konstruktør:

19     tegnepanel.setVindu(this);

Nøkkelordet this refererer til det objektet som utfører vedkommende kode, i dette tilfelle vindusobjektet, siden instruksjonen står inne i konstruktøren til vindusobjektet. Generelt blir det advart mot å bruke this inne i konstruktøren til et objekt, siden objektet ikke er ferdig konstruert ennå. Derfor er det nevnte metodekallet plassert helt på slutten av konstruktøren, etter at vinduet er gjort synlig på skjermen som følge av instruksjonen

18     setVisible( true );

Når den instruksjonen er utført, er vinduet blitt såkalt realisert og dermed ferdig konstruert. Bruk av nøkkelordet this skulle derfor være trygt å gjøre på dette tidspunkt.

Programeksempel: Musedrag

Programkoden for tegnepanelet Musedrag er gjengitt nedenfor. Det blir for panelet definert og registrert lytteobjekter for både museklikk og musebevegelse. Når det gjelder museklikk, er det bare noen av metodene det er behov for å implementere. Lytteklassen Muselytter blir derfor definert som en subklasse til adapterklassen MouseAdapter, slik at vi skal slippe å definere tomme metoder. Når det gjelder musebevegelser, er det imidlertid behov for å implementere begge metodene spesifisert av interface MouseMotionListener. Lytteklassen Musebevegelseslytter blir derfor spesifisert til å implementere dette.

Det blir registrert hvor på panelet museknappen blir trykket ned. Når musa dras med knappen nede, blir det kontinuerlig vekselvis trukket en rett (svært kort!) linje fra forrige til nåværende posisjon, og foretatt oppdatering av forrige posisjon ved at nåværende posisjon blir registrert som ny forrige-posisjon. Effekten av dette blir at musa virker som en penn: En kan skrive og tegne med musepekeren når museknappen holdes nede. Museposisjonen blir hele tida skrevet ut nederst i vinduet som panelet ligger i. For øvrig virker programmet på tilsvarende måte som det forrige på den måten at panelets bakgrunnsfarge skifter mellom hvit og rød avhengig av om musepekeren er inni eller utenfor panelet. Hver gang den kommer utenfor, blir dessuten panelet blanket ut.

Panelet er plassert i vinduet Tegnevindu. Dette har helt tilsvarende innhold og virkemåte som vinduet Testvindu i foregående eksempel. Driverklasse for programmet finnes i fila Musetegning.java.

I læreboka Deitel & Deitel, 9. utgave Fig. 14.34 og Fig. 14.5 er det et liknende program som er laget på en litt annen måte. Dette programmet gir imidlertid ikke sammenhengende strek dersom en fører musepekeren litt for fort.

Følgende bilde viser et eksempel på hvordan tegnevinduet til vårt program kan bli seende ut.

 1 import javax.swing.*;
 2 import java.awt.*;
 3 import java.awt.event.*;
 4
 5 public class Musedrag extends JPanel
 6 {
 7   private int xPos, yPos;
 8   private Tegnevindu vindu;
 9
10   public Musedrag()
11   {
12     setBackground( Color.red );
13     addMouseListener( new Muselytter() );
14     addMouseMotionListener( new Musebevegelseslytter() );
15   }
16
17   public void setTegnevindu(Tegnevindu v)
18   {
19     vindu = v;
20   }
21
22   public Dimension getPreferredSize()
23   {
24     return new Dimension( 400, 400 );
25   }
26
27   private class Muselytter extends MouseAdapter
28   {
29     public void mouseEntered( MouseEvent e )
30     {
31       setBackground( Color.white );
32       repaint();
33     }
34
35     public void mouseExited( MouseEvent e )
36     {
37       setBackground( Color.red );
38       repaint();
39     }
40
41     public void mousePressed( MouseEvent e )
42     {
43       xPos = e.getX();
44       yPos = e.getY();
45     }
46   } //class Muselytter
47
48   private class Musebevegelseslytter implements MouseMotionListener
49   {
50     public void mouseDragged( MouseEvent e )
51     {
52       Graphics panelgrafikk = getGraphics();
53       panelgrafikk.drawLine( xPos, yPos, e.getX(), e.getY() );
54       xPos = e.getX();
55       yPos = e.getY();
56       vindu.visHendelse("Museposisjon: [ " + xPos + ", " + yPos + "]");
57     }
58
59     public void mouseMoved( MouseEvent e )
60     {
61       vindu.visHendelse("Museposisjon: [ " + e.getX()+ ", " +
62               e.getY() + "]");
63     }
64   } //class Musebevegelseslytter
65 } //class Musedrag

Programeksempel: Tegne med valgt strekbredde og tegnefarge

Vi skal omarbeide tegneprogrammet i forrige programeksempel slik at brukeren har muligheter til underveis å velge bredde på tegnestreken samt hvilken av noen standardfarger det skal tegnes med. For fargevalget ønsker vi at fargealternativene skal vises i tegnevinduet som en listeboks med små fargepaneler, slik at når brukeren klikker på et fargepanel i denne listeboksen, så blir panelets farge satt som ny tegnefarge. Før vi kan skrive de nødvendige endringene i programkoden, må vi da vite hvordan vi kan få satt strekbredde, og hvordan vi kan få vist fargepaneler i en listeboks.

Hvordan sette strekbredde?
Som tidligere nevnt, er det til hver grafisk komponent på skjermen knyttet et Graphics-objekt som vi kan få tak i ved å gjøre kall på komponentens metode getGraphics. Objektet kalles komponentens grafikk-kontekst. Når vi ved hjelp av dette objektet gjør kall på tegnemetoder som drawLine, drawOval, etc., så vil bredden på tegnestreken være lik én piksel. I Graphics-klassen finnes det ingen metoder som vi kan bruke til å få endret på dette. Men det objektet som blir returnert fra getGraphics-metoden er egentlig av subklassetypen Graphics2D, og i denne klassen finnes det metoder for å endre strekbredden. For å gjøre bruk av dette, kan vi imidlertid ikke skrive

  Graphics2D g = getGraphics();

Det vil resultere i kompileringsfeil. Grunnen er at metoden getGraphics er deklarert til å returnere en verdi av type Graphics, og det er jo oppfylt av Graphics2D, siden det er en subklassetype. Men vi kan ikke tilordne returverdien til et objekt av typen Graphics2D, for kompilatoren tror at den er av type Graphics, siden det er det som står i metodesignaturen for getGraphics. Vi må isteden foreta eksplisitt typekonvertering:

  Graphics g = getGraphics();
  Graphics2D g2 = (Graphics2D) g;

For å sette ønsket strekbredde kan vi nå skrive

  g2.setStroke( new BasicStroke( strekbredde ) );

Men merk deg: Parameteren strekbredde må være av datatypen float. Husk også at når vi angir en konkret slik verdi, så må den avsluttes med stor eller liten f, som i eksemplet

  float strekbredde = 2.5F;

Begge klassene Graphics2D og BasicStroke ligger i pakken java.awt.

Hvordan sette tegnefarge?
For objekter av type Graphics vet vi at vi kan sette tegnefarge ved bruk av metoden setColor. Denne blir arvet til subklassen Graphics2D og kan også brukes på objekter av denne typen. Men i Graphics2D finnes det også en metode setPaint med noe større potensiale. Den har parameter av type Paint, som er navnet på et interface som blir implementert blant annet av Color-klassen. For å sette tegnefarge for et Graphics2D-objekt g2 kan vi derfor også skrive

  g2.setPaint( farge );

der farge er ønsket Color-objekt.

Definere visning av valgalternativene i en listeboks
Når valgalternativene i en listeboks blir vist på skjermen, brukes det en såkalt cellerendrer til å vise alternativene. Dersom vi ikke bestemmer noe annet, er det en forhåndsdefinert rendrer som brukes. Denne vet hvordan den skal vise tekst og bilder. Dersom listeboksalternativene er av annen type enn tekst eller bilde, er det resultatet av objektenes toString-metode som blir vist som alternativer i listeboksen. Dette er som regel ikke tilfredsstillende. For å få noe annet, er det nødvendig å definere og installere vår egen cellerendrer. Det skal vi nå gjøre.

Vi ønsker at listeboksalternativene skal bestå av Color-objekter og at listeboksen skal vise alternativene i form av små paneler som har de aktuelle fargene. Vi må da gjøre følgende:

Fra og med javaversjon 7 er interface ListCellRenderer<E> en parametrisert type. For nærmere beskrivelse av parametriserte typer, se Bruk av generiske datatyper.

En cellerendrer som viser listeboksalternativene på den ønskede måten kan vi skrive slik:

  private class Fargelisterendrer implements ListCellRenderer<Color>
  {
    //Følgende panel blir returnert for alle celler i listeboksen med
    //bakgrunnsfarge satt lik den farge som skal velges. Vil vise
    //hvert alternativ som et panel med den farge som alternativet
    //representerer.
    private JPanel panel = new JPanel();

    public Component getListCellRendererComponent(
       JList<? extends Color> liste,
       Color verdi,             // listealternativ som skal vises
       int indeks,              // celleindeks
       boolean erValgt,         // er cella valgt?
       boolean harFokus)        // lista og cella har fokus
    {
      panel.setBackground( verdi );
      return panel;
    }
  }

Vi skulle dermed ha nok bakgrunnskunnskaper til å ta for oss selve programkoden. Tegnepanelet er definert av klassen Tegnepanel som er gjengitt nedenfor. Grafikk-konteksten er konvertert til type Graphics2D for å kunne sette bredde på tegnestreken. Dette er det definert set-metode for. Legg merke til at grafikk-konteksten lagres i et datafelt, slik at vi skal slippe å hente den fram og typekonvertere den hver gang det skal tegnes på panelet. Legg også merke til at det er definert en set-metode for grafikk-konteksten. Grunnen til dette er at den ikke vil eksistere før panelet er realisert, det vil si vist på skjermen. Derfor blir metoden setPanelgrafikk kalt opp fra vinduet som panelet ligger i, etter at vinduet er opprettet og gjort synlig.

Det er dessuten definert funksjonalitet for å foreta utvisking av det som blir tegnet. Utvisking av hele tegningen, slik at vi kan starte på nytt, oppnås ved bruk av metoden blankUtTegning. Denne gjør kort og godt kall på repaint som blanker ut tegneflata. Metoden viskUt er definert for å kunne viske ut deler av det som er tegnet. Metoden setter strekbredden til 5.0 og tegnefargen lik panelets bakgrunnsfarge, slik at vi kan tegne med bakgrunnsfargen. Dessuten lagrer metoden verdiene som strekbredde og tegnefarge hadde før utvisking skal foretas, slik at vi kan bruke metoden avsluttVisking for å få tilbake de gamle verdiene og fortsette å tegne med de gamle verdiene når vi har visket ut det vi vil.

 1 import java.awt.*;
 2 import java.awt.event.*;
 3 import javax.swing.*;
 4
 5 public class Tegnepanel extends JPanel
 6 {
 7   private int xPos, yPos;
 8   private Graphics2D panelgrafikk;
 9   private Color tegnefarge = Color.BLACK;
10   private float strekbredde = 1.0F;
11
12   public Tegnepanel()
13   {
14     setBackground( Color.white );
15     addMouseListener( new Muselytter() );
16     addMouseMotionListener( new Musebevegelseslytter() );
17   }
18
19   public void setPanelgrafikk()
20   {
21     Graphics g = getGraphics();
22     panelgrafikk = (Graphics2D) g;
23   }
24
25   public void setTegnefarge( Color farge )
26   {
27     if ( panelgrafikk != null )
28       panelgrafikk.setPaint(farge);
29   }
30
31   public void setStrekbredde( float bredde )
32   {
33     if ( panelgrafikk != null )
34       panelgrafikk.setStroke(new BasicStroke(bredde));
35   }
36
37   public void blankUtTegning()
38   {
39     repaint();
40   }
41
42   public void viskUt()
43   {
44     tegnefarge = panelgrafikk.getColor();
45     BasicStroke  strek = (BasicStroke) panelgrafikk.getStroke();
46     strekbredde = strek.getLineWidth();
47     setTegnefarge(getBackground());
48     setStrekbredde(5.0F);
49   }
50
51   public void avsluttVisking()
52   {
53     setStrekbredde( strekbredde );
54     setTegnefarge(tegnefarge);
55   }
56
57   public Dimension getPreferredSize()
58   {
59     return new Dimension( 900, 500 );
60   }
61
62   private class Muselytter extends MouseAdapter
63   {
64     public void mousePressed( MouseEvent e )
65     {
66       xPos = e.getX();
67       yPos = e.getY();
68     }
69   } //class Muselytter
70
71   private class Musebevegelseslytter extends MouseMotionAdapter
72   {
73     public void mouseDragged( MouseEvent e )
74     {
75       panelgrafikk.drawLine( xPos, yPos, e.getX(), e.getY() );
76       xPos = e.getX();
77       yPos = e.getY();
78     }
79   } //class Musebevegelseslytter
80 }

Klassen Skriblevindu definerer tegnevinduet. Den er gjengitt nedenfor. I den blir det opprettet en listeboks med Color-objekter som alternativer. For listeboksen er det dessuten nødvendig å gjøre noen tilpasninger ved å skrive følgende instruksjoner:

    fargevelger.setVisibleRowCount( farger.length );
    fargevelger.setFixedCellWidth( 80 );
    fargevelger.setFixedCellHeight( 30 );
    fargevelger.setCellRenderer( new Fargelisterendrer() );
    fargevelger.addListSelectionListener( new Fargelytter() );

Den første instruksjonen bestemmer antall alternativer som skal være synlige samtidig. Vi ønsker at alle skal være det. (Forhåndssatt verdi er 8.) De to neste instruksjonene bestemmer hvor store de skal være panelene som viser fargene. De to siste instruksjonene installerer cellerendrer og lytteobjekt for listeboksen.

For å velge bredde på tegnestreken blir det definert en komboboks med tekstalternativer. Det er lagt opp til å kunne velge bredde fra 1 til 5 piksler, i sprang på en halv piksel. Legg for øvrig merke til hvordan lytteobjektet for listeboksen er definert.

  1 import java.awt.*;
  2 import javax.swing.*;
  3 import java.awt.event.*;
  4 import javax.swing.event.*;
  5
  6 public class Skriblevindu extends JFrame
  7 {
  8   private Tegnepanel tegnepanel;
  9   private JButton vindusblanker, viskUt, sluttVisking;
 10   private JComboBox<String> strekvelger;
 11   private JList<Color> fargevelger;
 12   private Color[] farger = {Color.black, Color.blue, Color.cyan,
 13       Color.darkGray, Color.gray, Color.green, Color.magenta,
 14       Color.orange, Color.pink, Color.red, Color.yellow};
 15
 16   public Skriblevindu()
 17   {
 18     super( "Musetegning" );
 19     tegnepanel = new Tegnepanel();
 20     fargevelger = new JList<>( farger );
 21     fargevelger.setVisibleRowCount( farger.length );
 22     fargevelger.setFixedCellWidth( 80 );
 23     fargevelger.setFixedCellHeight( 30 );
 24     fargevelger.setCellRenderer( new Fargelisterendrer() );
 25     fargevelger.addListSelectionListener( new Fargelytter() );
 26     String[] strekvalg = new String[9];
 27     double bredde = 1.0;
 28     for ( int i = 0; i < strekvalg.length; i++ )
 29     {
 30       strekvalg[i] = bredde + "";
 31       bredde += 0.5;
 32     }
 33     strekvelger = new JComboBox<>(strekvalg);
 34     vindusblanker = new JButton("Blank ut tegning");
 35     viskUt = new JButton("Visk ut");
 36     sluttVisking = new JButton("Slutt å viske");
 37     Knappelytter lytter = new Knappelytter();
 38     strekvelger.addActionListener(lytter);
 39     vindusblanker.addActionListener(lytter);
 40     viskUt.addActionListener(lytter);
 41     sluttVisking.addActionListener(lytter);
 42     JPanel knappepanel = new JPanel();
 43     knappepanel.add(new JLabel("Strekbredde"));
 44     knappepanel.add(strekvelger);
 45     knappepanel.add(vindusblanker);
 46     knappepanel.add(viskUt);
 47     knappepanel.add(sluttVisking);
 48     Container c = getContentPane();
 49     c.add(fargevelger, BorderLayout.LINE_START);
 50     c.add(knappepanel, BorderLayout.PAGE_END);
 51     c.add( tegnepanel, BorderLayout.CENTER );
 52     pack();
 53     setVisible(true);
 54     tegnepanel.setPanelgrafikk();
 55   }
 56
 57   private class Knappelytter implements ActionListener
 58   {
 59     public void actionPerformed( ActionEvent e )
 60     {
 61       if ( e.getSource() == strekvelger )
 62       {
 63         String valg = (String) strekvelger.getSelectedItem();
 64         float bredde = Float.parseFloat(valg + "F");
 65         tegnepanel.setStrekbredde(bredde);
 66       }
 67       else if ( e.getSource() == vindusblanker )
 68         tegnepanel.blankUtTegning();
 69       else if ( e.getSource() == viskUt )
 70         tegnepanel.viskUt();
 71       else if ( e.getSource() == sluttVisking )
 72         tegnepanel.avsluttVisking();
 73     }
 74   }
 75
 76   private class Fargelytter implements ListSelectionListener
 77   {
 78     public void valueChanged( ListSelectionEvent e )
 79     {
 80       if ( e.getSource() == fargevelger )
 81         tegnepanel.setTegnefarge(
 82             farger[ fargevelger.getSelectedIndex() ] );
 83     }
 84   }
 85
 86   //Definerer hvordan listealternativene i en liste skal vises.
 87   private class Fargelisterendrer implements ListCellRenderer<Color>
 88   {
 89     //Følgende panel blir returnert for alle celler i lista med 
 90     //bakgrunnsfarge satt lik den farge som skal velges. Vil vise 
 91     //hvert alternativ som et panel med den farge som alternativet 
 92     //representerer.
 93     private JPanel panel = new JPanel();
 94
 95     public Component getListCellRendererComponent(
 96        JList<? extends Color> liste,
 97        Color verdi,            // listealternativ som skal vises
 98        int indeks,              // celleindeks
 99        boolean erValgt,         // er cella valgt?
100        boolean harFokus)        // lista og cella har fokus
101     {
102       panel.setBackground( verdi );
103       return panel;
104     }
105   }
106 }

Kjøreklasse for programmet finnes på følgende link: Tegneprogram.java.

Følgende bilde viser tegnevinduet etter at det er tegnet en tegning med forskjellige farger og strekbredder.

Tastaturhendelser

Når vi bruker tastene på tastaturet, blir det generert hendelser av type KeyEvent. Disse kan en få fanget opp ved å definere et lytteobjekt av type KeyListener. Dette interface spesifiserer følgende tre metoder som da må implementeres:

  public void keyPressed( KeyEvent e ) { ... }
  public void keyReleased( KeyEvent e ) { ... }
  public void keyTyped( KeyEvent e ) { ... }

KeyListener-objekter kan registreres (ved kall på metoden addKeyListener) for alle typer skjermkomponenter. Det er imidlertid ikke så ofte vi har behov for å gjøre det. Det finnes et eksempel i Deitel & Deitel, 9. utgave Fig. 14.36.

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

Forrige avsnitt Neste avsnitt  Start på kapittel om grafiske brukergrensesnitt