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

Spinnere: JSpinner-objekter

Dersom de verdiene som skal være tillatt å bruke i et formatert tekstfelt har en opplagt rekkefølge, kan det være et alternativ å bruke en spinner. En spinner bruker et formatert tekstfelt. I tillegg har den to små knapper som kan brukes til syklisk gjennomløping av en liste av verdier. Spinneren kan altså brukes til å velge en verdi fra en serie av alternativer. På den måten har den tilsvarende funksjonalitet som en komboboks eller en listeboks. Følgende bilde viser et vindu med tre spinnere som har hver sin tilhørende ledetekst.

Vi skal seinere se på programkoden for programmet som dette hører til.

Som for andre swing-komponenter, så blir verdiene til en spinner lagret i en modell, ikke i selve spinnerobjektet. Som regel oppretter vi derfor en spinner ved at vi først oppretter modellen, og så bruker denne som konstruktørparameter når vi oppretter spinneren.

Spinnermodeller

Som spinnermodell kan vi enten bruke en av klassebibliotekets forhåndsdefinerte modeller, eller vi kan definere vår egen. I klassebiblioteket er det definert tre spinnermodeller:

SpinnerListModel
En spinnermodell der verdiene er definert av en array av objekter eller av en List
SpinnerNumberModel
Bruker sekvenser av tall. Disse kan uttrykkes som int-verdier, double-verdier, eller som Number-objekter. Vi kan spesifisere minimums- og maksimumsverdier, samt skrittlengde.
SpinnerDateModel
Bruker sekvenser av Date-objekter. Vi kan spesifisere start- og sluttdatoer, samt hvilket felt (for eksempel Calendar.YEAR) som skal brukes som skrittlengde.

Når vi setter modell for spinneren, så blir spinnerens editor automatisk satt. I klassebiblioteket er det en editorklasse for hver av de tre modellklassene listet opp ovenfor. Klassene heter henholdsvis JSpinner.ListEditor, JSpinner.NumberEditor og JSpinner.DateEditor. De er alle subklasser til JSpinner.DefaultEditor og bruker et formatert tekstfelt ved editeringen. Dersom vi bruker en modell som det ikke er definert editor for, så vil editoren være av type JSpinner.DefaultEditor. Denne bruker et ikke-editerbart formatert tekstfelt i sin editor.

Spesifisering av formatering

Vi kan endre formateringen som brukes av spinnerens standardeditor. Det kan vi gjøre enten ved selv å opprette og sette editor, eller ved å hente ut editorens formaterte tekstfelt og gjøre kall på metoder for dette.

Klassene JSpinner.NumberEditor og JSpinner.DateEditor har konstruktører som tillater oss å opprette en editor som formaterer sine data etter våre egne ønsker. I det følgende programeksemplet blir for eksempel en dato formatert til å vise bare måned og år på formatet 02/2012 ved at det for spinneren er skrevet følgende kode:

    datospinner.setEditor(new JSpinner.DateEditor(datospinner, "MM/yyyy"));

Dersom vi isteden ønsker å hente ut editorens formaterte tekstfelt og tilpasse dette, må vi være klar over at editoren i seg selv ikke er et formatert tekstfelt. Editoren er et panel som inneholder et formatert tekstfelt. Et eksempel på hvordan vi kan hente ut det formaterte tekstfeltet fra editoren og tilpasse dette, har vi i følgende kodeutdrag fra det følgende programeksemplet.

 35     //Henter spinnerens formatterte tekstfelt.
 36     JFormattedTextField ftf = getTextField(månedspinner);
 37     if (ftf != null)
 38     {
 39       ftf.setColumns(8); //specify more width than we need
 40       ftf.setHorizontalAlignment(JTextField.RIGHT);
 41     }

    ...

 94   /**
 95    * Return the formatted text field used by the editor, or null
 96    * if the editor doesn't descend from JSpinner.DefaultEditor.
 97    */
 98   public JFormattedTextField getTextField(JSpinner spinner)
 99   {
100     JComponent editor = spinner.getEditor();
101     if (editor instanceof JSpinner.DefaultEditor)
102     {
103       return ((JSpinner.DefaultEditor) editor).getTextField();
104     }
105     else
106     {
107       System.err.println("Unexpected editor type: "
108               + spinner.getEditor().getClass()
109               + " isn't a descendant of DefaultEditor");
110       return null;
111     }
112   }

Programeksempel 1

Fullstendig programkode for programmet SpinnerDemo2 er gjengitt nedenfor. Programmet er en tilpasning av SpinnerDemo.java som finnes i The Java Tutorials. Ovenfor er det vist bilde av programmets brukervindu. Kjør programmet og legg merke til følgende:

  1 import javax.swing.*;
  2 import java.awt.*;
  3 import java.util.Calendar;
  4 import java.util.Date;
  5
  6 public class SpinnerDemo2 extends JPanel
  7 {
  8   public SpinnerDemo2()
  9   {
 10     super(new BorderLayout());
 11     String[] labels =
 12     {
 13       "Måned: ", "År: ", "En annen dato: "
 14     };
 15     //Ledetekster til spinnerne:
 16     JLabel måned = new JLabel(labels[ 0]);
 17     JLabel år = new JLabel(labels[ 1]);
 18     JLabel dato = new JLabel(labels[ 2]);
 19
 20     //Legger ut ledetekstene i et panel
 21     JPanel labelpanel = new JPanel(new GridLayout(0, 1, 0, 5));
 22     labelpanel.add(måned);
 23     labelpanel.add(år);
 24     labelpanel.add(dato);
 25
 26     Calendar calendar = Calendar.getInstance();
 27
 28     //Oppretter den første spinneren.
 29     String[] monthStrings = getMonthStrings(); //får tak i månedsnavnene
 30
 31     //bruker standardmodell
 32     SpinnerListModel monthModel = new SpinnerListModel(monthStrings);
 33     JSpinner månedspinner = new JSpinner(monthModel);
 34
 35     //Henter spinnerens formatterte tekstfelt.
 36     JFormattedTextField ftf = getTextField(månedspinner);
 37     if (ftf != null)
 38     {
 39       ftf.setColumns(8); //specify more width than we need
 40       ftf.setHorizontalAlignment(JTextField.RIGHT);
 41     }
 42
 43     //Lager den andre spinneren.
 44     int currentYear = calendar.get(Calendar.YEAR);
 45     SpinnerModel yearModel = new SpinnerNumberModel(
 46             currentYear, //startverdi
 47             currentYear - 100, //min
 48             currentYear + 100, //max
 49             1); //skrittlengde
 50
 51     JSpinner årsspinner = new JSpinner(yearModel);
 52
 53     //Make the year be formatted without a thousands separator.
 54     årsspinner.setEditor(new JSpinner.NumberEditor(årsspinner, "#"));
 55
 56     //Lager den tredje spinneren.
 57     Date initDate = calendar.getTime();
 58     calendar.add(Calendar.YEAR, -100);
 59     Date earliestDate = calendar.getTime();
 60     calendar.add(Calendar.YEAR, 200);
 61     Date latestDate = calendar.getTime();
 62     SpinnerModel dateModel = new SpinnerDateModel(initDate,
 63             earliestDate,
 64             latestDate,
 65             Calendar.YEAR);
 66     //ignored for user input
 67     JSpinner datospinner = new JSpinner(dateModel);
 68     datospinner.setEditor(new JSpinner.DateEditor(datospinner,
 69             "MM/yyyy"));
 70     //Henter spinnerens formatterte tekstfelt.
 71     ftf = getTextField(datospinner);
 72     if (ftf != null)
 73     {
 74       ftf.setHorizontalAlignment(JTextField.RIGHT);
 75       ftf.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 3));
 76     }
 77     datospinner.setBorder(BorderFactory.createLineBorder(
 78             Color.BLACK, 1));
 79     //XXX: No easy way to get to the buttons and change their border.
 80
 81     //Legger spinnerne ut i et eget panel.
 82     JPanel spinnerpanel = new JPanel(new GridLayout(0, 1, 0, 5));
 83     spinnerpanel.add(månedspinner);
 84     spinnerpanel.add(årsspinner);
 85     spinnerpanel.add(datospinner);
 86
 87     //Legger labelpanelet til venstre og spinnerpanelet 
 88     //til høyre i dette panel.
 89     setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
 90     add(labelpanel, BorderLayout.CENTER);
 91     add(spinnerpanel, BorderLayout.LINE_END);
 92   }
 93
 94   /**
 95    * Return the formatted text field used by the editor, or null
 96    * if the editor doesn't descend from JSpinner.DefaultEditor.
 97    */
 98   public JFormattedTextField getTextField(JSpinner spinner)
 99   {
100     JComponent editor = spinner.getEditor();
101     if (editor instanceof JSpinner.DefaultEditor)
102     {
103       return ((JSpinner.DefaultEditor) editor).getTextField();
104     }
105     else
106     {
107       System.err.println("Unexpected editor type: "
108               + spinner.getEditor().getClass()
109               + " isn't a descendant of DefaultEditor");
110       return null;
111     }
112   }
113
114   /**
115    * DateFormatSymbols returns an extra, empty value at
116    * the end of the array of months. Remove it.
117    */
118   static protected String[] getMonthStrings()
119   {
120     String[] months = new java.text.DateFormatSymbols().getMonths();
121     int lastIndex = months.length - 1;
122
123     if (months[lastIndex] == null
124             || months[lastIndex].length() <= 0)
125     { //last item empty
126       String[] monthStrings = new String[lastIndex];
127       System.arraycopy(months, 0, monthStrings, 0, lastIndex);
128       return monthStrings;
129     }
130     else
131     { //last item not empty
132       return months;
133     }
134   }
135
136   /**
137    * Create the GUI and show it. For thread safety, this method
138    * should be invoked from the event-dispatching thread.
139    */
140   private static void createAndShowGUI()
141   {
142     //Create and set up the window.
143     JFrame frame = new JFrame("SpinnerDemo");
144     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
145
146     //Create and set up the content pane.
147     JComponent newContentPane = new SpinnerDemo2();
148     newContentPane.setOpaque(true); //content panes must be opaque
149     frame.setContentPane(newContentPane);
150
151     //Display the window.
152     frame.pack();
153     frame.setVisible(true);
154   }
155
156   public static void main(String[] args)
157   {
158     //Schedule a job for the event-dispatching thread:
159     //creating and showing this application's GUI.
160     javax.swing.SwingUtilities.invokeLater(new Runnable()
161     {
162       public void run()
163       {
164         createAndShowGUI();
165       }
166     });
167   }
168 }

Lytteobjekt for spinnere

Hendelsene som blir generert nå vi endrer verdiene som vises av spinnere, er av type ChangeEvent. De kan fanges opp av lytteobjekter av type ChangeListener. Lytteobjekt av denne typen kan registreres med metoden addChangeListener. Det kan gjøres enten for spinneren eller for dens spinnermodell. Klassen ChangeEvent og interface ChangeListener må importeres fra pakken javax.swing.event. Den eneste metoden som må implementeres for å definere lytteobjekt er

    public void stateChanged(ChangeEvent e)
    {
      ...
    }

Programeksempel 2

Programmet SpinnerDemo3 som er gjengitt nedenfor er en videreføring av eksemplet ovenfor. Datospinneren (den som er nederst i vinduet som programmet viser) er utstyrt med lytteobjekt av type ChangeListener. Dette virker på den måten at spinnerens tilhørende formaterte tekstfelt får bakgrunnsfarge bestemt av hvilken måned spinnerens dato er innstilt på. For hver av de fire årstidene vår, sommer, høst og vinter er det definert en fargekonstant. Denne brukes som bakgrunnsfarge for nevnte tektsfelt, bestemt av hvilken årstid spinnerens måned tilhører. Følgende bilde viser programvinduet når måneden er innstilt på april. Vi ser at datospinnerens tekstfelt da har grønn bakgrunnsfarge.

Datospinnerens lytteobjekt er definert av den indre klassen Spinnerlytter. Den nye metoden setÅrstidsfarge brukes av lytteobjektet til å få satt ønsket bakgrunnsfarge. Metoden blir også kalt opp rett etter at datospinneren er opprettet og initialisert, i konstruktøren for panelet som definerer contentpane for programvinduet.

  1 import java.awt.BorderLayout;
  2 import java.awt.Color;
  3 import java.awt.GridLayout;
  4 import java.util.Calendar;
  5 import java.util.Date;
  6 import javax.swing.*;
  7 import javax.swing.event.*;
  8
  9 public class SpinnerDemo3 extends JPanel
 10 {
 11   private Calendar calendar;
 12   private JSpinner datospinner;
 13   public static final Color VÅRFARGE = new Color(0, 204, 51);
 14   public static final Color SOMMERFARGE = Color.RED;
 15   public static final Color HØSTFARGE = new Color(255, 153, 0);
 16   public static final Color VINTERFARGE = Color.CYAN;
 17
 18   public SpinnerDemo3()
 19   {
 20     super(new BorderLayout());
 21     String[] labels =
 22     {
 23       "Måned: ", "År: ", "En annen dato: "
 24     };
 25     //Ledetekster til spinnerne:
 26     JLabel måned = new JLabel(labels[ 0]);
 27     JLabel år = new JLabel(labels[ 1]);
 28     JLabel dato = new JLabel(labels[ 2]);
 29
 30     //Legger ut ledetekstene i et panel
 31     JPanel labelpanel = new JPanel(new GridLayout(0, 1, 0, 5));
 32     labelpanel.add(måned);
 33     labelpanel.add(år);
 34     labelpanel.add(dato);
 35
 36     calendar = Calendar.getInstance();
 37
 38     //Lager den første spinneren.
 39     String[] monthStrings = getMonthStrings(); //henter månedsnavn
 40
 41     //bruker standardmodell
 42     SpinnerListModel monthModel = new SpinnerListModel(monthStrings);
 43     JSpinner månedspinner = new JSpinner(monthModel);
 44
 45     //Henter spinnerens formatterte tekstfelt.
 46     JFormattedTextField ftf = getTextField(månedspinner);
 47     if (ftf != null)
 48     {
 49       ftf.setColumns(8); //specify more width than we need
 50       ftf.setHorizontalAlignment(JTextField.RIGHT);
 51     }
 52
 53     //Lager den andre spinneren.
 54     int currentYear = calendar.get(Calendar.YEAR);
 55     SpinnerModel yearModel = new SpinnerNumberModel(
 56             currentYear, //startverdi
 57             currentYear - 100, //min
 58             currentYear + 100, //max
 59             1); //skrittlengde
 60
 61     JSpinner årsspinner = new JSpinner(yearModel);
 62
 63     //Make the year be formatted without a thousands separator.
 64     årsspinner.setEditor(new JSpinner.NumberEditor(årsspinner, "#"));
 65
 66     //Add the third spinner.
 67     Date initDate = calendar.getTime();
 68     calendar.add(Calendar.YEAR, -100);
 69     Date earliestDate = calendar.getTime();
 70     calendar.add(Calendar.YEAR, 200);
 71     Date latestDate = calendar.getTime();
 72     SpinnerModel dateModel = new SpinnerDateModel(initDate,
 73             earliestDate,
 74             latestDate,
 75             Calendar.YEAR);
 76     //ignored for user input
 77     datospinner = new JSpinner(dateModel);
 78     datospinner.setEditor(new JSpinner.DateEditor(datospinner,
 79             "MM/yyyy"));
 80     //Henter spinnerens formatterte tekstfelt.
 81     ftf = getTextField(datospinner);
 82     if (ftf != null)
 83     {
 84       ftf.setHorizontalAlignment(JTextField.RIGHT);
 85       ftf.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 3));
 86     }
 87     datospinner.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
 88     //XXX: No easy way to get to the buttons and change their border.
 89     datospinner.addChangeListener(new Spinnerlytter());
 90     setÅrstidsfarge(initDate);
 91
 92     //Legger spinnerne ut i et eget panel.
 93     JPanel spinnerpanel = new JPanel(new GridLayout(0, 1, 0, 5));
 94     spinnerpanel.add(månedspinner);
 95     spinnerpanel.add(årsspinner);
 96     spinnerpanel.add(datospinner);
 97
 98     //Legger labelpanelet til venstre og spinnerpanelet til 
 99     //høyre i dette panel.
100     setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
101     add(labelpanel, BorderLayout.CENTER);
102     add(spinnerpanel, BorderLayout.LINE_END);
103   }
104
105   /**
106    * Return the formatted text field used by the editor, or null
107    * if the editor doesn't descend from JSpinner.DefaultEditor.
108    */
109   public JFormattedTextField getTextField(JSpinner spinner)
110   {
111     JComponent editor = spinner.getEditor();
112     if (editor instanceof JSpinner.DefaultEditor)
113     {
114       return ((JSpinner.DefaultEditor) editor).getTextField();
115     }
116     else
117     {
118       System.err.println("Unexpected editor type: "
119               + spinner.getEditor().getClass()
120               + " isn't a descendant of DefaultEditor");
121       return null;
122     }
123   }
124
125   /**
126    * DateFormatSymbols returns an extra, empty value at
127    * the end of the array of months. Remove it.
128    */
129   static protected String[] getMonthStrings()
130   {
131     String[] months = new java.text.DateFormatSymbols().getMonths();
132     int lastIndex = months.length - 1;
133
134     if (months[lastIndex] == null
135             || months[lastIndex].length() <= 0)
136     { //last item empty
137       String[] monthStrings = new String[lastIndex];
138       System.arraycopy(months, 0, monthStrings, 0, lastIndex);
139       return monthStrings;
140     }
141     else
142     { //last item not empty
143       return months;
144     }
145   }
146
147   /**
148    * Create the GUI and show it. For thread safety, this method
149    * should be invoked from the event-dispatching thread.
150    */
151   private static void createAndShowGUI()
152   {
153     //Create and set up the window.
154     JFrame frame = new JFrame("SpinnerDemo");
155     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
156
157     //Create and set up the content pane.
158     JComponent newContentPane = new SpinnerDemo3();
159     newContentPane.setOpaque(true); //content panes must be opaque
160     frame.setContentPane(newContentPane);
161
162     //Display the window.
163     frame.pack();
164     frame.setVisible(true);
165   }
166
167   protected void setÅrstidsfarge(Date dato)
168   {
169     calendar.setTime(dato);
170     int måned = calendar.get(Calendar.MONTH);
171     JFormattedTextField ftf = getTextField(datospinner);
172     if (ftf == null)
173     {
174       return;
175     }
176     switch (måned)
177     {
178       case 2: //mars
179       case 3: //april
180       case 4: //mai
181         ftf.setBackground(SpinnerDemo3.VÅRFARGE);
182         break;
183       case 5: //juni
184       case 6: //juli
185       case 7: //august
186         ftf.setBackground(SpinnerDemo3.SOMMERFARGE);
187         break;
188       case 8: //september
189       case 9: //oktober
190       case 10: //november
191         ftf.setBackground(SpinnerDemo3.HØSTFARGE);
192         break;
193       default:
194         ftf.setBackground(SpinnerDemo3.VINTERFARGE);
195     }
196   }
197
198   private class Spinnerlytter implements ChangeListener
199   {
200     public void stateChanged(ChangeEvent e)
201     {
202       SpinnerModel modell = datospinner.getModel();
203       Date dato = ((SpinnerDateModel) modell).getDate();
204       setÅrstidsfarge(dato);
205     }
206   }
207
208   public static void main(String[] args)
209   {
210     //Schedule a job for the event-dispatching thread:
211     //creating and showing this application's GUI.
212     javax.swing.SwingUtilities.invokeLater(new Runnable()
213     {
214       public void run()
215       {
216         createAndShowGUI();
217       }
218     });
219   }
220 }

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

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