JSpinner zur Datums- und Zeitauswahl
Der vorliegende Artikel behandelt die Nutzung eines JSpinner
zur Datums- und Zeitanzeige. Die allgemeinen Grundlagen
zu dessen Aufbau und zur weiteren Nutzung eines JSpinner
werden im Artikel Auswahlmenu
JSpinner erläutert.
Das DateModel
Da ein JSpinner
im einfachsten Fall seine
Inhalte intern durch ein NumberModel verwaltet, wird er
erst durch Übergabe eines DateModels zur Verwaltung
von Datums- und Zeitwerten befähigt. Hierzu stellt
Java die Klasse SpinnerDateModel
bereit,
deren Konstruktoren entweder unparametrisiert oder durch
Angabe von vier Argumenten aufgerufen werden
können.
public class SpinnerDateModelBsp { public SpinnerDateModelBsp() { init(); } public void init() { SpinnerDateModel model = new SpinnerDateModel(); JSpinner spinner = new JSpinner(model); spinner.addChangeListener(e -> System.out.println(spinner.getValue())); JFrame frame = new JFrame("Date-Model-Beispiel"); frame.add(spinner); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new SpinnerDateModelBsp()); } }
Das Beispiel demonstriert die einfachste Form eines
solchen Spinners mit unparametrisiertem Model. Dem JSpinner
wurde lediglich ein ChangeListener hinzugefügt, um
auch die Ausgabe der gespeicherten Werte zu
demonstrieren. Man sieht, dass hierbei auf die Methode JSpinner.getValue()
zugegriffen wird. Sie ruft intern die entsprechende
Modelmethode SpinnerDateModel.getValue()
auf, sodass also gerade nicht auf die Darstellung
im Textfeld des Spinners, sondern auf den im Model
gespeicherten Wert zugegriffen wird. Das Ergebnis sind
zwei verschiedene Darstellungsweisen, die bei der
Betrachtung des Editors zu
berücksichtigen sein werden:
- Im Model ist der Wert in einer Form gespeichert, wie
er hinsichtlich seines Ausgabeformates, der Zeitzone
und der Standard-Gebietsvariablen durch ein
Calendar
-Objekt repräsentiert wird, z.B.Mon Oct 25 10:00:00 CEST 2021
. - Die Darstellung im Textfeld des Spinners ist
zusätzlich formatiert. Sie basiert auf dem mit
dem Spinner assoziierten
JSpinner.DateEditor
, der das oben bereits erwähnteJFormattedTextField
liefert und die Formatierung des Strings dort durch einenDateFormatter
regelt.
Sollen beide Formen identisch formatiert sein, so muss
entweder die Darstellung durch den JSpinner
,
durch Setzen eines geeigneten Editors
oder der Ausgabewert umformatiert werden.
Um die Anzeige eines JSpinner
nach oben
oder unten zu begrenzen und die maximale
Schrittgröße beim "Blättern" festzulegen,
kann ein SpinnerDateModel
mit vier
Parametern erzeugt und an den Spinner übergeben
werden.
//... Calendar cal = new GregorianCalendar(); Date now = cal.getTime(); cal.set(Calendar.HOUR_OF_DAY, 5); Date start = cal.getTime(); cal.set(Calendar.HOUR_OF_DAY, 20); Date end = cal.getTime(); SpinnerDateModel model = new SpinnerDateModel(now, start, end, Calendar.HOUR_OF_DAY); JSpinner spinner = new JSpinner(model); //...
- Der erste Wert wird durch ein
Date
-Objekt gestellt. Er gibt den Startwert des Spinners an und kann auch durchsetValue()
gesetzt werden. - Der zweite und dritte Wert sind Objekte vom Typ
Comparable
, häufig, wie im Beispiel auch, ebenfallsDate
-Werte, die eine obere und untere Grenze für den möglichen Auswahlbereich angeben. Nachträglich können sie durch die Accessor-MethodensetStart()
undsetEnd()
des Models gesetzt werden.
Beide Parameter können gemeinsam oder jeder für sichnull
sein. Dies bewirkt dann einen unbegrenzten Spinnerbereich. - Der vierte Parameter gibt die maximale Schrittweite
beim "Blättern" mit den Pfeiltasten vor. Er
muss einer der in der Klasse
Calendar
definierten Zeit-/Datumskonstanten entsprechen und kann ebenfalls durchsetCalendarField()
nachträglich gesetzt werden, darf jedoch nichtnull
sein. Eine Auflistung der erlaubten Felder findet sich außer in den API-Doc der KlasseCalendar
auch im Artikel zuJSpinner
Wichtig ist, dass der Zeitpunkt des ersten Wertes
zwischen start
und end
liegt.
Ist das nicht der Fall, so wird eine IllegalArgumentException
mit dem Hinweis
(start <= value <= end) is false
geworfen.
Verhalten des Spinners
Wird das obige Programm
ausgeführt, so befindet sich der Cursor am linken
Rand des Textfeldes vor der Anzeige. Wurde ein
unparametrisiertes Model übergeben oder für start
und end
eines parametrisierten Models null
angegeben, so wird bei Betätigen der Pfeilbuttons
der erste angezeigte Wert, hier, bei deutschem
Datumsformat, der Tag, erhöht oder erniedrigt.
Setzt man den Cursor in den Bereich der Jahreszahl oder
markiert sie, so wird diese verändert, usw. Die
Änderung bezieht sich immer auf den jeweils
markierten Anteil des angezeigten Datum-Zeit-Wertes.
Ist es erforderlich, nur vorher festgelegte Datums- oder
Zeitbereiche zu ändern, verwendet man einen
entsprechend parametrisierten Model-Konstruktor oder man
setzt die Schrittweite durch Aufruf von setCalendarField()
z.B. auf Calendar.HOUR_OF_DAY
. In diesem
Fall wird erst dann eine Aktivität beim
Betätigen der Buttons registriert, wenn, nach
Markieren eines Teilbereiches, sich die Änderung
innerhalb der gesetzten Datums-Zeit-Grenze befindet.
Hier zeigt sich der Einfluss des Parameters calendarField
:
Er bewirkt, dass nur die durch ihn angegebene und
kleinere Einheiten geändert werden können.
Dies gilt natürlich nur im Rahmen des durch start
und end
gesetzten Wertebereichs, sofern
dieser nicht durch Setzen von null
aufgehoben wurde.
Der DateEditor
Der DateEditor ist die für die Anzeige
zuständige Komponente. Sie wird durch die innere
Klasse JSpinner.DateEditor
bereitgestellt
und ist von JSpinner.DefaultEditor
abgeleitet. Auf Ebene des GUI besteht er aus einem JPanel
mit einem JFormattedTextField
, auf das auf
die folgende Weise zugegriffen werden kann.
JFormattedTextField field = ((JSpinner.DateEditor)spinner.getEditor()).getTextField();
Auf diese Weise kann auch der DateFormatter
des Textfeldes ermittelt und bei Bedarf modifiziert
werden. Durch setAllowsInvalid()
kann z.B.
eine Überprüfung des eingegebenen Wertes
erzwungen werden.
DateFormatter formatter = (DateFormatter) dateEditor.getTextField().getFormatter(); formatter.setAllowsInvalid(false);
Für die Einrichtung der Anzeigeformatierung ist ein
direkter Zugriff allerdings im Allgemeinen nicht
nötig. In den meisten Fällen bietet es sich
an, einen eigenen Editor zu erzeugen und diesen durch setEditor()
an den Spinner zu übergeben. Sein Konstruktor kann
als zweites Argument einen Formatierungs-String
erhalten, der das Datum-Zeit-Format vorgibt und den
Definitionen des SimpleDateFormat
folgen
muss.
Selbstverständlich muss darauf geachtet
werden, dass sich der Wert auch innerhalb des eventuell
durch ein eigenes Model festgelegten Bereichs befindet.
JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(spinner, "HH:mm:ss"); // z.B. 20:15:25 spinner.setEditor(dateEditor);
Hier steckt der Teufel allerdings im Detail! Das folgende Beispiel ist in der angegebenen Form so nicht verwendbar:
Calendar cal = new GregorianCalendar(); Date now = cal.getTime(); System.out.println("now: " + now); cal.set(Calendar.HOUR_OF_DAY, 5); Date start = cal.getTime(); System.out.println("early: " + start); cal.set(Calendar.HOUR_OF_DAY, 23); Date end = cal.getTime(); System.out.println("late: " + end); SpinnerDateModel model = new SpinnerDateModel(now, start, end, Calendar.HOUR_OF_DAY); JSpinner spinner = new JSpinner(model); JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(spinner, "HH:mm:ss"); // nicht spinnbar spinner.setEditor(dateEditor); //...
Es wird erwartet, dass zwischen 5 und 23 Uhr eine
Anpassung des Spinners im Stunden-, Minuten- und
Sekundenbereich möglich ist. Diese Erwartung wird
jedoch nicht erfüllt.
Die Ursache dieser
Dysfunktionalität liegt in der Art und Weise der
internen Verarbeitung der beteiligten Werte: Dem Editor
werden Date
-Objekte in Form von
Millisekunden seit dem 1. Januar 1970 00:00:00
übergeben. Bei der Validierung einer Eingabe,
entweder nach Klicken auf einen der Pfeile oder nach
manueller Eingabe wird der String in ein Date-Objekt
gewandelt. Dies wird anschließend mit den
angegebenen Grenzen abgeglichen. Fehlt - wie oben - die
Datumsangabe, so wird vom 1.1.1970 ausgegangen, sodass
der Bereich des zulässigen Intervalls
unterschritten wird. Ein ähnliches Verhalten gilt
ggf. auch für andere Teile des Datum-Zeit-Strings.
Mögliche Lösungen bestehen darin, entweder das
für start
und end
verwendete Date
-Objekt auf den 1.1.1970 zu
setzen
Calendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, 1970); cal.set(Calendar.MONTH, 0); cal.set(Calendar.DAY_OF_MONTH, 1); Date now = cal.getTime(); //...
oder aber den Spinner nicht zu begrenzen, indem die
Grenzwerte auf null
gesetzt werden.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.