In the application I'm currently working on, I need to select one date or period from the same JavaFX 8 DatePicker.
The preferred way to do this would be as follows:
Selecting a single date is the same as the default behavior for DatePicker.
Period selection - select the start / end date by holding down the mouse button and dragging it to the desired end / start date. When the mouse button is released, you have determined your period. Acceptable is that you cannot select dates other than those displayed.
Editing should work both for one date (ex 24.12.2014) and for a period (for example: 12.24.2014 - 12.27.2014)
A possible rendering of the selected period (minus the contents of the text editor) above will look like this:

If orange indicates the current date, blue indicates the selected period. The image is taken from a prototype that I made, but where the period is selected using 2 DatePickers, not one.
I looked at the source code for
com.sun.javafx.scene.control.skin.DatePickerContent
which has
protected List<DateCell> dayCells = new ArrayList<DateCell>();
to find a detection method when the mouse chose the end date when the mouse was released (or, possibly, drag and drop detection).
However, I'm not quite sure how to do this. Any suggestions?
I am attaching a simple prototype code that I have made so far (which uses 2 rather than the desired 1 datepicker).

import java.time.LocalDate; import javafx.beans.property.SimpleObjectProperty; public interface PeriodController { /** * @return Today. */ LocalDate currentDate(); /** * @return Selected from date. */ SimpleObjectProperty<LocalDate> fromDateProperty(); /** * @return Selected to date. */ SimpleObjectProperty<LocalDate> toDateProperty(); } import java.time.LocalDate; import java.time.format.DateTimeFormatter; import javafx.util.StringConverter; public class DateConverter extends StringConverter<LocalDate> { private DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"); // TODO i18n @Override public String toString(LocalDate date) { if (date != null) { return dateFormatter.format(date); } else { return ""; } } @Override public LocalDate fromString(String string) { if (string != null && !string.isEmpty()) { return LocalDate.parse(string, dateFormatter); } else { return null; } } } import static java.lang.System.out; import java.time.LocalDate; import java.util.Locale; import javafx.application.Application; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import javafx.geometry.HPos; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class PeriodMain extends Application { private Stage stage; public static void main(String[] args) { Locale.setDefault(new Locale("no", "NO")); launch(args); } @Override public void start(Stage stage) { this.stage = stage; stage.setTitle("Period prototype "); initUI(); stage.getScene().getStylesheets().add(getClass().getResource("/period-picker.css").toExternalForm()); stage.show(); } private void initUI() { VBox vbox = new VBox(20); vbox.setStyle("-fx-padding: 10;"); Scene scene = new Scene(vbox, 400, 200); stage.setScene(scene); final PeriodPickerPrototype periodPickerPrototype = new PeriodPickerPrototype(new PeriodController() { SimpleObjectProperty<LocalDate> fromDate = new SimpleObjectProperty<>(); SimpleObjectProperty<LocalDate> toDate = new SimpleObjectProperty<>(); { final ChangeListener<LocalDate> dateListener = (observable, oldValue, newValue) -> { if (fromDate.getValue() != null && toDate.getValue() != null) { out.println("Selected period " + fromDate.getValue() + " - " + toDate.getValue()); } }; fromDate.addListener(dateListener); toDate.addListener(dateListener); } @Override public LocalDate currentDate() { return LocalDate.now(); } @Override public SimpleObjectProperty<LocalDate> fromDateProperty() { return fromDate; } @Override public SimpleObjectProperty<LocalDate> toDateProperty() { return toDate; } }); GridPane gridPane = new GridPane(); gridPane.setHgap(10); gridPane.setVgap(10); Label checkInlabel = new Label("Check-In Date:"); GridPane.setHalignment(checkInlabel, HPos.LEFT); gridPane.add(periodPickerPrototype, 0, 1); vbox.getChildren().add(gridPane); } } import java.time.LocalDate; import javafx.beans.value.ChangeListener; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.control.DateCell; import javafx.scene.control.DatePicker; import javafx.scene.control.Label; import javafx.scene.control.Tooltip; import javafx.scene.layout.GridPane; import javafx.util.Callback; import javafx.util.StringConverter; /** * Selecting a single date or a period - only a prototype. * As long as you have made an active choice on the {@code toDate}, the {@code fromDate} and {@code toDate} will have the same date. */ public class PeriodPickerPrototype extends GridPane { private static final String CSS_CALENDAR_BEFORE = "calendar-before"; private static final String CSS_CALENDAR_BETWEEN = "calendar-between"; private static final String CSS_CALENDAR_TODAY = "calendar-today"; private static final boolean DISPLAY_WEEK_NUMBER = true; private Label fromLabel; private Label toLabel; private DatePicker fromDate; private DatePicker toDate; private StringConverter<LocalDate> converter; private PeriodController controller; private ChangeListener<LocalDate> fromDateListener; private ChangeListener<LocalDate> toDateListener; private Callback<DatePicker, DateCell> toDateCellFactory; private Callback<DatePicker, DateCell> fromDateCellFactory; private Tooltip todayTooltip; private boolean toDateIsActivlyChosenbyUser; public PeriodPickerPrototype(final PeriodController periodController) { this.controller = periodController; createComponents(); makeLayout(); createHandlers(); bindAndRegisterHandlers(); i18n(); initComponent(); } public void createComponents() { fromLabel = new Label(); toLabel = new Label(); fromDate = new DatePicker(); toDate = new DatePicker(); todayTooltip = new Tooltip(); } public void createHandlers() { fromDate.setOnAction(event -> { if ((!toDateIsActivlyChosenbyUser) || fromDate.getValue().isAfter(toDate.getValue())) { setDateWithoutFiringEvent(fromDate.getValue(), toDate); toDateIsActivlyChosenbyUser = false; } }); toDate.setOnAction(event -> toDateIsActivlyChosenbyUser = true); fromDateCellFactory = new Callback<DatePicker, DateCell>() { @Override public DateCell call(final DatePicker datePicker) { return new DateCell() { @Override public void updateItem(LocalDate item, boolean empty) { super.updateItem(item, empty); getStyleClass().removeAll(CSS_CALENDAR_TODAY, CSS_CALENDAR_BEFORE, CSS_CALENDAR_BETWEEN); if ((item.isBefore(toDate.getValue()) || item.isEqual(toDate.getValue())) && item.isAfter(fromDate.getValue())) { getStyleClass().add(CSS_CALENDAR_BETWEEN); } if (item.isEqual(controller.currentDate())) { getStyleClass().add(CSS_CALENDAR_TODAY); setTooltip(todayTooltip); } else { setTooltip(null); } } }; } }; toDateCellFactory = new Callback<DatePicker, DateCell>() { @Override public DateCell call(final DatePicker datePicker) { return new DateCell() { @Override public void updateItem(LocalDate item, boolean empty) { super.updateItem(item, empty); setDisable(item.isBefore(fromDate.getValue())); getStyleClass().removeAll(CSS_CALENDAR_TODAY, CSS_CALENDAR_BEFORE, CSS_CALENDAR_BETWEEN); if (item.isBefore(fromDate.getValue())) { getStyleClass().add(CSS_CALENDAR_BEFORE); } else if (item.isBefore(toDate.getValue()) || item.isEqual(toDate.getValue())) { getStyleClass().add(CSS_CALENDAR_BETWEEN); } if (item.isEqual(controller.currentDate())) { getStyleClass().add(CSS_CALENDAR_TODAY); setTooltip(todayTooltip); } else { setTooltip(null); } } }; } }; converter = new DateConverter(); fromDateListener = (observableValue, oldValue, newValue) -> { if (newValue == null) { // Restting old value and cancel.. setDateWithoutFiringEvent(oldValue, fromDate); return; } controller.fromDateProperty().set(newValue); }; toDateListener = (observableValue, oldValue, newValue) -> { if (newValue == null) { // Restting old value and cancel.. setDateWithoutFiringEvent(oldValue, toDate); return; } controller.toDateProperty().set(newValue); }; } /** * Changes the date on {@code datePicker} without fire {@code onAction} event. */ private void setDateWithoutFiringEvent(LocalDate newDate, DatePicker datePicker) { final EventHandler<ActionEvent> onAction = datePicker.getOnAction(); datePicker.setOnAction(null); datePicker.setValue(newDate); datePicker.setOnAction(onAction); } public void bindAndRegisterHandlers() { toDate.setDayCellFactory(toDateCellFactory); fromDate.setDayCellFactory(fromDateCellFactory); fromDate.valueProperty().addListener(fromDateListener); fromDate.setConverter(converter); toDate.valueProperty().addListener(toDateListener); toDate.setConverter(converter); } public void makeLayout() { setHgap(6); add(fromLabel, 0, 0); add(fromDate, 1, 0); add(toLabel, 2, 0); add(toDate, 3, 0); fromDate.setPrefWidth(120); toDate.setPrefWidth(120); fromLabel.setId("calendar-label"); toLabel.setId("calendar-label"); } public void i18n() { // i18n code replaced with fromDate.setPromptText("dd.mm.yyyy"); toDate.setPromptText("dd.mm.yyyy"); fromLabel.setText("From"); toLabel.setText("To"); todayTooltip.setText("Today"); } public void initComponent() { fromDate.setTooltip(null); // รnsker ikke tooltip setDateWithoutFiringEvent(controller.currentDate(), fromDate); fromDate.setShowWeekNumbers(DISPLAY_WEEK_NUMBER); toDate.setTooltip(null); // รnsker ikke tooltip setDateWithoutFiringEvent(controller.currentDate(), toDate); toDate.setShowWeekNumbers(DISPLAY_WEEK_NUMBER); } } /** period-picker.css goes udner resources (using maven) **/ .date-picker { /* -fx-font-size: 11pt;*/ } .calendar-before { } .calendar-between { -fx-background-color: