W aplikacji, w której aktualnie pracuję, konieczne jest wybranie jednej daty lub kropki z tego samego JavaFX 8 DatePicker.Wybieranie okresu lub daty przy użyciu JEDNEGO JavaFX 8 DatePicker
Preferowanym sposobem w ten sposób będzie w następujący sposób:
Wybór jeden termin - tak samo jak domyślne zachowanie DatePicker.
Wybór okresu - wybierz datę rozpoczęcia/zakończenia, przytrzymując przycisk myszy i przeciągnij do żądanej daty zakończenia/rozpoczęcia. Po zwolnieniu przycisku myszy zdefiniowano okres. Nie można zaakceptować dat innych niż wyświetlone.
Montaż powinien działać zarówno dla pojedynczego daty (ex 24.12.2014) i okres (ex: 24.12.2014 - 27.12.2014)
Możliwym renderowania wybranego okresu (minus treści edytor tekstu) powyżej będzie wyglądać następująco:
Gdzie pomarańczowy wskazuje aktualną datę, niebieski wskazuje wybrany okres. Zdjęcie pochodzi z wykonanego przeze mnie prototypu, ale w którym okres jest wybierany za pomocą dwóch formatów DatePickers zamiast jednego.
miałem przyjrzeć źródłowego
com.sun.javafx.scene.control.skin.DatePickerContent
który ma
protected List<DateCell> dayCells = new ArrayList<DateCell>();
w celu znalezienia sposobu wykrywania gdy mysz wybrana data zakończenia, gdy mysz została wydana (lub może wykryć opór).
Jednak nie jestem do końca pewny, jak do tego dojść. Jakieś sugestie?
Dołączam prosty prototypowy kod, który do tej pory zrobiłem (który używa 2 zamiast pożądanego 1 datepika).
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: #bce9ff;
}
.calendar-between:hover {
-fx-background-color: rgb(0, 150, 201);
}
.calendar-between:focused {
-fx-background-color: rgb(0, 150, 201);
}
.calendar-today {
-fx-background-color: rgb(255, 218, 111);
}
.calendar-today:hover {
-fx-background-color: rgb(0, 150, 201);
}
.calendar-today:focused {
-fx-background-color: rgb(0, 150, 201);
}
#calendar-label {
-fx-font-style: italic;
-fx-fill: rgb(75, 75, 75);
-fx-font-size: 11;
}
Było to zgodne z tym, co szukałem. W dalszej części opublikuję bardziej niezawodne rozwiązanie. Muszę być w stanie wybrać dni do następnego miesiąca, więc zamiast używać "getText" do wyprowadzenia daty, użyję refleksji, aby uzyskać dostęp do "dayCellDate" w "DatePickerContent", aby poprawnie zidentyfikować "LocalDate". Ponadto, kiedy wybór zostanie dokonany, powinien on zostać zamknięty, więc dodam 'datePicker.hide();'. Powinno działać zarówno wstecz, jak i do przodu, więc dodanie pierwszej/ostatniej metody pozwoli ci wybrać 25 pierwszych i 11 ostatnich (w przykładzie). Doceniam twój dowód koncepcji. – Skjalg
Dzięki. Do 'następnego miesiąca' masz na myśli wybór dni już pokazanych w tej samej sieci, prawda? Właśnie usunąłem je z selekcji komórek dla uproszczenia. Mają styl 'następnego miesiąca', więc nie będziesz potrzebował refleksji, tylko ustaw dla nich' datePicker.getValue(). GetMonth() + 1'' –
Tak, mam na myśli wybór dni pokazanych allready. Na powyższym zdjęciu możesz wybrać na przykład 31. grudnia do 3. stycznia. Zdałem sobie sprawę, że "następny miesiąc" był dla uproszczenia. Zdaję sobie sprawę, że wielu ludzi sprzeciwia się użyciu refleksji w OO, ale w moim przekonaniu bardziej sensowne jest tu użycie istniejącej metody, niż jej obliczenie, jakkolwiek by nie było. – Skjalg