Forrige kapittel Neste kapittel

Vindusbaserte programmer

Innledning

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.

Rammeverk for vindusbasert program

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.

Eksempel

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.
    */
  }
}

Programeksempel 1: Bruke vindu istedenfor meldingsboks

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 }

Merknad

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.

Hendelsesbaserte programmer

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.

Programeksempel 2: tidskonvertering

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 }

Oppgave 1

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.

Oppgave 2

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.

Programeksempel 3: Bruk av knapper

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 }

Oppgave 3

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.

Forrige kapittel Neste kapittel

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