Select 使用 DatePicker 的多个日期
Select multiple dates with DatePicker
我正在尝试创建一个 select 多个日期的 DatePicker。我可以 select 多个日期,但我想在我 select 它们时保持 DatePicker 打开。问题是,每次我 select 一个日期时 DatePicker 都会关闭。
我不想使用私人API。我在考虑添加这个:
datePicker.setOnHiding(event -> {
event.consume();
});
但是没用。
这是我的代码:
public static DatePicker getDatePicker() {
ObservableList<LocalDate> selectedDates = FXCollections.observableArrayList();
String pattern = "yyyy-MM-dd";
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern);
DatePicker datePicker = new DatePicker();
datePicker.setPromptText(pattern);
datePicker.setConverter(new StringConverter<LocalDate>() {
@Override
public String toString(LocalDate date) {
return (date == null) ? "" : dateFormatter.format(date);
}
@Override
public LocalDate fromString(String string) {
return ((string == null) || string.isEmpty()) ? null : LocalDate.parse(string, dateFormatter);
}
});
datePicker.setOnAction(event -> {
selectedDates.add(datePicker.getValue());
event.consume();
});
datePicker.setDayCellFactory((DatePicker param) -> new DateCell() {
@Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
boolean alreadySelected = selectedDates.contains(item);
setDisable(alreadySelected);
setStyle(alreadySelected ? "-fx-background-color: #09a30f;" : "");
}
});
return datePicker;
}
如果检查DatePickerContent
class,可以发现每次创建一个新的DateCell
,都会添加一个MOUSE_CLICKED
类型的EventHandler
给它。当用户单击单元格时,此处理程序将调用 selectDayCell(DateCell)
。 selectDayCell(DateCell)
设置新的日期值并隐藏 DatePicker
:
protected void createDayCells() {
final EventHandler<MouseEvent> dayCellActionHandler = ev -> {
if (ev.getButton() != MouseButton.PRIMARY) {
return;
}
DateCell dayCell = (DateCell)ev.getSource();
selectDayCell(dayCell);
lastFocusedDayCell = dayCell;
};
for (int row = 0; row < 6; row++) {
for (int col = 0; col < daysPerWeek; col++) {
DateCell dayCell = createDayCell();
dayCell.addEventHandler(MouseEvent.MOUSE_CLICKED, dayCellActionHandler);
dayCells.add(dayCell);
}
}
dayCellDates = new LocalDate[6 * daysPerWeek];
}
public void selectDayCell(DateCell dateCell) {
datePicker.setValue(dayCellDate(dateCell));
datePicker.hide();
}
如果您使用的是 Java 9 或更新版本,您可以扩展 DatePickerContent
class 并覆盖 selectDayCell(DateCell)
方法以不隐藏 DatePicker
选择单元格后:
public void selectDayCell(DateCell dateCell) {
datePicker.setValue(dayCellDate(dateCell));
}
不幸的是,在 Java 8 中,DatePickerContent
有一个包私有的构造函数,所以你不能从它扩展。作为解决方法,您可以在单击鼠标时添加另一个 EventHandler
,这将在单击单元格后再次显示 DatePicker
:
EventHandler<MouseEvent> mouseClickedEventHandler = clickEvent -> {
if (clickEvent.getButton() == MouseButton.PRIMARY) {
datePicker.show();
}
clickEvent.consume();
};
在你的细胞工厂:
@Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
//...
if (item != null && !empty) {
//...
addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
} else {
//...
removeEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
}
}
使用 Chanandler Bong 小姐的回答,我能够为 JavaFX8 创建这个:
public class MultiDatePicker
{
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final ObservableSet<LocalDate> selectedDates;
private final DatePicker datePicker;
public MultiDatePicker()
{
this.selectedDates = FXCollections.observableSet(new TreeSet<>());
this.datePicker = new DatePicker();
setUpDatePicker();
}
public ObservableSet<LocalDate> getSelectedDates()
{
return this.selectedDates;
}
public DatePicker getDatePicker()
{
return this.datePicker;
}
private void setUpDatePicker()
{
this.datePicker.setConverter(new StringConverter<LocalDate>()
{
@Override
public String toString(LocalDate date)
{
return (date == null) ? "" : DATE_FORMAT.format(date);
}
@Override
public LocalDate fromString(String string)
{
return ((string == null) || string.isEmpty()) ? null : LocalDate.parse(string, DATE_FORMAT);
}
});
EventHandler<MouseEvent> mouseClickedEventHandler = (MouseEvent clickEvent) ->
{
if (clickEvent.getButton() == MouseButton.PRIMARY)
{
if (!this.selectedDates.contains(this.datePicker.getValue()))
{
this.selectedDates.add(datePicker.getValue());
} else
{
this.selectedDates.remove(this.datePicker.getValue());
this.datePicker.setValue(getClosestDateInTree(new TreeSet<>(this.selectedDates), this.datePicker.getValue()));
}
}
this.datePicker.show();
clickEvent.consume();
};
this.datePicker.setDayCellFactory((DatePicker param) -> new DateCell()
{
@Override
public void updateItem(LocalDate item, boolean empty)
{
super.updateItem(item, empty);
//...
if (item != null && !empty)
{
//...
addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
} else
{
//...
removeEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
}
if (selectedDates.contains(item))
{
setStyle("-fx-background-color: rgba(3, 169, 244, 0.7);");
} else
{
setStyle(null);
}
}
});
}
private static LocalDate getClosestDateInTree(TreeSet<LocalDate> dates, LocalDate date)
{
Long lower = null;
Long higher = null;
if (dates.isEmpty())
{
return null;
}
if (dates.size() == 1)
{
return dates.first();
}
if (dates.lower(date) != null)
{
lower = Math.abs(DAYS.between(date, dates.lower(date)));
}
if (dates.higher(date) != null)
{
higher = Math.abs(DAYS.between(date, dates.higher(date)));
}
if (lower == null)
{
return dates.higher(date);
} else if (higher == null)
{
return dates.lower(date);
} else if (lower <= higher)
{
return dates.lower(date);
} else if (lower > higher)
{
return dates.higher(date);
} else
{
return null;
}
}
}
我正在尝试创建一个 select 多个日期的 DatePicker。我可以 select 多个日期,但我想在我 select 它们时保持 DatePicker 打开。问题是,每次我 select 一个日期时 DatePicker 都会关闭。
我不想使用私人API。我在考虑添加这个:
datePicker.setOnHiding(event -> {
event.consume();
});
但是没用。
这是我的代码:
public static DatePicker getDatePicker() {
ObservableList<LocalDate> selectedDates = FXCollections.observableArrayList();
String pattern = "yyyy-MM-dd";
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern);
DatePicker datePicker = new DatePicker();
datePicker.setPromptText(pattern);
datePicker.setConverter(new StringConverter<LocalDate>() {
@Override
public String toString(LocalDate date) {
return (date == null) ? "" : dateFormatter.format(date);
}
@Override
public LocalDate fromString(String string) {
return ((string == null) || string.isEmpty()) ? null : LocalDate.parse(string, dateFormatter);
}
});
datePicker.setOnAction(event -> {
selectedDates.add(datePicker.getValue());
event.consume();
});
datePicker.setDayCellFactory((DatePicker param) -> new DateCell() {
@Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
boolean alreadySelected = selectedDates.contains(item);
setDisable(alreadySelected);
setStyle(alreadySelected ? "-fx-background-color: #09a30f;" : "");
}
});
return datePicker;
}
如果检查DatePickerContent
class,可以发现每次创建一个新的DateCell
,都会添加一个MOUSE_CLICKED
类型的EventHandler
给它。当用户单击单元格时,此处理程序将调用 selectDayCell(DateCell)
。 selectDayCell(DateCell)
设置新的日期值并隐藏 DatePicker
:
protected void createDayCells() {
final EventHandler<MouseEvent> dayCellActionHandler = ev -> {
if (ev.getButton() != MouseButton.PRIMARY) {
return;
}
DateCell dayCell = (DateCell)ev.getSource();
selectDayCell(dayCell);
lastFocusedDayCell = dayCell;
};
for (int row = 0; row < 6; row++) {
for (int col = 0; col < daysPerWeek; col++) {
DateCell dayCell = createDayCell();
dayCell.addEventHandler(MouseEvent.MOUSE_CLICKED, dayCellActionHandler);
dayCells.add(dayCell);
}
}
dayCellDates = new LocalDate[6 * daysPerWeek];
}
public void selectDayCell(DateCell dateCell) {
datePicker.setValue(dayCellDate(dateCell));
datePicker.hide();
}
如果您使用的是 Java 9 或更新版本,您可以扩展 DatePickerContent
class 并覆盖 selectDayCell(DateCell)
方法以不隐藏 DatePicker
选择单元格后:
public void selectDayCell(DateCell dateCell) {
datePicker.setValue(dayCellDate(dateCell));
}
不幸的是,在 Java 8 中,DatePickerContent
有一个包私有的构造函数,所以你不能从它扩展。作为解决方法,您可以在单击鼠标时添加另一个 EventHandler
,这将在单击单元格后再次显示 DatePicker
:
EventHandler<MouseEvent> mouseClickedEventHandler = clickEvent -> {
if (clickEvent.getButton() == MouseButton.PRIMARY) {
datePicker.show();
}
clickEvent.consume();
};
在你的细胞工厂:
@Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
//...
if (item != null && !empty) {
//...
addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
} else {
//...
removeEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
}
}
使用 Chanandler Bong 小姐的回答,我能够为 JavaFX8 创建这个:
public class MultiDatePicker
{
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final ObservableSet<LocalDate> selectedDates;
private final DatePicker datePicker;
public MultiDatePicker()
{
this.selectedDates = FXCollections.observableSet(new TreeSet<>());
this.datePicker = new DatePicker();
setUpDatePicker();
}
public ObservableSet<LocalDate> getSelectedDates()
{
return this.selectedDates;
}
public DatePicker getDatePicker()
{
return this.datePicker;
}
private void setUpDatePicker()
{
this.datePicker.setConverter(new StringConverter<LocalDate>()
{
@Override
public String toString(LocalDate date)
{
return (date == null) ? "" : DATE_FORMAT.format(date);
}
@Override
public LocalDate fromString(String string)
{
return ((string == null) || string.isEmpty()) ? null : LocalDate.parse(string, DATE_FORMAT);
}
});
EventHandler<MouseEvent> mouseClickedEventHandler = (MouseEvent clickEvent) ->
{
if (clickEvent.getButton() == MouseButton.PRIMARY)
{
if (!this.selectedDates.contains(this.datePicker.getValue()))
{
this.selectedDates.add(datePicker.getValue());
} else
{
this.selectedDates.remove(this.datePicker.getValue());
this.datePicker.setValue(getClosestDateInTree(new TreeSet<>(this.selectedDates), this.datePicker.getValue()));
}
}
this.datePicker.show();
clickEvent.consume();
};
this.datePicker.setDayCellFactory((DatePicker param) -> new DateCell()
{
@Override
public void updateItem(LocalDate item, boolean empty)
{
super.updateItem(item, empty);
//...
if (item != null && !empty)
{
//...
addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
} else
{
//...
removeEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedEventHandler);
}
if (selectedDates.contains(item))
{
setStyle("-fx-background-color: rgba(3, 169, 244, 0.7);");
} else
{
setStyle(null);
}
}
});
}
private static LocalDate getClosestDateInTree(TreeSet<LocalDate> dates, LocalDate date)
{
Long lower = null;
Long higher = null;
if (dates.isEmpty())
{
return null;
}
if (dates.size() == 1)
{
return dates.first();
}
if (dates.lower(date) != null)
{
lower = Math.abs(DAYS.between(date, dates.lower(date)));
}
if (dates.higher(date) != null)
{
higher = Math.abs(DAYS.between(date, dates.higher(date)));
}
if (lower == null)
{
return dates.higher(date);
} else if (higher == null)
{
return dates.lower(date);
} else if (lower <= higher)
{
return dates.lower(date);
} else if (lower > higher)
{
return dates.higher(date);
} else
{
return null;
}
}
}