Advanced
Java
Services
|
JSpinner |
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 | |
Returntyp | Name 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(). |
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) ;
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);
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;
}
}
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]);
}
}
}
}
|