eu.schudt.javafx.controls.calendar.DatePicker Maven / Gradle / Ivy
package eu.schudt.javafx.controls.calendar;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Popup;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
/**
* This is the class DateControl is based on. I change the modifier to default
* (package visibility) so it won't be visible by TiwulFX developer. I didn't delete
* it just for future reference
* @author Christian Schudt
* http://myjavafx.blogspot.com/2012/01/javafx-calendar-control.html
*/
class DatePicker extends HBox {
private static final String CSS_DATE_PICKER_VALID = "datepicker-valid";
private static final String CSS_DATE_PICKER_INVALID = "datepicker-invalid";
/**
* Initializes the date picker with the default locale.
*/
public DatePicker() {
this(Locale.getDefault());
}
private Timer timer;
/**
* Initializes the date picker with the given locale.
*
* @param locale The locale.
*/
public DatePicker(Locale locale) {
calendarView = new CalendarView(locale);
textField = new TextField();
this.locale.set(locale);
calendarView.setEffect(new DropShadow());
// Use the same locale.
calendarView.localeProperty().bind(localeProperty());
// Bind the current date of the calendar view with the selected date, so that the calendar shows up with the same month as in the text field.
calendarView.currentDateProperty().bind(selectedDateProperty());
// When the user selects a date in the calendar view, hide it.
calendarView.selectedDateProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
selectedDate.set(calendarView.selectedDateProperty().get());
hidePopup();
}
});
// Let the prompt text property listen to locale or date format changes.
textField.promptTextProperty().bind(new StringBinding() {
{
super.bind(localeProperty(), promptTextProperty(), dateFormatProperty());
}
@Override
protected String computeValue() {
// First check, if there is a custom prompt text.
if (promptTextProperty().get() != null) {
return promptTextProperty().get();
}
// If not, use the the date format's pattern.
DateFormat dateFormat = getActualDateFormat();
if (dateFormat instanceof SimpleDateFormat) {
return ((SimpleDateFormat) dateFormat).toPattern();
}
return "";
}
});
// Change the CSS styles, when this control becomes invalid.
invalid.addListener(new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
if (invalid.get()) {
textField.getStyleClass().add(CSS_DATE_PICKER_INVALID);
textField.getStyleClass().remove(CSS_DATE_PICKER_VALID);
} else {
textField.getStyleClass().remove(CSS_DATE_PICKER_INVALID);
textField.getStyleClass().add(CSS_DATE_PICKER_VALID);
}
}
});
// When the text field no longer has the focus, try to parse the date.
textField.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler() {
@Override
public void handle(MouseEvent event) {
if (!textField.focusedProperty().get()) {
if (!textField.getText().equals("")) {
tryParse(true);
}
} else {
showPopup();
}
}
});
// Listen to user input.
textField.textProperty().addListener(new ChangeListener() {
@Override
public void changed(ObservableValue extends String> observableValue, String s, String s1) {
// Only evaluate the input, it it wasn't set programmatically.
if (textSetProgrammatically) {
return;
}
if (timer != null) {
timer.cancel();
}
// If the user clears the text field, set the date to null and the field to valid.
if (s1.equals("")) {
selectedDate.set(null);
invalid.set(false);
} else {
// Start a timer, so that the user input is not evaluated immediately, but after a second.
// This way, input like 01/01/1 is not immediately parsed as 01/01/01.
// The user gets one second time, to complete his date, maybe his intention was to enter 01/01/12.
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
Platform.runLater(new Runnable() {
@Override
public void run() {
tryParse(false);
}
});
}
}, 1000);
}
}
});
selectedDateProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
updateTextField();
invalid.set(false);
}
});
localeProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
updateTextField();
}
});
textField.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler() {
@Override
public void handle(KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.DOWN) {
showPopup();
}
}
});
Button button = new Button(">");
button.setFocusTraversable(false);
button.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent actionEvent) {
showPopup();
}
});
getChildren().add(textField);
//getChildren().add(button);
}
private void hidePopup() {
if (popup != null) {
popup.hide();
}
}
/**
* Tries to parse the text field for a valid date.
*
* @param setDateToNullOnException True, if the date should be set to null, when a {@link ParseException} occurs.
* This is the case, when the text field loses focus.
*/
private void tryParse(boolean setDateToNullOnException) {
if (timer != null) {
timer.cancel();
}
try {
// Double parse the date here, since e.g. 01.01.1 is parsed as year 1, and then formatted as 01.01.01 and then parsed as year 2001.
// This might lead to an undesired date.
DateFormat dateFormat = getActualDateFormat();
Date parsedDate = dateFormat.parse(textField.getText());
parsedDate = dateFormat.parse(dateFormat.format(parsedDate));
if (selectedDate.get() == null || selectedDate.get() != null && parsedDate.getTime() != selectedDate.get().getTime()) {
selectedDate.set(parsedDate);
}
invalid.set(false);
updateTextField();
} catch (ParseException e) {
invalid.set(true);
if (setDateToNullOnException) {
selectedDate.set(null);
}
}
}
private boolean textSetProgrammatically;
/**
* Updates the text field.
*/
public void updateTextField() {
// Mark the we update the text field (and not the user), so that it can be ignored, by textField.textProperty()
textSetProgrammatically = true;
if (selectedDateProperty().get() != null) {
String date = getActualDateFormat().format(selectedDateProperty().get());
if (!textField.getText().equals(date)) {
textField.setText(date);
}
} else {
textField.setText("");
}
textSetProgrammatically = false;
}
/**
* Gets the actual date format. If {@link #dateFormatProperty()} is set, take it, otherwise get a default format for the current locale.
*
* @return The date format.
*/
private DateFormat getActualDateFormat() {
if (dateFormat.get() != null) {
return dateFormat.get();
}
DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, locale.get());
format.setCalendar(calendarView.getCalendar());
format.setLenient(false);
return format;
}
private CalendarView calendarView;
/**
* Use this to set further properties of the calendar.
*
* @return The calendar view.
*/
public CalendarView getCalendarView() {
return calendarView;
}
private TextField textField;
private BooleanProperty invalid = new SimpleBooleanProperty();
/**
* States whether the user input is invalid (is no valid date).
*
* @return The property.
*/
public ReadOnlyBooleanProperty invalidProperty() {
return invalid;
}
/**
* The locale.
*
* @return The property.
*/
public ObjectProperty localeProperty() {
return locale;
}
private ObjectProperty locale = new SimpleObjectProperty();
public void setLocale(Locale locale) {
this.locale.set(locale);
}
public Locale getLocale() {
return locale.get();
}
/**
* The selected date.
*
* @return The property.
*/
public ObjectProperty selectedDateProperty() {
return selectedDate;
}
private ObjectProperty selectedDate = new SimpleObjectProperty();
public void setSelectedDate(Date date) {
this.selectedDate.set(date);
}
public Date getSelectedDate() {
return selectedDate.get();
}
/**
* Gets the date format.
*
* @return The date format.
*/
public ObjectProperty dateFormatProperty() {
return dateFormat;
}
private ObjectProperty dateFormat = new SimpleObjectProperty();
public void setDateFormat(DateFormat dateFormat) {
this.dateFormat.set(dateFormat);
}
public DateFormat getDateFormat() {
return dateFormat.get();
}
private StringProperty promptText = new SimpleStringProperty();
/**
* The prompt text for the text field.
* By default, the prompt text is taken from the date format pattern.
*
* @return The property.
*/
public StringProperty promptTextProperty() {
return promptText;
}
public void setPromptText(String promptText) {
this.promptText.set(promptText);
}
public String getPromptText() {
return promptText.get();
}
private Popup popup;
/**
* Shows the pop up.
*/
private void showPopup() {
if (popup == null) {
popup = new Popup();
popup.setAutoHide(true);
popup.setHideOnEscape(true);
popup.setAutoFix(true);
popup.getContent().add(calendarView);
}
Bounds calendarBounds = calendarView.getBoundsInLocal();
Bounds bounds = localToScene(getBoundsInLocal());
double posX = calendarBounds.getMinX() + bounds.getMinX() + getScene().getX() + getScene().getWindow().getX();
double posY = calendarBounds.getMinY() + bounds.getHeight() + bounds.getMinY() + getScene().getY() + getScene().getWindow().getY();
popup.show(this, posX, posY);
}
@Override
public void requestFocus() {
textField.requestFocus();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy