De programmene vi har laget hittil har ikke hatt noe eget
programvindu (hovedvindu) på skjermen med grafiske skjermkomponenter (knapper,
tekstfelter etc.), slik vi er vant med fra programmer som
TextPad, Word, Excel etc. Det eneste vi har brukt av vinduer, er
dialogbokser (av type MessageDialog
for utskrift, og av type
InputDialog
og OptionDialog
for innlesing).
Det normale for et program er imidlertid
at det for programmet blir definert og opprettet et eget vindu på skjermen.
Dette vinduet vises kontinuerlig på skjermen så lenge programmet kjører.
Vinduet inneholder knapper, tekstfelter, tekstområder, menyer og andre typer grafiske
skjermkomponenter som brukeren behøver for å kjøre programmet.
Programmet blir avsluttet ved at brukeren lukker programvinduet. Vi skal nå
lære å lage slike programmer.
Når vi skal skrive et vindusbasert program, må vi bygge videre på
programkode som finnes i javas klassebibiotek: Vi må definere det som kalles
en subklasse til klassen
JFrame
(som vi importerer fra pakken javax.swing
).
Klassen JFrame
inneholder programkode for det vi kan kalle
rammeverket for et vindu. Det at vi definerer en subklasse til den, betyr at vi
bygger videre på den ved å supplere den med vår egen programkode, slik at vi
får lagt inn i vinduet de komponentene vi ønsker å ha der, og får definert
den funksjonaliteten som vi ønsker at vinduet skal ha.
En subklasse til klassen JFrame
får vi definert ved at vi
skriver extends JFrame
bak klassenavnet til klassen vi definerer.
(Subklasser får du lære mer om i kapittel 9.)
Det blir denne subklassen som blir vår vindusklasse, det vil si som definerer vinduet vårt.
I main
-metoden til programmet oppretter vi et objekt av den
vindusklassen vi definerer.
Nedenfor følger et eksempel i pseudo-kode som skisserer rammeverket for et slikt program.
import javax.swing.*; //importerer det vi trenger fra pakken //javax.swing, blant annet JFrame public class Vindu extends JFrame { < datafelter, blant annet for grafiske vinduskomponenter > public Vindu() // konstruktør { super( "Vinduseksempel" ); //parameteren kommer som tittellinje i vinduet < oppretter grafiske komponenter og bygger opp skjermbilde, eventuelt ved å gjøre kall på en egen metode for dette > setSize( 400, 300 ); //setter bredde og høyde for vinduet i antall pixler setVisible( true ); //gjør vinduet synlig på skjermen } < metoder etter ønsker og behov > }
Jeg minner om at konstruktører for klasser ble
omtalt i kapittel 3.
I programmets main
-metode må vi opprette et
objekt av den klassen som er definert. Det er tillatt å plassere
main
-metoden inni samme klassen, men det gir ryddigere
programmering å plassere den i en egen klasse:
public class Vindustest { public static void main( String[] args ) { Vindu vindu = new Vindu(); /* Kunne også her ha skrevet vindu.setSize( ..., ... ); //med parametre for ønsket størrelse vindu.setVisible( true ); i tilfelle det ikke var gjort i konstruktøren til Vindu. */ } }
Som første eksempel på et vindusbasert program skal vi omarbeide
programeksempel 4 fra kapittel 7 til et
vindusbasert program. Programmet demonstrerte bruk av arrayer som metodeparametre.
Resultatene fra programmet ble skrevet ut i et tekstområde. Dette ble til slutt
vist i en meldingsboks av type MessageDialog
. Programmet skal
ha samme funksjonalitet som før. Eneste endringen skal være at programmet får sitt eget vindu
på skjermen. I dette vinduet skal tekstområdet med programutskrift vises,
istedenfor at det vises i en meldingsboks, som jo egentlig bare er ment å være et
hjelpevindu for et annet programvindu. Det at et program får sitt eget vindu
på skjermen, kan vi blant annet se ved at det blir en tilhørende knapp på
oppgavelinja nederst på skjermen.
Klassen
Arraybehandler
som ble brukt i det opprinnelige
programmet kan brukes uten endringer i det nye programmet. Klassen
Arrayparametre
fra det opprinnelige programmet er omarbeidet til vindusklassen
Arraytestvindu
som er gjengitt nedenfor. Av grafiske komponenter skal ikke vinduet inneholde
annet enn tekstområdet for utskrift av programmets resultater. Tekstområdet
utskrift
blir deklarert på klassenivå og opprettet i klassens
konstruktør ved instruksjonen
utskrift = new JTextArea(10, 40);
Tidligere har vi opprettet tekstområder ved å bruke default-konstruktør. Det vil da automatisk bli laget akkurat stort nok til å romme den tekst vi legger inn i det, det vil si høyt nok til å romme de tekstlinjene vi legger inn, og bredt nok til å romme den lengste av disse. Men når vi skal legge tekstområdet inn i et egendefinert vindu, slik vi skal gjøre her, ønsker vi som regel selv å bestemme størrelse på vinduet. Da trenger vi vanligvis også å bestemme størrelse på tekstområder, og eventuelle andre komponenter, slik at de passer inn i vinduet vårt. Størrelse på tekstområder får vi bestemt ved to konstruktørparametre, som vist i instruksjonen ovenfor. Størrelsen avgjør ikke hvor mye tekst det er mulig å plassere i tekstområdet, men hvor mye som kan være synlig på skjermen samtidig. I dette tilfellet blir det 10 linjer i høyden og 40 tegn i bredden. Dette med bredde må vi imidlertid ta med en liten klype salt. Som vi vet, kan tegn ha litt forskjellig bredde, avhengig av hvilken skrifttype som brukes. Vi må derfor oppfatte 40 som en slags gjennomsnittsverdi.
Som nevnt, kan et tekstområde inneholde mer tekst enn det som kan vises
samtidig: det kan ha flere linjer, og linjer kan være så lange at det ikke er
plass til dem innenfor tekstområdets bredde på skjermen. Det vil da være
aktuelt å utstyre tekstområdet med skrollefelter, slik at brukeren kan
navigere i det for å komme fram til de forskjellige delene av teksten.
For å få til dette, legger vi tekstområdet inn i en komponent av type
JScrollPane
, ved å skrive følgende instruksjon:
JScrollPane skrollefelt = new JScrollPane(utskrift);
Det er da JScrollPane
-komponenten vi plasserer i vinduet,
istedenfor tekstområdet. Men det er fortsatt tekstområdet vi legger tekst inn
i (ved å bruke setText
eller append
)!
Grafiske skjermkomponenter ligger ikke direkte i selve vinduet, men i det som på
engelsk kalles vinduets contentPane. Siden dette er noe som kan
inneholde komponenter, kalles det en Container
. (Det er et objekt
av type Container
.) Vi får tak i den ved å skrive instruksjonen
Container c = getContentPane();
For å legge inn komponenter bruker vi
Container
-objektets
add
-metode ved at vi i vårt tilfelle skriver
c.add(skrollefelt);
Merknad 1 Du har kanskje merket deg at når vi har lagt inn tekstområde i meldingsbokser, så har vi ikke trengt å legge det inn i noen contentPane. Vi har skrevet en instruksjon som
JOptionPane.showMessageDialog( null, tekstområde, "Vindustittel", JOptionPane.PLAIN_MESSAGE );
Her gjør vi kall på metoden showMessageDialog
for å vise
dialogboksen med tekstområdet. Da er det denne metoden som sørger for å legge
inn grafiske komponenter i dialogboksens contentPane, blant annet tekstområdet
som metoden mottar via en parameter. Programkoden er derfor skjult for oss i
denne metoden. I det programmet vi nå holder på med, er det vi selv som må
skrive den nødvendige koden for å legge inn grafiske skjermkomponenter i
vinduet vårt.
Merknad 2 Fra og med java-versjon 1.5, eller 5.0 som den også kalles, er
det tilatt å bruke add
-metoden direkte for vindusklassen. Vi
kunne altså ha skrevet bare
add(skrollefelt);
for å legge tekstområdet inn i vinduet, uten å få tak i vinduets contentPane.
Men det som i virkeligheten da
skjer, er at denne add
-metoden henter fram vinduets
contentPane, slik vi har gjort, og gjør kall på add
-metoden for
dette, slik vi har gjort.
Instruksjonen
setSize(650, 250);
bestemmer størrelse for vinduet vårt, ved at bredde og høyde angis som metodeparametre. Måleenhet for størrelse på vinduer, vinduskomponenter og grafiske skjermkomponenter for øvrig, er piksel (eller pixel). Dette er en relativ måleenhet. Størrelsen er bestemt av hvilken oppløsning som er satt for skjermen. En typisk oppløsning er 1280 piksler i bredden og 1024 piksler i høyden. Grafiske skjermkomponenter er altså bygget opp av piksler. En piksel er det minste elementet på en bildeflate som kan tildeles farge og intensitet. Når vi setter størrelse på tekstområder, vinduer og andre komponenter, må vi ofte prøve oss litt fram for å komme fram til en størrelse som vi er fornøyd med.
Et vindu vil ikke bli gjort synlig på skjermen uten at programmet har en instruksjon for dette. Det er instruksjonen
setVisible(true);
Vindusklassen vår inneholder i tillegg til konstruktøren, som setter opp
vinduet og gjør det synlig, metoden demonstrerArrayparametre
.
I denne ligger den funksjonaliteten som vi i dette tilfelle ønsker at vinduet
skal ha. I andre programmer vil det selvsagt dreie seg om en eller flere andre metoder.
I dette tilfelle vil vi ha opprettet et Arraybehandler
-objekt,
gjort de metodekall vi ønsker for dette, og plassert utskrift i tekstområdet
som vises på skjermen. I vindusklassens konstruktør blir det gjort kall på
metoden demonstrerArrayparametre
for å få utført de instruksjonene
den inneholder. Legg merke til at siden vi trenger å referere til vinduets
tekstområde både i konstruktøren og i metoden
demonstrerArrayparametre
, så må tekstområdet deklareres
utenfor både konstruktøren og denne metoden, det vil si på klassenivå.
Programmets main
-metode er inneholdt i klassen
Arraytest
som er gjengitt nedenfor, etter vindusklassen. Det blir opprettet et vindu av den type vi har
definert. Vinduet vil se ut som vist på følgende bilde
De data som vises i vinduet vil imidlertid variere fra kjøring til kjøring, siden det er slumptall. Legg merke til at det i tillegg til lukkeknappen i vinduets øverste høyre hjørne er knapper for maksimering og minimering, slik vi ellers er vant til for vinduer. Dialogbokser er ikke utstyrt med slike knapper. Instruksjonen
vindu.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
bestemmer hvordan vinduets lukkeknapp skal virke. Den forhåndsprogrammerte virkemåten er at vinduet lukker seg når vi klikker på knappen, men vindusobjektet vil fortsatt eksistere og programmet blir ikke avsluttet. Den viste instruksjonen har som virkning at programmet blir avsluttet i tillegg til at vinduet lukker seg.
1 import javax.swing.*; 2 import java.awt.*; 3 4 public class Arraytestvindu extends JFrame 5 { 6 private JTextArea utskrift; 7 private Arraybehandler behandler; 8 9 public Arraytestvindu() 10 { 11 super("Arrayparametre"); 12 behandler = new Arraybehandler(); 13 Container c = getContentPane(); 14 utskrift = new JTextArea(10, 30); 15 utskrift.setEditable(false); 16 utskrift.setTabSize(5); 17 JScrollPane skrollefelt = new JScrollPane(utskrift); 18 c.add(skrollefelt); 19 setSize(650, 250); 20 setVisible(true); 21 } 22 23 public void demonstrerArrayparametre() 24 { 25 int tallgrense = 20; 26 //oppretter slumptallsliste og printer den ut 27 int[] liste = behandler.lagListe( 10, tallgrense ); 28 utskrift.setText("Illustrasjon av arrayparametres virkemåte " + 29 "sammenliknet med parametre av primitiv type\n"); 30 behandler.print( utskrift, "Opprinnelig array", liste ); 31 32 //reverserer lista og printer ut reversert liste 33 behandler.reverser( liste ); 34 utskrift.append( 35 "\nReverserer arrayen ved å bruke den som parameter " + 36 "i en metode som utfører reverseringen." ); 37 behandler.print( utskrift, "\nReversert array", liste ); 38 39 //printer ut siste listeelement 40 utskrift.append( 41 "\nSiste arrayelement " + liste[ liste.length - 1 ] + 42 " skal brukes som aktuell parameter en metode som dobler " + 43 "mottatt parameterverdi." ); 44 45 //viser virkning av dubleringsmetode 46 int doblet = behandler.dubler( liste[ liste.length - 1 ] ); 47 utskrift.append( "\nDoblet verdi av aktuell parameter: " + 48 doblet ); 49 utskrift.append( "\nAktuell parameter har beholdt sin verdi: " + 50 liste[ liste.length - 1 ] ); 51 } 52 }
1 import javax.swing.JFrame; 2 3 public class Arraytest 4 { 5 public static void main( String[] args ) 6 { 7 Arraytestvindu vindu = new Arraytestvindu(); 8 vindu.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 9 vindu.demonstrerArrayparametre(); 10 } 11 }
Legg merke til at vi i dette programmet kunne bruke klassen
Arraybehandler
om igjen uten endringer. Det er denne som inneholder
det som i datasjargong blir kalt for programlogikken. Den inneholder metoder
som foretar diverse operasjoner på en array, uten tanke på hvordan resultatene
av disse skal vises for brukeren, bortsett fra at den har en metode som
tilføyer innholdet av mottatt array i et tekstområde som også mottas. Hvordan
resultatene fra programmet skal vises for brukeren blir bestemt av andre klasser
som hører til programmet. De definerer det som kalles programmets brukergrensesnitt.
En slik oppdeling av programmet i moduler på den måten
at vi får skilt programlogikk fra kode for brukergrensesnitt, legger til rette
for fleksibilitet og mulighet for gjenbruk av programkode.
Når en bruker av et dataprogram bruker tastatur eller mus, skjer en eller
flere hendelser (engelsk: events). Eksempler på hendelser er:
en tastaturtast blir trykket ned, tasten blir sluppet opp igjen, tilsvarende for
museknapper, musa flyttes (med eller uten en museknapp trykket ned),
musemarkøren kommer inn i et vindu, markøren går ut av et vindu, etc.
Programmet kan registrere
hendelsene. Hva som utføres videre av programmet, vil være avhengig av hvilke
typer hendelser som skjer og på hvilken måte programmet behandler dem.
I de programmene vi har hatt hittil, er det bare noen ganger at vi har
skrevet inn data som er blitt brukt av programmet. Håndteringen av
hendelser har da vært skjult i klassen
JOptionPane
som er blitt brukt ved
innlesing. Vi skal nå begynne å lage programmer der vi selv programmerer
hendelseshåndteringen.
For å håndtere hendelser, må vi definere lytteobjekt. Et lytteobjekt 'lytter på' hendelser, det vil si venter på at det skal skje en hendelse. Hver gang den skjer, utfører lytteobjektet de instruksjonene det er programmert til å gjøre. Hvordan dette virker, kan best forklares ved at vi tar for oss et programeksempel. Som eksempel skal vi bruke en omarbeidet versjon av et tidligere program.
I et programeksempel i kapittel 4 ble det lest inn tid i sekunder. Programmet skrev ut resultatet av å konvertere den innleste tiden til timer, minutter og sekunder. Både innlesing av sekunder og utskrift av resultat skjedde i dialogbokser. Programmet gikk i en løkke inntil det ble lest inn et negativt antall sekunder. Vi skal nå omarbeide dette programmet til et hendelsesbasert program. Programvinduet som vi skal lage, kan du se på figuren nedenfor. Hver gang brukeren skriver en heltallsverdi i tekstfeltet og trykker på returtasten, skal programmet lese inn dette tallet, konvertere det til timer, minutter og sekunder, og tilføye resultatet i tekstområdet. Istedenfor at programmet avsluttes med å skrive inn et negativt antall sekunder, skal det nå avsluttes ved at brukeren lukker programvinduet.
Det er nødvendig med ganske omfattende endringer av det opprinnelige
programmet, samtidig som vi må introdusere en del nye programmeringselementer.
I det opprinnelige programmet ble alle instruksjoner utført av
main
-metoden. I et vindusbasert program er det
vanlig at main
-metoden har som hovedoppgave å opprette objektet for programvinduet.
Ofte er det den eneste oppgaven den har. Videre er det, som nevnt foran, gunstig å dele
opp programmet i moduler slik at kode som har med brukerkommunikasjon og visning
av resultater å gjøre skilles fra kode som har med behandling av data å gjøre.
En slik oppdeling skal vi også gjøre i dette programmet.
Vi tar først for oss
den delen av programmet som skal behandle data. Det som skal gjøres av
databehandling i dette programmet, er altså å konvertere et antall sekunder
til timer, minutter og sekunder.
Det naturlige vil være å definere en egen metode
til dette formål, siden det er en klart avgrenset oppgave. Dessuten er det
en oppgave som skal kunne utføres gjentatte ganger. Det vil være naturlig
at en slik konverteringsmetode mottar i form av en parameter det antall
sekunder den skal konvertere og at metoden returnerer i form av en tekst
resultatet av konverteringen. Selve konverteringsinstruksjonene blir som i
det opprinnelige programmet. I den returnerte teksten ønsker vi å raffinere
litt i forhold til det opprinnelige programmet, ved at vi nå skal skille mellom
entall og flertall i benevningene. Dessuten ønsker vi bare å ta med positive
antall i utskriften. Siden dette med å foreta en slik konvertering er noe som
også kan være aktuelt å gjøre i andre sammenhenger, velger vi å definere en
egen klasse som inneholder konverteringsmetoden som en verktøymetode, det vil
si som en static
-metode. Klassen
Tidskonverterer
med konverteringsmetoden
konvertertTid
er gjengitt nedenfor.
1 public class Tidskonverterer 2 { 3 //returnerer parameterverdien konvertert til timer, 4 //minutter og sekunder 5 public static String konvertertTid( int sekunder ) 6 { 7 if ( sekunder >= 0 ) 8 { 9 int timer = sekunder / 3600; 10 sekunder = sekunder % 3600; 11 int minutter = sekunder / 60; 12 sekunder = sekunder % 60; 13 14 String resultat = ""; 15 if ( timer > 0 ) 16 { 17 resultat += timer + " time"; 18 if ( timer > 1 ) 19 resultat += "r"; 20 resultat += " "; 21 } 22 if ( minutter > 0 ) 23 { 24 resultat += minutter + " minutt"; 25 if ( minutter != 1 ) 26 resultat += "er"; 27 resultat += " "; 28 } 29 if ( sekunder > 0 ) 30 { 31 resultat += sekunder + " sekund"; 32 if ( sekunder > 1 ) 33 resultat += "er"; 34 } 35 return resultat; 36 } 37 else 38 return "Konverterer ikke negativ tid!"; 39 } 40 }
Det neste vi skal se på er vindusklassen
Konverteringsvindu
.
Fullstendig kode for denne er gjengitt nedenfor. Nærmere kommentarer og
forklaringer til koden kommer etter kildekoden.
1 import javax.swing.*; 2 import java.awt.*; 3 import java.awt.event.*; 4 5 public class Konverteringsvindu extends JFrame implements ActionListener 6 { 7 private JTextArea utskriftsområde; //for utskrift av resultater 8 private JTextField skrivefelt; //for input 9 private JLabel skrivefelttekst; //for tekst foran skrivefeltet 10 11 public Konverteringsvindu() 12 { 13 super( "Tidskonvertering" ); 14 utskriftsområde = new JTextArea( 10, 30 ); 15 JScrollPane skrollområde = new JScrollPane( utskriftsområde ); 16 skrivefelt = new JTextField( 10 ); 17 skrivefelt.addActionListener( this ); //registrerer vindusobjektet 18 //som lytteobjekt for skrivefeltet 19 skrivefelttekst = new JLabel( "Sekunder som skal konverteres:" ); 20 Container c = getContentPane(); //vinduets komponentkontainer 21 c.setLayout( new FlowLayout() ); //komponentene skal plasseres etter 22 //hverandre rad for rad 23 c.add( skrivefelttekst ); //plasserer komponentene i kontaineren 24 c.add( skrivefelt ); //i den rekkefølge vi vil ha dem 25 c.add( skrollområde ); 26 utskriftsområde.setText( "Konverterte sekunder\n" ); 27 } 28 29 //metode som blir kalt opp hver gang det trykkes på retur-tasten 30 //mens markøren står i skrivefeltet 31 public void actionPerformed( ActionEvent e ) 32 { 33 String input = skrivefelt.getText(); //leser inn det som 34 //står i tekstfeltet 35 int sek = Integer.parseInt( input ); 36 String utskrift = input + " = " + 37 Tidskonverterer.konvertertTid( sek ); 38 utskriftsområde.append( utskrift + "\n" ); 39 } 40 }
Klassen Konverteringsvindu
definerer programvinduet. Når
programmet skal kjøres, må det opprettes
et objekt av den typen klassen definerer. Vi vet at det da er instruksjonene i
klassens konstruktør som utføres først. Konstruktøren har som
oppgave å foreta nødvendig initialisering, i dette tilfelle blant annet å opprette grafiske
komponenter og legge disse ut i vinduet. I vårt vindu er det tre
grafiske komponenter:
Ledeteksten er en komponent av type JLabel
,
mens tekstfeltet er en komponent av type JTextField
.
Tekstområdet, som er av type JTextArea
, kjenner vi
fra tidligere. Det blir på klassenivå
deklarert variable for de tre komponentene:
JTextArea utskriftsområde; //for utskrift av resultater JTextField skrivefelt; //for input JLabel skrivefelttekst; //for tekst foran skrivefeltet
Merk at det her er nødvendig å deklarere på klassenivå de to første av disse.
Grunnen er at vi trenger å referere til dem i klassens metode
actionPerformed
.
Den tredje blir det bare referert til i konstruktøren. Denne kunne derfor ha vært
deklarert lokalt i denne. Alle de tre komponentene blir opprettet i
konstruktøren ved bruk av
new
-operatoren.
Ledeteksten blir opprettet ved instruksjonen
skrivefelttekst = new JLabel( "Sekunder som skal konverteres:" );
Vi ser at den ønskede teksten skrives som konstruktørparameter ved opprettelsen av objektet.
Tekstfeltet opprettes ved instruksjonen
skrivefelt = new JTextField( 10 );
Et tekstfelt kan bare inneholde én tekstlinje. Ved opprettelsen angir vi hvor mange tegn som vi ønsker skal være synlig (i bredden) samtidig. Det er ingen begrensning på hvor mange tegn tekstfeltet kan inneholde. Dersom det inneholder flere tegn enn det antall (i vårt tilfelle 10) som kan være synlig samtidig, kan vi navigere i tekstfeltet ved å bruke piltastene, slik at vi får sett de andre tegnene.
Når det gjelder tekstområdet, ønsker vi at det automatisk skal utstyres med skrollefelter i tilfelle det kommer til å inneholde mer tekst enn det er plass til å vise samtidig, slik at vi ved å bruke skrollefeltene kan navigere oss fram til tekst som ikke vises. For å få til dette, går vi fram som i foregående programeksempel.
Som i det foregående programeksempel, må vi, for at komponentene skal synes på
skjermen, først plassere dem i vinduets komponent-kontainer. Til forskjell fra
forrige eksempel skal vi her ikke bare legge inn én komponent, men
flere. Det er da nødvendig å angi hvordan vi ønsker å plassere dem. Det gjøres
ved å sette en layout for komponent-kontaineren. Foreløpig skal vi nøye oss med
å bruke den enkleste typen layout. Den kalles
FlowLayout
. I kildekoden henter vi fram
komponent-kontaineren og setter layout på den ved instruksjonene
Container c = getContentPane(); //vinduets komponentkontainer c.setLayout( new FlowLayout() ); //komponentene skal plasseres etter //hverandre rad for rad
Som det står i kommentaren, vil komponentene bli plassert etter hverandre
rad for rad i den rekkefølge vi skriver
add
-instruksjonene. Vi ønsker først
ledeteksten, så tekstfeltet, og til slutt tekstområdet. Derfor skriver vi nå
følgende instruksjoner:
c.add( skrivefelttekst ); //plasserer komponentene i kontaineren c.add( skrivefelt ); //i den rekkefølge vi vil ha dem c.add( skrollområde ); utskriftsområde.setText( "Konverterte sekunder\n" );
Merk deg at når det gjelder tekstområdet, så er det det omsluttende
JScrollPane
-objektet vi legger inn i
komponent-kontaineren, ikke selve
JTextArea
-objektet. Når vi derimot skal
plassere tekst i tekstområdet, så er det
JTextArea
-objektet vi refererer til.
Den siste instruksjonen plasserer en starttekst i utskriftsområdet.
For å oppnå at ledetekst og tekstfelt kommer på samme rad, mens tekstområdet
kommer på neste rad, må vi (når vi bruker
FlowLayout
) tilpasse vinduets bredde. Instruksjon
for dette skal vi i dette eksemplet plassere i programmets main
-metode.
Instruksjonen kunne isteden stått i vindusklassens konstruktør, slik det ble
gjort i foregående programeksempel. Ofte må vi prøve oss fram for å finne
riktig bredde (og høyde).
Merknad Fra og med javaversjon 5 er det tillatt å sette layout for vinduet direkte, uten å hente fram komponentkontaineren. Vi kunne altså ha skrevet
setLayout( new FlowLayout() );
Dette betyr at vi gjør kall på vindusobjektets setLayout
-metode. Den vil i sin
tur gjøre kall på komponentkontainerens setLayout
-metode, som vi har gjort
direkte ovenfor. Sluttresultatet vil derfor bli det samme.
Tilsvarende gjelder, som allerede nevnt, for add
-metoden.
Gjør vi kall på vindusobjektets add
-metode, vil den i sin tur
gjøre kall på komponentkontainerens add
-metode, slik at sluttresultatet
blir det samme.
***
Programmet skal virke på den måten at når vi har skrevet et tall i
tekstfeltet og trykker retur-tast, så skal programmet lese inn tallet og
bruke det som aktuell parameter i et kall på metoden
konvertertTid
. Returverdien fra denne skal
tilføyes som ny tekst i tekstområdet. For å få til dette, må vi gjøre følgende:
Det mest ryddige er å definere lytteobjekter som ikke har noen annen
oppgave ved siden av. Vi skal imidlertid foreløpig, for enkelhets skyld, bruke
selve vindus-objektet som lytteobjekt.
For at vindus-objektet skal kunne være
lytteobjekt, må vi i klassedefinisjonen gjøre tilføyelsen
implements ActionListener
, slik at første
linje i klassedefinisjonen nå lyder
public class Konverteringsvindu extends JFrame implements ActionListener
ActionListener
er et såkalt
interface
definert i klassebibliotekets pakke
java.awt.event
. Denne må vi derfor importere til
programmet ved at vi forrest i fila skriver
import
-instruksjonen
import java.awt.event.*;
Den nevnte tilføyelsen i klassedefinisjonen medfører at vi i klassen vår må definere en metode som har signatur
public void actionPerformed( ActionEvent e )
Det er denne som skal kalles opp hver gang lytteobjektet blir aktivert. Den må
derfor inneholde de instruksjoner som vi ønsker utført hver gang lytteobjektet
blir aktivert. Ovenfor er det beskrevet når lytteobjektet ønskes
aktivert og hvilke instruksjoner som da skal utføres. Vår
actionPerformed
-metode kan derfor skrives som
følger:
31 public void actionPerformed( ActionEvent e ) 32 { 33 String input = skrivefelt.getText(); //leser inn det som 34 //står i tekstfeltet 35 int sek = Integer.parseInt( input ); 36 String utskrift = input + " = " + 37 Tidskonverterer.konvertertTid( sek ); 38 utskriftsområde.append( utskrift + "\n" ); 39 }
Innlesing fra et tekstfelt får vi gjort ved å gjøre kall på
tekstfeltets metode getText
slik det er gjort i instruksjonen
skrivefelt.getText();
Den innleste verdien blir alltid returnert fra metoden i form av en
String
. Den fanger vi derfor opp i en
String
-variabel. Siden det i dette programmet
skal være en heltallsverdi som leses inn, må vi konvertere den innleste
sifferstrengen til en int
-verdi ved å bruke
den som parameter i et kall på
parseInt
-metoden.
Merk deg kallet på metoden konvertertTid
i instruksjonen
String utskrift = input + " = " + Tidskonverterer.konvertertTid( sek );
Siden vi har definert metoden konvertertTid
som en
static
-metode
i klassen Tidskonverterer
, må vi bruke klassenavnet som prefiks
ved metodekallet.
Husk at den teksten som metoden returnerer når den kalles opp blir returnert til
kallstedet. Teksten blir i dette tilfelle tilføyd til den tekst som står foran
på samme linje. Instruksjonen
utskriftsområde.append( utskrift + "\n" );
gjør kall på tekstområdets metode append
.
Denne kjenner vi fra tidligere. Jeg minner om at
den har som virkning at den tekst
som er aktuell parameter blir tilføyd i tekstområdet i tillegg til den
tekst som (eventuelt) er i tekstområdet fra før av.
Det er imidlertid fortsatt noe som mangler for at vindusobjektet skal fungere som lytteobjekt for tekstfeltet vårt: Vi må legge inn en instruksjon som registrerer vindus-objektet som lytteobjekt for tekstfeltet. Det gjøres av instruksjonen
skrivefelt.addActionListener( this );
som du finner i konstruktøren.
Nøkkelordet this
vil i dette tilfelle være
en referanse (også kalt peker) til vindusobjektet.
Det som gjenstår for å få et kjørbart program, er en main
-metode
som oppretter et vindusobjekt av den typen som klassen Konverteringsvindu
definerer og gjør dette synlig på skjermen. Metoden må dessuten sette størrelse på
vinduet, for det er ikke gjort i vindusklassens konstruktør. Klassen
Konverteringstester
som er gjengitt nedenfor inneholder det som er nødvendig.
1 import javax.swing.JFrame; 2 3 public class Konverteringstester 4 { 5 public static void main( String[] args ) 6 { 7 Konverteringsvindu vindu = new Konverteringsvindu(); 8 vindu.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 9 vindu.setSize( 400, 250 ); 10 vindu.setVisible( true ); 11 } 12 }
I forbindelse med trafikk er det vanlig å angi fart med måleenheten kilometer per time, km/h. Men den metriske måleenheten for fart er meter per sekund, m/s. Noen ganger er det aktuelt å regne om fra km/h til m/s. Siden det er 1000 meter i hver kilometer og 3600 sekunder i hver time, kan vi bruke formelen
v * 1000 / 3600
når vi vil foreta denne omregningen. I denne formelen står v
for en fart uttrykt i km/h. Formelen gir den tilsvarende farten uttrykt i m/s.
Definer en metode som beregner og returnerer en fart uttrykt i m/s når
metoden mottar via en parameter en fart uttrykt i km/h. NB! Dersom formelen
ovenfor skal virke riktig når du skriver den i java, må variabelen
v
være av type double
.
Dersom den er av type int
,
vil det bli foretatt heltallsdivisjon. Du vil da vanligvis
ikke få riktig resultat.
Lag et hendelsesbasert program som kan lese inn fra et tekstfelt verdier for fart uttrykt i km/h. Programmet skal skrive ut i et tekstområde tilsvarende fart uttrykt i m/s, en ny linje for hver fart som leses inn. Programmet skal bruke metoden som du definerte tidligere i oppgaven.
Lag et hendelsesbasert program som virker på følgende måte: Fra et tekstfelt
kan programmet lese inn en terningverdi, altså et helt tall fra 1 til 6.
Hver gang det er lest inn en slik verdi, skal programmet simulere 6000 terningkast,
telle opp og skrive ut i et annet tekstfelt hvor mange ganger kastene resulterte
i den innleste verdien. Dersom den innleste verdien er mindre enn 1 eller større
enn 6, skal programmet skrive ut en feilmelding istedenfor å foreta kastene.
Til å representere terningen, kan du bruke et objekt av klassen
Terning
som ble definert i
eksempel 2 i kapittel 6.
En svært vanlig vinduskomponent i tillegg til tekstfelter, tekstområder og ledetekster, er knapper til å klikke på. Det finnes egentlig flere typer av slike, men den vanligste er den som vi ser eksempel på til venstre i følgende programvindu.
Programmet virker på den måten at hver gang brukeren klikker på knappen Trekk måned, så trekkes det en tilfeldig av årets tolv måneder og månedens navn skrives ut i tekstfeltet. Programmet har derfor tilsvarende virkemåte som programeksempel 1 i kapittel 7, men det er nå omarbeidet til et hendelsesbasert vindusprogram. Vi skal se nærmere på de endringer som er gjort.
Klassen
Trekningsautomat
kan vi beholde som den er. Det er den som inneholder koden for å foreta selve
trekningen. Det som må endres, er den delen av programmet som foretar
brukerkommunikasjonen. Denne skal nå foregå i programvinduet, ikke via
dialogbokser. (Vi har her et nytt eksempel på at når vi programmerer
brukerkommunikasjon og behandling av data i hver sine moduler, så er det større
sjanse for at vi kan bruke kode om igjen.) Innholdet i fila
Maanedstrekningsvindu.java
som definerer programvinduet er gjengitt nedenfor.
1 import javax.swing.*; 2 import java.awt.*; 3 import java.awt.event.*; 4 5 6 public class Maanedstrekningsvindu extends JFrame implements 7 ActionListener 8 { 9 private Trekningsautomat lykkehjul; 10 private JTextField utskrift; 11 private JButton trekk; 12 13 public Maanedstrekningsvindu() 14 { 15 super( "Månedstrekning" ); 16 lykkehjul = new Trekningsautomat(); 17 JLabel ledetekst = new JLabel( "Trukket måned: " ); 18 utskrift = new JTextField( 12 ); 19 utskrift.setEditable( false ); //gjør tekstfeltet ikke-editerbart 20 trekk = new JButton( "Trekk måned" ); 21 trekk.addActionListener( this ); //registrerer vinduet som 22 //lytteobjekt for knappen 23 Container c = getContentPane(); 24 c.setLayout( new FlowLayout() ); 25 c.add( trekk ); 26 c.add( ledetekst ); 27 c.add( utskrift ); 28 } 29 30 //Kalles opp hver gang brukeren klikker på knappen trekk. 31 public void actionPerformed( ActionEvent e ) 32 { 33 String måned = lykkehjul.trekkMåned(); 34 utskrift.setText( måned ); 35 } 36 }
Vi ser at vindusklassen har samme struktur som i foregående programeksempel.
Når det gjelder tekstfeltet
utskrift
, skal det i dette programmet bare brukes til utskrift av
trukket måned, det er ikke meningen at brukeren skal skrive noe i
tekstfeltet. Vi kan hindre brukeren i å kunne gjøre dette ved at vi
skriver instruksjonen
utskrift.setEditable( false );
Tekstfelter og tekstområder vil i utgangspunktet være editerbare, det vil si at brukeren kan skrive i dem. Instruksjonen ovenfor gjør tekstfeltet ikke-editerbart. Vi kan gjøre det editerbart igjen ved instruksjonen
utskrift.setEditable( true );
Tilsvarende gjelder for tekstområder. Under programmets kjøring kan vi veksle mellom å ha tekstfelter og tekstområder editerbare og ikke-editerbare så mange ganger vi bare vil.
Det vesentlig nye i programmet er bruk av knapp til å klikke på. Dette ser vi nærmere på.
Bruk av knapper
En vanlig knapp til å klikke på er et objekt av
type JButton
. For å opprette knappen med tekst
"Trekk måned"
som er vist i vinduet, kan man bruke instruksjonen
JButton trekk = new JButton( "Trekk måned" );
I vårt program er det imidlertid behov for å referere til knappen både i
klassens konstruktør og i
actionPerformed
-metoden. Knappeobjektet må derfor deklareres på
klassenivå, mens opprettelsen av knappeobjektet skjer i konstruktøren.
Siden programmet vårt skal virke på den måten at det blir trukket en ny
måned hver gang vi klikker på knappen, må det være knyttet et lytteobjekt til
knappen, slik at dette blir aktivert hver gang det blir klikket på den. Til
knapper kan vi knytte lytteobjekt på nøyaktig samme måte som vi gjorde for
tekstfelt i foregående programeksempel. Som der er det i metoden
actionPerformed
vi må legge inn de instruksjonene vi ønsker utført
når brukeren klikker på knappen. I dette tilfelle er det kode for å trekke en
måned og skrive ut resultatet i tekstfeltet. Vi plasserer tekst i et tekstfelt
ved å bruke dens setText
-metode. Eventuell eksisterende tekst vil
da bli erstattet med den som vi bruker som parameter til metoden. (På tilsvarende
måte virker det for tekstområder. Men for slike kan vi også tilføye ny tekst
til eksisterende tekst ved å bruke append
-metoden.)
For å få et kjørbart program, må vi ha en main
-metode.
Den har tilsvarende innhold som i foregående programeksempel og er vist
nedenfor i klassen
Maanedstrekker
1 import javax.swing.JFrame; 2 3 public class Maanedstrekker 4 { 5 public static void main( String[] args ) 6 { 7 Maanedstrekningsvindu vindu = new Maanedstrekningsvindu(); 8 vindu.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 9 vindu.setSize( 400, 80 ); 10 vindu.setVisible( true ); 11 } 12 }
Lag et hendelsesbasert program som kan beregne arealer for rektangler. Programmet skal fra hvert sitt tekstfelt lese inn rektangelets bredde og høyde i form av desimaltall. Innlesing og beregning av areal skal skje som følge av at brukeren klikker på en knapp med passende tekst på. Det beregnede arealet skal skrives ut i et tredje tekstfelt som er ikke-editerbart. Bruk passende ledetekster foran tekstfeltene. For selve arealberegningen skal det defineres en egen metode med parametre for bredde og høyde, og som returnerer arealet. Metoden skal sjekke at bredde og høyde ikke er negative. Dersom minst én av dem er negativ, skal metoden returnere tallverdien null som verdi for arealet.
Copyright © Kjetil Grønning og Eva Hadler Vihovde, revidert 2014