INF 1010, vår 2005 Løsningsforslag uke 10 Anders Brunland 1. april 2005 Oppgave 1 Oppgave 15.4 i i Rett på Java. Løsningsforslag De forskjellige komponentene settes i metoden initcomponents. Her settes også lyttere til de to knappene. Det gjøres med anonyme klasser. En anonym klasse er en klasse som ikke har noe navn. Det vil si at objektet lages samtidig med at klassen deklareres. Konstruksjonen ser slik ut: new AbstractAction () { lesfil (); Her lages det en klasse som er en subklasse av den abstrakte klassen AbstractAction. AbstractAction-klassen har en abstrakt metode som må overskrives, nemlig metoden actionperfomed. Det gjøres i den anonyme klassen hvor metoden inneholder et kall videre til lesfil-metoden. Objektet lages (som vanlig) ved å bruke nøkkelorden new. Hele konstruksjonen over sendes som en parameter til addactionlistener-metoden til den aktuelle knappen. Dermed blir koden slik: lesfilknapp. addactionlistener ( new AbstractAction () { lesfil (); ); Programmet er listet i avsnitt A.1. Oppgave 2 Ta utgangspunkt i ko- Jobbing videre med frihåndstegne-programmet den MuseDemo.java og legg til følgende forbedringer: 1
Generell organisasjon av programmet Før jeg viser hvordan de enkelte delene i oppgaven er løst, vil jeg vise hvordan programmet er organisert. Programmet består av to klasser: Tegner og Tegnelerret (listet i avsnitt A.2 og A.3). Figur 1 viser programmet under kjøring. Figur 1: Skjermdump av Tegneprogrammet Klassen Tegnelerret er en subklasse av JPanel. Tegnelerrettet er i stand til å oppfatte hendelsen at en knapp på musa holdes nede, og at musa beveges. Når dette skjer, lagres posisjonen til musa i to arrayer, xpos og ypos, og en teller som angir antall punkter økes. Deretter kalles det på metoden repaint. Dette metodekallet er et signal til systemet om at det skal tegne opp komponenten på nytt. Systemet kaller da på metoden paintcomponent og sender med et objekt av typen Graphics, som representerer flaten det skal tegnes på. I Tegnlerret-klassen er paintcomponent-metoden overskrevet. Her går jeg igjennom alle koordinatene, og tegner strek mellom disse. Klassen Tegner er en subklasse av JFrame, og utgjør kontrolldelen av programmet. Tegnlerretklassen legges inn i CENTER-delen av JFramen. Knapper legges i en rekke til venstre (de samles i et eget JPanel som legges i WEST). Hver knapp har en egen anonym lytteklasse. Når brukeren for eksempel trykker på knappen for å slette tegningen (knappen merket clr ), kalles det på en metode (clear) på tegnelerretet. Figur 2 på neste side viser et forenklet klassediagram. Det opprinnelige programmet inneholder en bug hvis man tegner lenge nok, blir arrayene som holder på koordinatene fulle. Jeg har derfor lagt inn en metode som dobler størrelsen på arrayene når disse blir fulle. 2
JFrame JPanel Registrerer brukerens valg (via knapper) og oppdaterer attributtene i tegnlerretet. Eksempel på attributter som oppdateres er farge og tykkelse. Tegner Tegnelerret JButton Knapp Ansvar for tegningen. Registrerer musas bevegelser over komponenten, oppdaterer koordinatene og (via kall på repaint) tegner streker mellom koordinatene. Figur 2: Forenklet klassediagram for tegneprogrammet 3
a) Gjør det mulig å tegne flere uavhengigige streker (ikke bare en lang strek) Hint: du bør oppdage når noen slipper museknappen (du må implementere nok et grensesnitt) og da sette inn 0 i x- og y-arrayen for å signalisere slutt på et linjesegnent og begynnelse på neste. Samtidig i utttegningen må du aldri tegne hverken til eller fra verdien 0. Får du ikke til dette - se filen: Muse2Demo.java Løsningsforslag To endringer må til. I tillegg til å lytte på musebevegelser, må vi lytte på knappene på musa. Grensesnittet vi må implementere for dette heter MouseListener, og inneholder fem metoder. I midlertid er vi kun interessert i en av metodene, nemlig den som kalles når en museknapp slippes opp. I stedet for å implementere grensesnittet (og alle fem metodene) velger jeg derfor å lage lytteklassen som en subklasse av den abstrakte klassen MouseAdapter. MouseAdapter er en hjelpeklasse som gir en tom definisjon av alle metodene i MouseListenergrensesnittet. For eksempel er metoden mouseclicked implementert slik: public void mouseclicked ( MouseEvent e) { Fordelen med dette, er at jeg da kun trenger å skrive over den metoden jeg er interessert i, nemlig mousereleased: public void mousereleased ( MouseEvent e) { xpos [ ant ] = Integer. MIN_VALUE ; ypos [ ant ] = Integer. MIN_VALUE ; ant ++; Legg merke til at jeg bruker verdien Integer.MIN_VALUE som signal på at jeg ikke skal tegne en strek. I tegnemetoden sjekker jeg nå om det skal tegnes en strek mellom et punkt og forrige, ved ganske enkelt å teste mot verdien over i for-løkka som løper gjennom alle punktene: for ( int i = 1; i < ant ; i ++) { if ( xpos [i] > Integer. MIN_VALUE ) { g. drawline ( xpos [i - 1], ypos [i - 1], xpos [i], ypos [i ]); else { i ++; Legg merke til at hvis jeg ikke skal bruke punktet (dvs. at if-testen ikke slår til) økes verdien på i med to før neste iterasjon; én i else-grenen, og én som en del av forløkka. Effekten er at punktet hoppes over ved tegning. b) Legg farge på linjene. Hint: Innfør nok en array som hele tiden holder rede på hvilken fage et linjesegnemt har (se hvordag du setter farge i class Graphics i java-dok en). Du må selvsagt ogå innføre noen trykksnapper slik at brukeren kan velge farge ved f.eks å trykke på Rødknappen. 4
Løsningsforslag Jeg oppretter en array til som holder fargen for et gitt punkt. Metoden for å sette farge det skal tegnes med heter setcolor. For-løkka som gjør tegningen blir dermed slik: for ( int i = 1; i < ant ; i ++) { if ( xpos [i] > Integer. MIN_VALUE ) { g. setcolor ( farge[i ]); g. drawline ( xpos [i - 1], ypos [i - 1], xpos [i], ypos [i ]); else { i ++; c) Som b) men nå valg av tynnere eller tykkere linje.. Løsningsforslag Innfører en array til, som skal holde orden på tykkelsen til en gitt strek. En global variabel tykkelse økes eller minskes avhengig av hvilken knapp brukeren trykker. For å tegne en tykkere strek, bruker jeg metoden setstroke, som finnes i graphics-objektet. Dvs. - det finnes i et objekt av typen Graphics2D, som er en subklasse av Graphics. Objektet som blir sendt til paintcomponent-metoden har en metode som returnerer et nytt graphics-objekt (metoden create). Dette objektet kan omtypes ( castes ) til et Graphics2D objekt. I tillegg flytter jeg tegningen til en egen metode. De to metodene (paintcomponent og tegn blir slik: public void paintcomponent ( Graphics g) { super. paintcomponent (g ); Graphics2D g2d = ( Graphics2D ) g. create (); for ( int i =1; i< ant ; i ++) { if ( xpos [i] > Integer. MIN_VALUE ) { g2d. setcolor ( farge [i ]); tegn (g2d, i ); else { i ++; g2d. dispose (); void tegn ( Graphics2D g, int i) { float tykkelse = tykk [ i] + 1; // minste mulige tykkelse på en // strek er 1. g. setstroke ( new BasicStroke ( tykkelse )); // setter tykkelsen int x1 = xpos [i -1], x2 = xpos [i], y1 = ypos [i -1], y2 = ypos [i]; g. drawline (x1, y1, x2, y2 ); 5
d) Viskelær. Ved å trykke på en knapp som du legger inn, skal brukeren kunne skru av og på viskelærfunksjonen. Når den er på, skal alle punkter som er inenfor en bestemt radius (eks 10 pkt) fra musa, settes = 0. Hele tida mens du visker, skal effekten framvises på skjermen. (Bruk selvsagt Pytagoras til å finne avstanden mellom musas nåværende posisjon og alle punktene) Løsningsforslag Her velger jeg en litt annen fremgangsmåte en den som er antydet i oppgaven. Jeg innfører en bolsk variabel visk, som settes til true når brukeren velger viskelær. I tillegg øker jeg tykkelsen for punktet til 16, og setter fargen til å være den samme som bakgrunsfargen. e) Lagring og innlesning fra en fil på en tegning (bruk f.eks en dialog til å be om filnavmet). Løsningsforslag En dialog (inputdialog) lar brukeren skrive inn filnavn. Deretter skrives/leses antall punkter, og for hvert punkt, en linje med x-posisjonen, en linje med y- posisjonen, en linje med fargen og en linje med tykkelsen. For lesing/skriving bruker jeg klassene InExp og OutExp, som er superklasser til hhv. In- og Outklassene. Dette gjøres fordi jeg ønsker å kunne fange opp eventuelle feil som kan oppstå. Et eksempel på en slik feil er at brukeren ved lesing fra fil oppgir en fil som ikke finnes. Hvis det skjer, kastes et såkalt unntak (eng: Exception), som jeg kan fange opp. Unntak er behandlet i kapittel 19 i Rett på Java. A Programlistinger A.1 Editor.java import javax. swing.*; import java. awt.*; import java. awt. event.*; import easyio.*; import java.io.*; class Editor extends JFrame { JPanel filvindu ; JLabel filtekstlabel ; JTextField filnavnfelt ; JButton lesfilknapp ; JTextArea tekstvindu ; JButton lagrefilknapp ; String filnavn ; Editor () { initcomponents (); 6
layoutcomponents (); setdefaultcloseoperation( JFrame. EXIT_ ON_ CLOSE ); pack (); setvisible ( true ); void initcomponents () { filvindu = new JPanel (); // default flowlayout filtekstlabel = new JLabel (" Filnavn :"); filnavnfelt = new JTextField ( 20); filtekstlabel. setlabelfor ( filnavnfelt ); lesfilknapp = new JButton (" Les fil "); lesfilknapp. addactionlistener ( new AbstractAction () { lesfil (); ); filvindu. add ( filtekstlabel ); filvindu. add ( filnavnfelt ); filvindu. add ( lesfilknapp ); tekstvindu = new JTextArea (40, 60); tekstvindu. seteditable ( true ); tekstvindu. setlinewrap ( true ); tekstvindu. setwrapstyleword ( true ); lagrefilknapp = new JButton (" Lagre fil "); lagrefilknapp. addactionlistener ( new AbstractAction () { skrivfil (); ); void layoutcomponents () { Container lerret = getcontentpane (); lerret. add ( filvindu, BorderLayout. NORTH ); JScrollPane sc = new JScrollPane ( tekstvindu ); lerret. add (sc, BorderLayout. CENTER ); lerret. add ( lagrefilknapp, BorderLayout. SOUTH ); void skrivfil () { OutExp out = null ; String tmpfilnavn = filnavnfelt. gettext (); if ( tmpfilnavn. equals ( filnavn )) { int fortsett = JOptionPane. showconfirmdialog (this, " Gammel fil blir overskrevet \ nfortsett?", 7
" Skriv til fil ", JOptionPane. YES_ NO_ OPTION ); if ( fortsett == JOptionPane. NO_ OPTION ) { return ; filnavn = tmpfilnavn ; try { out = new OutExp ( filnavn ); out. outln( tekstvindu. gettext ()); catch ( IOException ioe ) { JOptionPane. showmessagedialog ( this, " Uventet feil ved skriving til fil " ); finally { if ( out!= null ) { out. close (); JOptionPane. showmessagedialog ( this, " Fil lagret." ); void lesfil () { filnavn = filnavnfelt. gettext (); InExp in = null ; try { in = new InExp ( filnavn ); tekstvindu. settext (""); while (! in. endoffile ()) { tekstvindu. append (in. inline () + "\n"); catch ( FileNotFoundException fne ) { JOptionPane. showmessagedialog (this, " Fant ikke filen" ); catch ( IOException ioe ) { JOptionPane. showmessagedialog ( this, " Uventet feil ved lesing fra fil." ); public static void main ( String [] args ) { new Editor (); A.2 Tegner.java import javax. swing.*; 8
import java. awt.*; import java. awt. event.*; import easyio.*; import java.io.*; class Tegner extends JFrame { Tegnelerret lerret ; Tegner () { super (" Tegneprogram "); lerret = new Tegnelerret (); getcontentpane (). add ( lerret, BorderLayout. CENTER ); JButton clr = new JButton (" clr " ); clr. addactionlistener ( new AbstractAction () { lerret. clear (); ); JButton rød = new JButton (" rød " ); rød. addactionlistener ( new AbstractAction () { lerret. setcolor ( Color. red ); ); JButton grønn = new JButton (" grønn "); grønn. addactionlistener ( new AbstractAction () { lerret. setcolor ( Color. green ); ); JButton sort = new JButton (" sort " ); sort. addactionlistener ( new AbstractAction () { lerret. setcolor ( Color. black ); ); JButton tykk = new JButton (" tykkere "); tykk. addactionlistener ( new AbstractAction () { lerret. tykkere (); ); JButton tynn = new JButton (" tynn " ); tynn. addactionlistener ( new AbstractAction () { lerret. tynnere (); ); JButton visk = new JButton (" viskelær "); 9
visk. addactionlistener ( new AbstractAction () { lerret. visk (); ); JButton tilfil = new JButton (" lagre "); tilfil. addactionlistener ( new AbstractAction () { tilfil (); ); JButton frafil = new JButton (" åpne "); frafil. addactionlistener ( new AbstractAction () { frafil (); ); JPanel knapper = new JPanel ( new GridLayout (0, 1)); knapper. add ( clr ); knapper. add ( visk ); knapper. add ( sort ); knapper. add ( rød ); knapper. add ( grønn ); knapper. add ( tykk ); knapper. add ( tynn ); knapper. add ( tilfil ); knapper. add ( frafil ); getcontentpane (). add ( knapper, BorderLayout. WEST ); pack (); setlocation ( 200, 200); setdefaultcloseoperation( JFrame. DISPOSE_ ON_ CLOSE ); setvisible ( true ); void tilfil () { int ant = lerret. getant (); int [] x = lerret. getxpos (); int [] y = lerret. getypos (); Color [] c = lerret. getcolor (); int [] t = lerret. gettykkelse (); String filnavn = JOptionPane. showinputdialog ( this, " filnavn : " ); OutExp fil = null ; try { fil = new OutExp ( filnavn ); 10
fil. outln( ant ); for ( int i = 0; i < ant ; i ++) { fil. outln(x[i ]); fil. outln(y[i ]); fil. outln(c[i]. getrgb ()); fil. outln(t[i ]); catch ( FileNotFoundException fn) { JOptionPane. showmessagedialog ( this, " Fant ikke filen "); catch ( IOException e) { JOptionPane. showmessagedialog ( this, " Feil ved skriving til fil "); finally { if ( fil!= null ) { fil. close (); public void frafil () { String filnavn = JOptionPane. showinputdialog ( this, " filnavn : " ); InExp fil = null ; try { fil = new InExp ( filnavn ); int ant = fil. inint (); int [] x = new int [ ant ]; int [] y = new int [ ant ]; Color [] c = new Color [ ant ]; int [] t = new int [ ant ]; for ( int i = 0; i < ant ; i ++) { x[i] = fil. inint (); y[i] = fil. inint (); c[i] = new Color ( fil. inint ()); t[i] = fil. inint (); lerret. setant ( ant ); lerret. setxpos (x); lerret. setypos (y); lerret. setcolor (c); lerret. settykkelse (t); lerret. repaint (); catch ( FileNotFoundException fn) { JOptionPane. showmessagedialog ( this, " Fant ikke filen "); catch ( IOException e) { JOptionPane. showmessagedialog ( this, " Feil ved skriving til fil "); 11
public static void main ( String [] args ) { new Tegner (); A.3 Tegnelerret.java import java. awt.*; import java. awt. event.*; import javax. swing.*; class Tegnelerret extends JPanel { final int xdim = 300, ydim =300; // Default størrelse på lerret int size = 1024; int [] xpos, ypos ; int ant = 0; Color [] farge ; int [] tykk ; Color color = Color. black ; int tykkelse = 0; int gmltykkelse = 0; boolean visk = false; Tegnelerret () { super ( true ); // DubbelBufferet, gjør tegningen mer effektiv. doinit (); dolayout (xdim, ydim ); * Indre lytteklasse, fanger opp to hendelser : 1) Musa " dras ", og * at knappen slippes. class Lytter extends MouseAdapter implements MouseMotionListener { // MouseAdapter public void mousereleased ( MouseEvent e) { if ( ant == xpos. length ) { doublesize (); xpos [ ant ] = Integer. MIN_VALUE ; ypos [ ant ] = Integer. MIN_VALUE ; tykk [ ant ] = tykkelse ; farge [ ant ] = color ; ant ++; 12
// MouseMotionListener public void mousedragged ( MouseEvent e) { if ( ant == xpos. length ) { doublesize (); xpos [ ant ]= e. getx (); ypos [ ant ]= e. gety (); farge [ ant ] = color ; tykk [ ant ] = tykkelse ; ant ++; repaint (); // MouseMotionListener ( bryr meg ikke om denne hendelsen ) public void mousemoved ( MouseEvent e) {; /* * Initierer arrayene void doinit () { xpos = new int [ size ]; ypos = new int [ size ]; farge = new Color[ size ]; tykk = new int [ size ]; Lytter l = new Lytter (); // indre klasse. addmousemotionlistener( l); addmouselistener ( l ); * Setter størrelsen på lerretet void dolayout ( int xdim, int ydim ) { setpreferredsize ( new Dimension ( xdim, ydim )); * Sletter tegninen void clear () { ant = 0; repaint (); * Setter fargen som det skal tegnes med void setcolor ( Color c) { if ( visk ) { 13
tykkelse = gmltykkelse ; visk = false; color = c; * Øker tykkelsen på streken void tykkere () { tykkelse ++; * Minsker tykkelsen på streken void tynnere () { if ( tykkelse > 0) tykkelse --; * Setter tykkelsen opp, og setter fargen til den samme som * bakgrunnsfargen. Resultatet er at det blir et viskelær. void visk () { visk = true ; color = getbackground (); gmltykkelse = tykkelse ; tykkelse = 16; * Tegner opp strekene mellom koordinatene. Blir kalt av systemet * når det er behov for å tegne komponenten på nytt. Det skjer * enten når systemet finner ut at komponenten trenger å tegnes * opp ( for eksmpel etter at vinduet har blitt flyttet ), eller * etter at metoden repaint har blitt kalt. public void paintcomponent ( Graphics g) { super. paintcomponent ( g); Graphics2D g2d = ( Graphics2D ) g. create (); // Henter ut et // Graphics2D - objekt, // som inneholder // litt fler // metoder enn det // " trauste " // Graphics - objektet // ( bl.a. metoden // for å sette // tykkelsen på // streken. for ( int i =1; i< ant ; i ++) { if ( xpos [i] > Integer. MIN_VALUE ) { g2d. setcolor ( farge [i ]); 14
tegn (g2d, i); else { i ++; g2d. dispose (); // Dokumentasjonen anbefaler at man kaller // denne metoden til slutt for å frigjøre // resurser. * Metoden som tegner strek mellom to punkter. void tegn ( Graphics2D g, int i) { float tykkelse = tykk [ i] + 1; // minste mulige tykkelse på en // strek er 1. g. setstroke ( new BasicStroke ( tykkelse )); // setter tykkelsen int x1 = xpos [i -1], x2 = xpos [i], y1 = ypos [i -1], y2 = ypos [i ]; g. drawline (x1, y1, x2, y2 ); * Returnerer antall punkter int getant () { return ant ; * Setter antall punkter void setant ( int ant ) { this. ant = ant ; * Returnerer alle x- koordinatene int [] getxpos () { return xpos ; * Setter alle x- koordinatene void setxpos ( int [] x) { xpos = x; * Returnerer alle y- koordinatene 15
int [] getypos () { return ypos ; * Setter alle y- koordinatene void setypos ( int [] y) { ypos = y; * Returnerer alle farger som brukes for hvert punkt Color [] getcolor () { return farge ; * Setter alle farger som skal brukes for hvert punkt void setcolor ( Color [] c) { farge = c; * Returnerer tykkelsen for hvert punkt int [] gettykkelse () { return tykk ; * Setter tykkelsen for hvert punkt void settykkelse ( int [] t) { tykk = t; * Hjelpemetode som dobler størrelsen på arrayene void doublesize () { int nsize = 2 * xpos. length ; int [] nx = new int [ nsize ]; int [] ny = new int [ nsize ]; Color [] nf = new Color [ nsize ]; int [] nt = new int [ nsize ]; System. arraycopy ( xpos, 0, nx, 0, xpos. length ); System. arraycopy ( ypos, 0, ny, 0, ypos. length ); System. arraycopy ( farge, 0, nf, 0, farge. length ); System. arraycopy ( tykk, 0, nt, 0, tykk. length ); 16
xpos = nx ; ypos = ny ; farge = nf ; tykk = nt ; size = nsize ; 17