Forrige avsnitt Neste avsnitt  Start på kapittel om spesialiserte komponenter for grafiske brukergrensesnitt

Bruk av formaterte tekstfelter: JFormattedTextField-objekter

Subklassen JFormattedTextField til JTextField definerer et såkalt formatert tekstfelt. Et formatert tekstfelt gjør det mulig å spesifisere hva som skal oppfattes som lovlige tegn i tekstfeltet og ignorere andre tegn. Et JFormattedTextField tilføyer en formaterer og en verdi (av objekttype) til egenskapene som arves fra JTextField. Det finnes forskjellige typer formaterere. Formatereren foretar konvertering mellom feltets nevnte verdi og hva som vises på skjermen, som er tekstfeltets tekst. Og motsatt: Det som skrives i tekstfeltet vil formatereren forsøke å konvertere til ny verdi for tekstfeltet (uten at vi trenger å legge inn spesielle try-catch-instruksjoner). Ved å bruke de formatererne som finnes i klassebiblioteket, kan vi for eksempel definere formaterte tekstfelter som kan vise og lese inn datoer og tallverdier på det format som er standard på vedkommende sted (det vil si i Norge for vårt vedkommende). Det er også mulig å sette opp masker som definerer hva som er lovlige tegn i hver posisjon i tekstfeltet, slik at vi for eksempel kan lese inn og vise telefonnumre eller fødselsnumre i et bestemt format.

Som eksempel på bruk av formaterte tekstfelter skal vi ta for oss et program som står i The Java Tutorials. Programmet er en enkel "lånekalkulator", der det er formaterte tekstfelter for lånebeløp, rentefot, løpetid og månedlig lånekostnad. Vi ser først på hvordan vi kan opprette formaterere.

Spesifisering av format

Den abstrakte klassen Format har de to konkrete subklassene DateFormat og NumberFormat for formatering av henholdsvis datoer og tall, begge på en slik måte at det tilpasses det som er standard på vedkommende sted (det vil si i Norge for oss). Den første av disse brukes for øvrig i notatet Dato og tid. Vi kan også bruke klassen SimpleDateFormat som er omtalt i samme notat. Enkleste måten å få opprettet formateringsobjekt på, er å bruke en av verktøymetodene som disse klassene har, og deretter eventuelt tilpasse dette objektet. Som eksempel på dette skal vi ta for oss de tallformatene som brukes av det kommende programeksemplet.

Eksempler på tallformaterere

  NumberFormat  amountFormat = NumberFormat.getNumberInstance();
    //Gir korrekt formatering av tall, med korrekt gruppeseparator og
    //desimaltegn, inntil 3 desimaler. Eksempel: 1 234 567,5

  NumberFormat  percentFormat = NumberFormat.getNumberInstance();
  percentFormat.setMinimumFractionDigits(3);
    //Gir alltid minst 3 desimaler. Eksempel: 7,500

  NumberFormat  paymentFormat = NumberFormat.getCurrencyInstance();
    //Gir korrekt formatering av pengebeløp. Eksempel: kr 1 398,43

Opprette formatert tekstfelt

Når vi har fått opprettet det formateringsobjektet vi ønsker å bruke, får vi opprettet et formatert tekstfelt som bruker denne formatereren ved å bruke formateringsobjektet som konstruktørparameter:

  JFormattedTextField f = new JFormattedTextField(formaterer);

Det er imidlertid også mulig å bruke default-konstruktør og standardformaterer i tilfelle vi ikke har behov for å tilpasse formatereren til spesielle ønsker:

  JFormattedTextField standardfelt = new JFormattedTextField();

Når vi nå setter for eksempel en heltallsverdi for tekstfeltet, så vil denne bli formatert ved bruk av en standardformaterer for heltall. På denne måten er det gjort for løpetiden for det kommende programeksemplet med den nevnte lånekalkulatoren:

     numPeriodsField = new JFormattedTextField();
     numPeriodsField.setValue(new Integer(numPeriods));
     numPeriodsField.setColumns(10);

Sette verdi og hente verdi for det formaterte tekstfeltet

Som nevnt tidligere, har et formatert tekstfelt både en tekst (den som vises i tekstfeltet, tilsvarende som for JTextField) og en verdi (som settes på grunnlag av teksten). Dette er to forskjellige egenskaper for tekstfeltet, og som regel vil oppdatering av tekstfeltets verdi være forsinket i forhold til oppdatering av teksten. Mens brukeren skriver input i tekstfeltet, vil tekstegenskapen endre seg, mens verdiegenskapen ikke endrer seg før endringene er såkalt bekreftet (engelsk: committed). Det skjer dersom brukeren trykker på returtasten mens tekstfeltet har fokus (markøren er i feltet), eller dersom tekstfeltet mister fokus (for eksempel ved at markøren flyttes til et annet felt).

Det finnes imidlertid to forskjellige metoder som kan endre verdien til et formatert tekstfelt: setValue og commitEdit. Metoden setValue setter verdien til den verdi som er aktuell parameter ved metodekallet. Denne kan teknisk sett være et hvilket som helst Object, men det må være av en type som formatereren kan konvertere til en streng. Ellers vil det oppstå en IllegalArgumentException.

Metoden commitEdit setter verdien til det objekt som formatereren tolker som representert av feltets tekst. Hvordan dette virker i praksis når det befinner seg ugyldige tegn i feltet (for eksempel i en tekst som skal representere et tall), kan en se ved å prøve ut det følgende programeksemplet. Metoden commitEdit blir kalt opp automatisk i disse to tilfellene:

Når det blir satt ny verdi for det formaterte tekstfeltet, vil feltets tekst bli oppdatert til å reflektere den nye verdien. Hvordan verdien vises, avgjøres av formatereren.

Merk at selv om et formatert tekstfelt arver metoden setText fra JTextField, så brukes denne metoden vanligvis ikke for formaterte tekstfelter. Brukes den, vil feltets tekst bli oppdatert, men ikke dets verdi.

For å hente ut verdien til et formatert tekstfelt, bruker vi dets getValue-metode. For å være sikker på at verdien reflekterer feltets tekst, kan vi først gjøre kall på commitEdit (uten parameter). Siden getValue returnerer et Object, er det nødvendig å konvertere det til den type som brukes for feltets verdi. For eksempel:

  Date dato = (Date) datofelt.getValue();

Lytte på verdiendring: PropertyChangeListener

Ovenfor er det forklart i hvilke tilfeller verdien til et formatert tekstfelt vil endre seg. Endringen kan for eksempel ha skjedd som følge av at brukeren har skrevet noe i feltet og trykket returtast. Dersom vi ønsker at noe skal skje som følge av denne endringen, bruker vi ikke lytter av type ActionListener, slik vi gjør for vanlige tekstfelter, men derimot lytter av type PropertyChangeListner. For en slik må vi implementere metoden

  public void propertyChange(PropertyChangeEvent e)

Det finnes to måter vi kan bruke for å knytte en PropertyChangeListner til et formatert tekstfelt. En mulighet er å bruke den måten som vi er vant med for andre typer lyttere:

  felt.addPropertyChangeListner(lytter);

Da må vi være klar over at lytteobjektet blir varslet når det skjer endringer i en hvilken som helst av de såkalte bundne egenskapene til feltet. (I tillegg til verdi, er det slikt som font, gjennomsiktighet, ramme, etc.) I propertyChange-metoden kan vi få tak i navnet på den egenskapen som har endret seg ved å gjøre kall på PropertyChangeEvent-objektets getPropertyName-metode:

  public void propertyChange(PropertyChangeEvent e)
  {
    String navn = e.getPropertyName();
    if (navn.equals("value"))  // verdi-egenskapen har endret seg
      ...
  }

Som alternativ til dette kan vi bruke den andre måten for å knytte PropertyChangeListner til et formatert tekstfelt. Vi spesifiserer da hvilken egenskap det skal lyttes på endringer av. Er det endringer av feltets verdi vi vil lytte på, skriver vi da

  felt.addPropertyChangeListner("value", lytter);

Det er denne måten som brukes i følgende programeksempel.

Programeksempel

Programmet FormattedTextFieldDemo har et programvindu som ved oppstart ser ut som vist på dette bilde.

Programmet har fire formaterte tekstfelter, de tre første editerbare. For feltet som viser løpetid brukes det standardformaterer, bestemt av feltets verdi. Feltet opprettes på følgende måte:

 65     numPeriodsField = new JFormattedTextField();
 66     numPeriodsField.setValue(new Integer(numPeriods));
 67     numPeriodsField.setColumns(10);
 68     numPeriodsField.addPropertyChangeListener("value", this);

For de tre andre feltene blir det definert formaterer. Det gjøres av metoden setUpFormats som ser slik ut:

181   //Create and set up number formats. These objects also
182   //parse numbers input by user.
183   private void setUpFormats()
184   {
185     amountFormat = NumberFormat.getNumberInstance();
186
187     percentFormat = NumberFormat.getNumberInstance();
188     percentFormat.setMinimumFractionDigits(3);
189
190     paymentFormat = NumberFormat.getCurrencyInstance();
191   }

De definerte formatene brukes som konstruktørparametre når de formaterte tekstfeltene blir opprettet. Koden for dette ser ut slik:

 54     //Create the text fields and set them up.
 55     amountField = new JFormattedTextField(amountFormat);
 56     amountField.setValue(new Double(amount));
 57     amountField.setColumns(10);
 58     amountField.addPropertyChangeListener("value", this);
 59
 60     rateField = new JFormattedTextField(percentFormat);
 61     rateField.setValue(new Double(rate));
 62     rateField.setColumns(10);
 63     rateField.addPropertyChangeListener("value", this);

 70     paymentField = new JFormattedTextField(paymentFormat);
 71     paymentField.setValue(new Double(payment));
 72     paymentField.setColumns(10);
 73     paymentField.setEditable(false);
 74     paymentField.setForeground(Color.red);

Panelet som de formaterte tekstfeltene ligger i, fungerer også som PropertyChangeListener for de tre editerbare feltene. Koden som implementerer lytteobjektet ser slik ut:

103   /**
104    * Called when a field's "value" property changes.
105    */
106   public void propertyChange(PropertyChangeEvent e)
107   {
108     Object source = e.getSource();
109     if (source == amountField)
110     {
111       amount = ((Number) amountField.getValue()).doubleValue();
112     }
113     else if (source == rateField)
114     {
115       rate = ((Number) rateField.getValue()).doubleValue();
116     }
117     else if (source == numPeriodsField)
118     {
119       numPeriods = ((Number) numPeriodsField.getValue()).intValue();
120     }
121
122     double payment = computePayment(amount, rate, numPeriods);
123     paymentField.setValue(new Double(payment));
124   }

Det brukes ingen annen type lytteobjekt for feltene, altså ingen ActionListener. Vi ser at innlesing skjer ved bruk av feltets getValue-metode, det brukes ikke getText. Og det er ikke nødvendig med noe try-catch. Hver gang det blir lest inn en ny verdi, blir det foretatt ny beregning av månedlig kostnad for lånet (som er et annuitetslån), og denne verdien blir skrevet ut i det ikke-editerbare feltet. Kjør programmet og prøv deg fram med å skrive inn forskjellige verdier i feltene, gyldige og ugyldige, og legg merke til hva som skjer. Legg merke til at oppdatering skjer enten som følge av trykk på returtast, eller ved at markøren flyttes til et annet felt.

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

Forrige avsnitt Neste avsnitt  Start på kapittel om spesialiserte komponenter for grafiske brukergrensesnitt