All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javafx.scene.control.DatePicker Maven / Gradle / Ivy

There is a newer version: 24-ea+15
Show newest version
/*
 * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene.control;

// editor and converter code in sync with ComboBox 4858:e60e9a5396e6

import java.time.LocalDate;
import java.time.DateTimeException;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import com.sun.javafx.scene.control.FakeFocusTextField;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableProperty;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.LocalDateStringConverter;

import javafx.css.converter.BooleanConverter;
import javafx.scene.control.skin.DatePickerSkin;
import com.sun.javafx.scene.control.skin.resources.ControlResources;

/**
 * The DatePicker control allows the user to enter a date as text or
 * to select a date from a calendar popup. The calendar is based on
 * either the standard ISO-8601 chronology or any of the other
 * chronology classes defined in the java.time.chrono package.
 *
 * 

The {@link #valueProperty() value} property represents the * currently selected {@link java.time.LocalDate}. An initial date can * be set via the {@link #DatePicker(java.time.LocalDate) constructor} * or by calling {@link #setValue setValue(LocalDate)}. The * default value is null. * *

 DatePicker datePicker = new DatePicker();
 * datePicker.setOnAction(e {@literal ->} {
 *     LocalDate date = datePicker.getValue();
 *     System.err.println("Selected date: " + date);
 * });
* * Image of the DatePicker control * *

The {@link #chronologyProperty() chronology} property specifies a * calendar system to be used for parsing, displaying, and choosing * dates. * The {@link #valueProperty() value} property is always defined in * the ISO calendar system, however, so applications based on a * different chronology may use the conversion methods provided in the * {@link java.time.chrono.Chronology} API to get or set the * corresponding {@link java.time.chrono.ChronoLocalDate} value. For * example: * *

LocalDate isoDate = datePicker.getValue();
 * ChronoLocalDate chronoDate =
 *     ((isoDate != null) ? datePicker.getChronology().date(isoDate) : null);
 * System.err.println("Selected date: " + chronoDate);
* * @since JavaFX 8.0 */ public class DatePicker extends ComboBoxBase { private LocalDate lastValidDate = null; private Chronology lastValidChronology = IsoChronology.INSTANCE; /** * Creates a default DatePicker instance with a null date value set. */ public DatePicker() { this(null); } /** * Creates a DatePicker instance and sets the * {@link #valueProperty() value} to the given date. * * @param localDate to be set as the currently selected date in the DatePicker. Can be null. */ public DatePicker(LocalDate localDate) { valueProperty().addListener(observable -> { LocalDate date = getValue(); Chronology chrono = getChronology(); if (validateDate(chrono, date)) { lastValidDate = date; } else { //System.err.println("Restoring value to " + // ((lastValidDate == null) ? "null" : getConverter().toString(lastValidDate))); setValue(lastValidDate); } }); chronologyProperty().addListener(observable -> { LocalDate date = getValue(); Chronology chrono = getChronology(); if (validateDate(chrono, date)) { lastValidChronology = chrono; defaultConverter = new LocalDateStringConverter(FormatStyle.SHORT, null, chrono); } else { //System.err.println("Restoring value to " + lastValidChronology); setChronology(lastValidChronology); } }); setValue(localDate); getStyleClass().add(DEFAULT_STYLE_CLASS); setAccessibleRole(AccessibleRole.DATE_PICKER); setEditable(true); focusedProperty().addListener(o -> { if (!isFocused()) { try { commitValue(); } catch (Exception e) { cancelEdit(); } } }); } private boolean validateDate(Chronology chrono, LocalDate date) { try { if (date != null) { chrono.date(date); } return true; } catch (DateTimeException ex) { //System.err.println(ex); return false; } } /* ************************************************************************* * * * Properties * * * **************************************************************************/ /** * A custom cell factory can be provided to customize individual * day cells in the DatePicker popup. Refer to {@link DateCell} * and {@link Cell} for more information on cell factories. * Example: * *

     * final Callback<DatePicker, DateCell> dayCellFactory = new Callback<DatePicker, DateCell>() {
     *     public DateCell call(final DatePicker datePicker) {
     *         return new DateCell() {
     *             @Override public void updateItem(LocalDate item, boolean empty) {
     *                 super.updateItem(item, empty);
     *
     *                 if (MonthDay.from(item).equals(MonthDay.of(9, 25))) {
     *                     setTooltip(new Tooltip("Happy Birthday!"));
     *                     setStyle("-fx-background-color: #ff4444;");
     *                 }
     *                 if (item.equals(LocalDate.now().plusDays(1))) {
     *                     // Tomorrow is too soon.
     *                     setDisable(true);
     *                 }
     *             }
     *         };
     *     }
     * };
     * datePicker.setDayCellFactory(dayCellFactory);
     * 
* * @defaultValue null */ private ObjectProperty> dayCellFactory; public final void setDayCellFactory(Callback value) { dayCellFactoryProperty().set(value); } public final Callback getDayCellFactory() { return (dayCellFactory != null) ? dayCellFactory.get() : null; } public final ObjectProperty> dayCellFactoryProperty() { if (dayCellFactory == null) { dayCellFactory = new SimpleObjectProperty<>(this, "dayCellFactory"); } return dayCellFactory; } /** * The calendar system used for parsing, displaying, and choosing * dates in the DatePicker control. * *

The default value is returned from a call to * {@code Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT))}. * The default is usually {@link java.time.chrono.IsoChronology} unless * provided explicitly in the {@link java.util.Locale} by use of a * Locale calendar extension. * * Setting the value to null will restore the default * chronology. * @return a property representing the Chronology being used */ public final ObjectProperty chronologyProperty() { return chronology; } private ObjectProperty chronology = new SimpleObjectProperty<>(this, "chronology", null); public final Chronology getChronology() { Chronology chrono = chronology.get(); if (chrono == null) { try { chrono = Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT)); } catch (Exception ex) { //System.err.println(ex); } if (chrono == null) { chrono = IsoChronology.INSTANCE; } //System.err.println(chrono); } return chrono; } public final void setChronology(Chronology value) { chronology.setValue(value); } /** * Whether the DatePicker popup should display a column showing * week numbers. * *

The default value is specified in a resource bundle, and * depends on the country of the current locale. * @return true if popup should display a column showing * week numbers */ public final BooleanProperty showWeekNumbersProperty() { if (showWeekNumbers == null) { String country = Locale.getDefault(Locale.Category.FORMAT).getCountry(); boolean localizedDefault = (!country.isEmpty() && ControlResources.getNonTranslatableString("DatePicker.showWeekNumbers").contains(country)); showWeekNumbers = new StyleableBooleanProperty(localizedDefault) { @Override public CssMetaData getCssMetaData() { return StyleableProperties.SHOW_WEEK_NUMBERS; } @Override public Object getBean() { return DatePicker.this; } @Override public String getName() { return "showWeekNumbers"; } }; } return showWeekNumbers; } private BooleanProperty showWeekNumbers; public final void setShowWeekNumbers(boolean value) { showWeekNumbersProperty().setValue(value); } public final boolean isShowWeekNumbers() { return showWeekNumbersProperty().getValue(); } // --- string converter /** * Converts the input text to an object of type LocalDate and vice * versa. * *

If not set by the application, the DatePicker skin class will * set a converter based on a {@link java.time.format.DateTimeFormatter} * for the current {@link java.util.Locale} and * {@link #chronologyProperty() chronology}. This formatter is * then used to parse and display the current date value. * * Setting the value to null will restore the default * converter. * *

Example using an explicit formatter: *


     * datePicker.setConverter(new StringConverter<LocalDate>() {
     *     String pattern = "yyyy-MM-dd";
     *     DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern);
     *
     *     {
     *         datePicker.setPromptText(pattern.toLowerCase());
     *     }
     *
     *     {@literal @Override public String toString(LocalDate date) {
     *         if (date != null) {
     *             return dateFormatter.format(date);
     *         } else {
     *             return "";
     *         }
     *     }}
     *
     *     {@literal @Override public LocalDate fromString(String string) {
     *         if (string != null && !string.isEmpty()) {
     *             return LocalDate.parse(string, dateFormatter);
     *         } else {
     *             return null;
     *         }
     *     }}
     * });
     * 
*

Example that wraps the default formatter and catches parse exceptions: *


     *   final StringConverter<LocalDate> defaultConverter = datePicker.getConverter();
     *   datePicker.setConverter(new StringConverter<LocalDate>() {
     *       @Override public String toString(LocalDate value) {
     *           return defaultConverter.toString(value);
     *       }
     *
     *       @Override public LocalDate fromString(String text) {
     *           try {
     *               return defaultConverter.fromString(text);
     *           } catch (DateTimeParseException ex) {
     *               System.err.println("HelloDatePicker: "+ex.getMessage());
     *               throw ex;
     *           }
     *       }
     *   });
     * 
* *

The default base year for parsing input containing only two digits for * the year is 2000 (see {@link java.time.format.DateTimeFormatter}). This * default is not useful for allowing a person's date of birth to be typed. * The following example modifies the converter's fromString() method to * allow a two digit year for birth dates up to 99 years in the past. *


     *   {@literal @Override public LocalDate fromString(String text) {
     *       if (text != null && !text.isEmpty()) {
     *           Locale locale = Locale.getDefault(Locale.Category.FORMAT);
     *           Chronology chrono = datePicker.getChronology();
     *           String pattern =
     *               DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT,
     *                                                                    null, chrono, locale);
     *           String prePattern = pattern.substring(0, pattern.indexOf("y"));
     *           String postPattern = pattern.substring(pattern.lastIndexOf("y")+1);
     *           int baseYear = LocalDate.now().getYear() - 99;
     *           DateTimeFormatter df = new DateTimeFormatterBuilder()
     *                       .parseLenient()
     *                       .appendPattern(prePattern)
     *                       .appendValueReduced(ChronoField.YEAR, 2, 2, baseYear)
     *                       .appendPattern(postPattern)
     *                       .toFormatter();
     *           return LocalDate.from(chrono.date(df.parse(text)));
     *       } else {
     *           return null;
     *       }
     *   }}
     * 
* * @return the property representing the current LocalDate string converter * @see javafx.scene.control.ComboBox#converterProperty */ public final ObjectProperty> converterProperty() { return converter; } private ObjectProperty> converter = new SimpleObjectProperty<>(this, "converter", null); public final void setConverter(StringConverter value) { converterProperty().set(value); } public final StringConverter getConverter() { StringConverter converter = converterProperty().get(); if (converter != null) { return converter; } else { return defaultConverter; } } // Create a symmetric (format/parse) converter with the default locale. private StringConverter defaultConverter = new LocalDateStringConverter(FormatStyle.SHORT, null, getChronology()); // --- Editor /** * The editor for the DatePicker. * * @see javafx.scene.control.ComboBox#editorProperty */ private ReadOnlyObjectWrapper editor; public final TextField getEditor() { return editorProperty().get(); } public final ReadOnlyObjectProperty editorProperty() { if (editor == null) { editor = new ReadOnlyObjectWrapper<>(this, "editor"); editor.set(new FakeFocusTextField()); } return editor.getReadOnlyProperty(); } /** {@inheritDoc} */ @Override protected Skin createDefaultSkin() { return new DatePickerSkin(this); } /** * If the {@link DatePicker} is {@link #editableProperty() editable}, calling this method will attempt to * commit the current text and convert it to a {@link #valueProperty() value}. * @since 18 */ public final void commitValue() { if (!isEditable()) { return; } String text = getEditor().getText(); StringConverter converter = getConverter(); if (converter != null) { LocalDate value = converter.fromString(text); setValue(value); } } /** * If the {@link DatePicker} is {@link #editableProperty() editable}, calling this method will attempt to * replace the editor text with the last committed {@link #valueProperty() value}. * @since 18 */ public final void cancelEdit() { if (!isEditable()) { return; } LocalDate committedValue = getValue(); StringConverter converter = getConverter(); if (converter != null) { String valueString = converter.toString(committedValue); getEditor().setText(valueString); } } /* ************************************************************************* * * * Stylesheet Handling * * * **************************************************************************/ private static final String DEFAULT_STYLE_CLASS = "date-picker"; private static class StyleableProperties { private static final String country = Locale.getDefault(Locale.Category.FORMAT).getCountry(); private static final CssMetaData SHOW_WEEK_NUMBERS = new CssMetaData<>("-fx-show-week-numbers", BooleanConverter.getInstance(), (!country.isEmpty() && ControlResources.getNonTranslatableString("DatePicker.showWeekNumbers").contains(country))) { @Override public boolean isSettable(DatePicker n) { return n.showWeekNumbers == null || !n.showWeekNumbers.isBound(); } @Override public StyleableProperty getStyleableProperty(DatePicker n) { return (StyleableProperty)n.showWeekNumbersProperty(); } }; private static final List> STYLEABLES; static { final List> styleables = new ArrayList<>(Control.getClassCssMetaData()); Collections.addAll(styleables, SHOW_WEEK_NUMBERS ); STYLEABLES = Collections.unmodifiableList(styleables); } } /** * Gets the {@code CssMetaData} associated with this class, which may include the * {@code CssMetaData} of its superclasses. * @return the {@code CssMetaData} */ public static List> getClassCssMetaData() { return StyleableProperties.STYLEABLES; } /** * {@inheritDoc} * @since JavaFX 8.0 */ @Override public List> getControlCssMetaData() { return getClassCssMetaData(); } /* ************************************************************************* * * * Accessibility handling * * * **************************************************************************/ /** {@inheritDoc} */ @Override public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { switch (attribute) { case DATE: return getValue(); case TEXT: { String accText = getAccessibleText(); if (accText != null && !accText.isEmpty()) return accText; LocalDate date = getValue(); StringConverter c = getConverter(); if (date != null && c != null) { return c.toString(date); } return ""; } default: return super.queryAccessibleAttribute(attribute, parameters); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy