Advanced   Java   Services
JSpinner
Back Next Up Home

Mit der Version 1.4 wurden die Swingklassen um zwei Klassen erweitert. Neu eingeführt wurden JSpinner (to spin = drehen) und JFormattedTextField (siehe Neue Klassen in 1.4). Ein JSpinner ist ein kleines wahlweise editierbares Textfeld, das einen Wert aus einer Reihe von vordefinierten Werten anzeigt. Mit zwei kleinen Buttons kann man die Reihe vorwärts oder rückwärts durchwandern. Bei dem Textfeld handelt es sich im Defaultfall um die neue Klasse JFormattedTextField. Die angezeigten Werte werden aus einem Array, einer List oder aus dem Wertebereich eines Datentyps genommen und sind damit geordnet. ist man am Ende eines Wertebereichs angelangt, so gibt es zwei Möglichkeiten. Entweder man muß die Reihe wieder zurückwandern oder beim Weiterklicken springt der Spinner auf den kleinsten Wert der Reihe. Die folgende Graphik zeigt JSpinner im "Kreise seiner Verwandten".

Konstruktoren
JSpinner()
 
 
 
 
 
 
 
 
 
Constructs a spinner with an Integer SpinnerNumberModel with initial value 0 and no minimum or maximum limits. Genauer: Der Defaultkonstruktor setzt ein SpinnerNumberModel, wie ein Blick in den Quellcode zeigt:
public JSpinner()
{
   this(new SpinnerNumberModel());
}
Dieses Model liefert einen Spinner mit dem Wertebereich von int. Ist man bei Integer.MAX_VALUE angelangt springt der Spinner weiter nach Integer.MIN_VALUE.
JSpinner(SpinnerModel model) Constructs a complete spinner with pair of next/previous buttons and an editor for the SpinnerModel.
Wichtige Methoden
ReturntypName der Methode
void
 
addChangeListener(ChangeListener listener)
Adds a listener to the list that is notified each time a change to the model occurs.
void
 
removeChangeListener(ChangeListener listener)
Removes a ChangeListener from this spinner.
JComponent
 
getEditor()
Returns the component that displays and potentially changes the model's value.
void
 
setEditor(JComponent editor)
Changes the JComponent that displays the current value of the SpinnerModel.
SpinnerModel
 
getModel()
Returns the SpinnerModel that defines this spinners sequence of values.
void
 
setModel(SpinnerModel model)
Changes the model that represents the value of this spinner.
Object
 
getValue()
Returns the current value of the model, typically this value is displayed by the editor.
void
 
setValue(Object value)
Changes current value of the model, typically this value is displayed by the editor.
Object
 
getNextValue()
Returns the object in the sequence that comes after the object returned by getValue().
Object
 
getPreviousValue()
Returns the object in the sequence that comes before the object returned by getValue().


Verschiedene Beispiele



Zahlenspinner



das folgende Applet zeigt sechs Spinner. Sie verwenden alle ein SpinnerNumberModel, entweder das vom Defaultkonstruktor erzeugte SpinnerNumberModel, das seinerseits vom Defaultkonstruktor initialisert wird oder ein selbstkonfiguriertes Spinnermodel. Die Spinner unterscheiden sich durch verschiedene Formatierungen des verwendeten TextFeldes. Das SpinnerNumberModel arbeitet zusammen mit einem javax.swing.JSpinner.NumberEditor. Wie man aus der obigen Graphik entnehmen kann ist NumberEditor kein Editor, sondern ein JPanel, das als Editor ein JFormattedTextField enthält. Dieses JFormattedTextField kann man je nach Geschmack oder lokaler Färbung (Locale) konfigurieren und mit der Klasse DecimalFormatSymbols länderspezifische Trennzeichen zur Darstellung größerer Ganzzahlen setzen.


Spinner 1

Verwendet das vom Defaultkonstruktor erzeugte SpinnerNumberModel, das seinerseits durch den Defaultkonstruktor von SpinnerNumberModel initialisert wird (Locale.GERMAN). Tausendertrennzeichen ist der Punkt.

Spinner 2

Ein nicht editierbarer Spinner mit dem Leerzeichen als Tausendertrennzeichen ist mit Hilfe von Locale.FRENCH wie folgt zu erhalten:

JComponent defEditor = spinner.getEditor()  ;
JFormattedTextField ftf = ((JSpinner.DefaultEditor)defEditor).getTextField() ;
ftf.setEditable(false);
// Format setzen
JFormattedTextField.AbstractFormatter formatter = ftf.getFormatter() ;
Format format = ((InternationalFormatter)formatter).getFormat() ;
DecimalFormatSymbols frSymbols = new DecimalFormatSymbols(Locale.FRENCH)  ; //2 345 678
((DecimalFormat)format).setDecimalFormatSymbols(frSymbols)  ;


Spinner 3

Ein Spinner für rationale Zahlen mit einer Schrittweite von 0.5. Als Dezimaltrennzeichen wird das Komma verwendet, als Tausendertrennzeichen der Punkt (Locale.GERMAN) .

JSpinner.DefaultEditor defEditor = (JSpinner.DefaultEditor)spinner.getEditor()  ;
JFormattedTextField ftf = defEditor.getTextField() ;
InternationalFormatter intFormatter = (InternationalFormatter)ftf.getFormatter() ;
DecimalFormat decimalFormat = (DecimalFormat)intFormatter.getFormat() ;
decimalFormat.applyPattern("#,##0.0")  ;
// pattern = #,##0.0   eine obligatorische stelle hinter dem komma
DecimalFormatSymbols geSymbols = new DecimalFormatSymbols(Locale.GERMAN)  ; //2.345.678,9
decimalFormat.setDecimalFormatSymbols(geSymbols)  ;


Spinner 4

Ein Spinner für rationale Zahlen mit einer Schrittweite von 0.5. Als Dezimaltrennzeichen wird der Punkt verwendet, als Tausendertrennzeichen das Komma.

JSpinner.DefaultEditor defEditor = (JSpinner.DefaultEditor)spinners[3].getEditor()  ;
JFormattedTextField ftf = defEditor.getTextField() ;
InternationalFormatter intFormatter = (InternationalFormatter)ftf.getFormatter() ;
DecimalFormat decimalFormat = (DecimalFormat)intFormatter.getFormat() ;
//decimalFormat.setDecimalSeparatorAlwaysShown(true); // wirkt nicht ??
decimalFormat.applyPattern("#,##0.0")  ;
// pattern = #,##0.0   eine obligatorische stelle hinter dem komma
DecimalFormatSymbols usSymbols = new DecimalFormatSymbols(Locale.US)  ; //2,345,678.9
decimalFormat.setDecimalFormatSymbols(usSymbols)  ;


Spinner 5

Ein Spinner für rationale Zahlen mit einer Schrittweite von 0.5. Als Dezimaltrennzeichen wird das Komma verwendet, als Tausendertrennzeichen ein Leerzeichen.

JSpinner.DefaultEditor defEditor = (JSpinner.DefaultEditor)spinner.getEditor()  ;
JFormattedTextField ftf = defEditor.getTextField() ;
InternationalFormatter intFormatter = (InternationalFormatter)ftf.getFormatter() ;
DecimalFormat decimalFormat = (DecimalFormat)intFormatter.getFormat() ;
decimalFormat.applyPattern("#,##0.0")  ;
// pattern = #,##0.0   eine obligatorische stelle hinter dem komma
DecimalFormatSymbols frSymbols = new DecimalFormatSymbols(Locale.FRENCH)  ; //2 345 678,5
decimalFormat.setDecimalFormatSymbols(frSymbols)  ;


Spinner 6

Ein Spinner für rationale Zahlen mit einer Schrittweite von 0.5. Als Dezimaltrennzeichen wird der Punkt verwendet, als Tausendertrennzeichen ein Leerzeichen.

JSpinner.DefaultEditor  defEditor = (JSpinner.DefaultEditor)spinner.getEditor()  ;
JFormattedTextField ftf = defEditor.getTextField() ;
InternationalFormatter intFormatter = (InternationalFormatter)ftf.getFormatter() ;
DecimalFormat decimalFormat = (DecimalFormat)intFormatter.getFormat() ;
decimalFormat.applyPattern("#,##0.0")  ;
// pattern = #,##0.0   eine obligatorische stelle hinter dem komma
DecimalFormatSymbols    frSymbols = new DecimalFormatSymbols(Locale.FRENCH);//2 345 678.5
frSymbols.setDecimalSeparator('.') ;
decimalFormat.setDecimalFormatSymbols(frSymbols)  ;


DateSpinner und ListSpinner



Das nächste Applet bringt einen Spinner für Datumsanzeigen und Zwei ListSpinner. Der erste ListSpinner für die Monate verwendet ein SpinnerListModel aus der API, der zweite Listspinner verwendet ein eigenes Model, das eine rotieren der Werte zuläßt.



Spinner 1

Konfiguration

spinner.setModel( new SpinnerDateModel() ) ;
JComponent dateEditorComp = spinner.getEditor()  ;
// liefert einen JSpinner.DateEditor
JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)dateEditorComp;
// bei DateEditor kommt man nicht an das Textfeld ran
SimpleDateFormat  simpleDateFormat = dateEditor.getFormat() ;
// simpleDateFormat : dd.MM.yy HH:mm  (defaultpattern)
String pattern = "  dd.MM.yyyy";
simpleDateFormat.applyPattern(pattern) ;


Spinner 2

Konfiguration

String[] months = { "Januar", "Februar", "März", "April", "Mai", "Juni",
                  "Juli", "August", "September", "Oktober", "November", "Dezember",};

spinner.setModel( new SpinnerListModel(months) ) ;
JComponent listEditorComp = spinner.getEditor()  ;
// JSpinner.ListEditor ist im wesentlichen der Defaulteditor
JSpinner.DefaultEditor listEditor = (JSpinner.DefaultEditor)listEditorComp;
JFormattedTextField tefi = listEditor.getTextField() ;
tefi.setHorizontalAlignment(SwingConstants.CENTER);


Spinner 3

Konfiguration

String[] days = { "Montag", "Dienstag", "Mittwoch", "Donnerstag",
                  "Freitag", "Samstag", "Sonntag"};

RotatingSpinnerListModel rotatingModel = new RotatingSpinnerListModel(days);
spinner.setModel(rotatingModel);
listEditorComp = spinner.getEditor()  ;
// JSpinner.ListEditor ist im wesentlichen der Defaulteditor
JSpinner.ListEditor listEditor = (JSpinner.ListEditor)listEditorComp;
JFormattedTextField tefi = listEditor.getTextField() ;
tefi2.setHorizontalAlignment(SwingConstants.CENTER);


RotatingSpinnerListModel



Hier der Quelltext der Klasse RotatingSpinnerListModel.

/**
*  class  RotatingSpinnerListModel
*
*/
import javax.swing.*;

public class RotatingSpinnerListModel extends SpinnerListModel
{
   int maxIndex ;

   {
      maxIndex = getList().size() -1;
   }

   public SpinnerRotatingListModel()
   {
   }

   public SpinnerRotatingListModel(java.util.List values)
   {
      super(values);
   }
   public SpinnerRotatingListModel(Object[] values)
   {
      super(values);
   }

   //Returns the next legal value of the underlying sequence
   public Object getNextValue()
   {
      Object next = super.getNextValue();
      if(next==null)
         return getList().get(0);

      return next;
   }

   //Returns the previous element of the underlying sequence
   public Object getPreviousValue()
   {
      Object prev = super.getPreviousValue();
      if(prev==null)
         return getList().get(maxIndex);

      return prev;
   }
}


TemperaturSpinner



Vier Spinner zeigen die gleiche Temperatur in verschiedenen Maßsystemen an. Die Schrittweite beträgt jeweils eins. Das Drehen eines Spinners bewirkt automatisch die Drehung der anderen Spinner auf den entsprechenden Zahlenwert in den jeweils anderen Maßsystemen. Der Einfachheit halber wird -273 Grad Celsius als absoluter Nullpunkt verwendet und nicht -273.15 . Die höchste einstellbare Temperatur ist die in etwa die Temperatur im Inneren unserer Sonne (ca. 15 Millionen Grad). Ergeben sich ein bei der Umrechnng Kommastellen, so werden diese zunächst angezeigt. Wechselt man anschließend zu einem anderen Spinner (Focus), so wird (aus optischen Gründen) aufgerundet.


Der verwendete Listener ist ein ChangeListener. Er ist zwangsläufig die erste Wahl. Dabei ist allerdings ein kleines Problem zu überwinden. Man möchte gerne wie folgt codieren:

Entwurf von stateChanged()

public void stateChanged(ChangeEvent e)
{
   //System.out.println("--celisuspinner--");
   Double celsius = (Double)spinners[0].getValue() ;
   double cels = celsius.doubleValue();
   double fahr = 32 + 9.0*cels/5 ;
   spinners[1].setValue( new Double(fahr)  );
   spinners[2].setValue( new Double(cels+273) );
   spinners[3].setValue( new Double(0.8*cels) );
}

Ein Aufruf der Methode setValue() löst (wie natürlich das Drehen am Spinner) jedoch seinerseits wieder einen ChangeEvent aus, so das die Methode stateChanged() rekursiv aufgerufen wird, was mit einem StackOverflowError quittiert wird. Eine Lösung wäre, ein SpinnerModel zu schreiben, in dem setValue() keinen ChangeEvent auslöst, was allerdings der BeanKonvention widerspricht. Hier wird folgender Weg eingeschlagen: Bevor man (als Benutzer) einen Spinner drehen kann muß er den Focus bekommen, mit anderen Worten, der FocusEvent kommt vor dem ChangeEvent. Dies wird folgendermaßen eingesetzt: Am Anfang erhält kein Spinner einen ChangeListener. Erhält ein Listener den Fokus, so wird ein ChangeListener hinzugefügt, verliert er den Fockus, wird der ChangeListener wieder entfernt. Auf diese Weise kann es nicht zu Rückkopplungen kommen.

Ermöglicht durch folgenden FocusListener

class SpinnerFocusListener implements FocusListener
{
   public void focusGained(FocusEvent e)
   {
      Component source = (Component)e.getSource();
      JSpinner.DefaultEditor defEditor = (JSpinner.DefaultEditor)source.getParent();
      JSpinner spinner = defEditor.getSpinner();
      for(int i=0; i<spinners.length; i++)
      {
         if(spinners[i]==spinner)
         {
            spinners[i].addChangeListener(listeners[i]);
         }
      }
   }
   public void focusLost(FocusEvent e)
   {
      Component source = (Component)e.getSource();
      JSpinner.DefaultEditor defEditor = (JSpinner.DefaultEditor)source.getParent();
      JSpinner spinner = defEditor.getSpinner();
      for(int i=0; i<spinners.length; i++)
      {
         if(spinners[i]==spinner)
         {
            spinners[i].removeChangeListener(listeners[i]);
         }
      }
   }
}

Copyright Dipl.Math. H.M.Straub
top Back Next Up Home