文档



JavaFX:使用JavaFX UI组件

26 日期选择器

本章概述了JavaFX中支持的日期数据,并描述了DatePicker控件的基本特性。

JavaFX DatePicker是一个控件,可以从给定的日历中选择一个日期。它主要用于网站或需要用户输入日期的应用程序。 DatePicker控件由一个带有日期字段和日期选择器的组合框组成。

处理时间数据和日期格式

JDK 8中引入的新的日期时间API使您能够执行各种与日期和时间数据相关的操作,包括在不同时区设置日历和本地时间。

日期时间API的基本包是java.time。它提供了以下基于国际标准化组织(ISO)日历的日历系统中定义时间的类。

  • Clock - 提供访问当前时刻、日期和时间的时钟,使用时区

  • Duration - 一段基于时间的时间量

  • Instant - 时间线上的一个瞬时点

  • LocalDate - ISO-8601日历系统中没有时区的日期

  • LocalDateTime - ISO-8601日历系统中没有时区的日期时间

  • LocalTime - ISO-8601日历系统中没有时区的时间

  • MonthDay - ISO-8601日历系统中的月份日期

  • OffsetDateTime - ISO-8601日历系统中与格林威治/UTC时间偏移的日期时间

  • OffsetTime - ISO-8601日历系统中与格林威治/UTC时间偏移的时间

  • Period - 基于日期的时间量

  • Year - ISO-8601日历系统中的年份

  • YearMonth - ISO-8601日历系统中的年份和月份

  • ZonedDateTime - ISO-8601日历系统中带有时区的日期时间

  • ZoneId - 时区ID

  • ZoneOffset - 与格林威治/UTC时间的时区偏移

有关可用功能和代码示例的更多信息,请参见Java教程的日期时间API教程

日期选择器设计概述

为了增强JavaFX应用程序的用户界面,利用JDK 8日期时间API的新功能,JavaFX SDK引入了DatePicker控件。 DatePicker控件由一个可编辑的组合框(日期字段)和一个日历(日期选择器)组成,如图26-1所示。

图26-1 日期选择器控件示例

图26-1的描述
"图26-1 日期选择器控件示例"的描述

向应用程序UI添加日期选择器

使用javafx.scene.control包中提供的DatePicker类,将日期选择器组件添加到JavaFX应用程序的用户界面(UI)中,如示例26-1所示。

示例26-1 添加日期选择器组件

import java.util.Locale;
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.scene.Scene;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
 
public class DatePickerSample extends Application {
 
    private Stage stage;
    private DatePicker checkInDatePicker;
    public static void main(String[] args) {
        Locale.setDefault(Locale.US);
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        this.stage = stage;
        stage.setTitle("DatePickerSample ");
        initUI();
        stage.show();
    }
 
    private void initUI() {
        VBox vbox = new VBox(20);
        vbox.setStyle("-fx-padding: 10;");
        Scene scene = new Scene(vbox, 400, 400);
        stage.setScene(scene);

        checkInDatePicker = new DatePicker();

        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);

        Label checkInlabel = new Label("入住日期:");
        gridPane.add(checkInlabel, 0, 0);

        GridPane.setHalignment(checkInlabel, HPos.LEFT);
        gridPane.add(checkInDatePicker, 0, 1);
        vbox.getChildren().add(gridPane);
    }
}

示例26-1实现了一个典型的用于选择酒店预订入住日期的UI。当您编译和运行应用程序时,将显示图26-2中显示的组件。

图26-2 日期选择器组件的初始视图

图26-2的描述
"图26-2 日期选择器组件的初始视图"的描述

请注意,在初始状态下,日期字段为空。但是,您可以更改此行为,并在选择任何日期之前指定要显示的日期值。在DatePicker构造函数中设置value属性,或调用从ComboBox类继承的setValue方法。 示例26-2显示了设置LocalDate值的几个选项。

示例 26-2 设置日期值

//在类构造函数中设置特定的日期值
checkInDatePicker = new DatePicker(LocalDate.of(1998, 10, 8));
//使用setValue方法设置特定的日期值
checkInDatePicker.setValue(LocalDate.of(1998, 10, 8));
//设置日历中可用的最小日期
checkInDatePicker.setValue(LocalDate.MIN);
//设置日历中可用的最大日期
checkInDatePicker.setValue(LocalDate.MAX);
//设置当前日期
checkInDatePicker.setValue(LocalDate.now());

一旦初始值设置完成,在编译和运行应用程序后,它将显示在日期字段中。 图 26-3 显示了一个指定的初始日期在日期字段中。

图 26-3 带有指定初始日期的日期字段

图 26-3 的描述如下
"图 26-3 带有指定初始日期" 的描述

DatePicker API 提供了多个属性和方法来修改日期选择器的默认外观和行为。特别是,您可以切换显示周数,自定义日期格式,并定义和应用日期单元格工厂。

自定义日期选择器

您可以使用DatePicker类的setShowWeekNumbers方法来启用或禁用在日历中显示ISO周数。 示例 26-3是一个实现此任务的代码行,用于checkInDatePicker。

示例 26-3 启用日期选择器中的周数

checkInDatePicker.setShowWeekNumbers(true);

此修改会在日期选择器组件中添加周数,如图 26-4所示。

图 26-4 在日历中显示周数

图 26-4 的描述
"图 26-4 在日历中显示周数" 的描述

默认情况下,日期字段中的日期以系统区域设置和ISO日历系统定义的格式显示。因此,在示例 26-1中选择的日期以以下格式显示:mm/dd/yyyy。通常情况下,您不需要修改默认格式。但是,DatePicker类的converter属性和setConverter方法使您能够在需要时设置替代日期格式。将converter值设置为null可恢复默认日期格式。

示例 26-4中,您创建了一个转换器,根据以下模式更改日期格式:yyyy-MM-dd。当用户在日期字段中输入日期时,它将输入文本转换为LocalDate类型的对象。它还将与日历中选择的日期对应的LocalDate对象转换为日期字段中显示的文本。

示例 26-4 转换日期格式

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.scene.Scene;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
 
public class DatePickerSample extends Application {
 
    private Stage stage;
    private DatePicker checkInDatePicker;
    private final String pattern = "yyyy-MM-dd";
 
    public static void main(String[] args) {
        Locale.setDefault(Locale.US);
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        this.stage = stage;
        stage.setTitle("DatePickerSample ");
        initUI();
        stage.show();
    }
 
    private void initUI() {
        VBox vbox = new VBox(20);
        vbox.setStyle("-fx-padding: 10;");
        Scene scene = new Scene(vbox, 400, 400);
        stage.setScene(scene);
        checkInDatePicker = new DatePicker();
        StringConverter converter = new StringConverter<LocalDate>() {
            DateTimeFormatter dateFormatter = 
                DateTimeFormatter.ofPattern(pattern);
            @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;
                }
            }
        };             
        checkInDatePicker.setConverter(converter);
        checkInDatePicker.setPromptText(pattern.toLowerCase());
        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        Label checkInlabel = new Label("入住日期:");
        gridPane.add(checkInlabel, 0, 0);
        GridPane.setHalignment(checkInlabel, HPos.LEFT);
        gridPane.add(checkInDatePicker, 0, 1);
        vbox.getChildren().add(gridPane);
        checkInDatePicker.requestFocus();
    }
}

除了转换日期格式外,示例26-4还将日期字段的提示文本设置为通知用户所需的日期格式。 图26-5显示了应用日期格式转换器的日期选择器的两种状态。新格式中同时显示了提示文本和所选日期。

图26-5 带有格式转换器和提示文本的日期选择器

图26-5的描述如下
"图26-5 带有格式转换器和提示文本"的描述

您还可以修改默认外观并为日历元素的任何单元格或多个单元格设置特定行为。为了实现这个任务,考虑一个真实的用例:在酒店预订期间选择入住和退房日期。 示例26-5创建了一个带有两个日期选择器的典型用户界面。

示例26-5 添加两个日期选择器组件

import java.time.LocalDate;
import java.util.Locale;
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.scene.Scene;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
 
public class DatePickerSample extends Application {
 
    private Stage stage;
    private DatePicker checkInDatePicker;
    private DatePicker checkOutDatePicker;
 
    public static void main(String[] args) {
        Locale.setDefault(Locale.US);                  
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        this.stage = stage;
        stage.setTitle("DatePickerSample ");
        initUI();
        stage.show();
    }
 
    private void initUI() {
        VBox vbox = new VBox(20);
        vbox.setStyle("-fx-padding: 10;");
        Scene scene = new Scene(vbox, 400, 400);
        stage.setScene(scene);
        checkInDatePicker = new DatePicker();
        checkOutDatePicker = new DatePicker();
        checkInDatePicker.setValue(LocalDate.now());
        checkOutDatePicker.setValue(checkInDatePicker.getValue().plusDays(1));
        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        Label checkInlabel = new Label("入住日期:");
        gridPane.add(checkInlabel, 0, 0);
        GridPane.setHalignment(checkInlabel, HPos.LEFT);
        gridPane.add(checkInDatePicker, 0, 1);
        Label checkOutlabel = new Label("退房日期:");
        gridPane.add(checkOutlabel, 0, 2);
        GridPane.setHalignment(checkOutlabel, HPos.LEFT);
        gridPane.add(checkOutDatePicker, 0, 3);
        vbox.getChildren().add(gridPane);
    }
}

示例26-5中,checkInDatePicker的预定义值是LocalDate.now(),它对应于系统时钟的当前日期。checkOutDatePicker的预定义日期是当前日期之后的下一个日期。

当您在2013年9月19日编译和运行此示例时,它会产生图26-6中显示的输出。

图26-6 两个日期选择器组件

图26-6的描述如下
"图26-6 两个日期选择器组件"的描述

默认情况下,日历元素中的所有单元格都可以选择。这可能导致退房日期早于入住日期,这是不正确的。

示例26-6为checkOutDatePicker创建了一个日期单元格工厂,以禁用与checkInDatePicker中选择的日期对应的单元格以及所有之前的日期对应的单元格。

示例 26-6 实现一个日期单元格工厂以禁用某些日期

import java.time.LocalDate;
import java.util.Locale;
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.scene.Scene;
import javafx.scene.control.DateCell;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
 
public class DatePickerSample extends Application {
 
    private Stage stage;
    private DatePicker checkInDatePicker;
    private DatePicker checkOutDatePicker;
 
    public static void main(String[] args) {
        Locale.setDefault(Locale.US);                  
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        this.stage = stage;
        stage.setTitle("DatePickerSample ");
        initUI();
        stage.show();
    }
 
    private void initUI() {
        VBox vbox = new VBox(20);
        vbox.setStyle("-fx-padding: 10;");
        Scene scene = new Scene(vbox, 400, 400);
        stage.setScene(scene);
        checkInDatePicker = new DatePicker();
        checkOutDatePicker = new DatePicker();
        checkInDatePicker.setValue(LocalDate.now());
        final Callback<DatePicker, DateCell> dayCellFactory = 
            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);
                           
                            if (item.isBefore(
                                    checkInDatePicker.getValue().plusDays(1))
                                ) {
                                    setDisable(true);
                                    setStyle("-fx-background-color: #ffc0cb;");
                            }   
                    }
                };
            }
        };
        checkOutDatePicker.setDayCellFactory(dayCellFactory);
        checkOutDatePicker.setValue(checkInDatePicker.getValue().plusDays(1));
        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        Label checkInlabel = new Label("入住日期:");
        gridPane.add(checkInlabel, 0, 0);
        GridPane.setHalignment(checkInlabel, HPos.LEFT);
        gridPane.add(checkInDatePicker, 0, 1);
        Label checkOutlabel = new Label("退房日期:");
        gridPane.add(checkOutlabel, 0, 2);
        GridPane.setHalignment(checkOutlabel, HPos.LEFT);
        gridPane.add(checkOutDatePicker, 0, 3);
        vbox.getChildren().add(gridPane);
    }
}

dayCellFactory应用于checkOutDatePicker上,调用DateCell上的setDisablesetStyle方法,以保护单元格不被选择,并用粉色进行绘制。

当您在示例26-6中编译和运行代码时,DatePickerSample会产生一个具有预期行为的UI,如图26-7所示。

图26-7 禁用日历元素中的单元格

图26-7的描述
"图26-7 禁用日历元素中的单元格"的描述

现在您已经学会了如何创建day cell factories,您可以扩展checkOutDatePicker的默认行为,并为启用选择的日期单元格提供额外的功能。

示例26-7计算了checkInDatePicker中选择的日期与当前日期单元格之间的天数间隔。结果在单元格的工具提示中呈现。

示例 26-7 计算时间间隔

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Locale;
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.scene.Scene;
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.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
 
public class DatePickerSample extends Application {
 
    private Stage stage;
    private DatePicker checkInDatePicker;
    private DatePicker checkOutDatePicker;
 
    public static void main(String[] args) {
        Locale.setDefault(Locale.US);                  
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        this.stage = stage;
        stage.setTitle("DatePickerSample ");
        initUI();
        stage.show();
    }
 
    private void initUI() {
        VBox vbox = new VBox(20);
        vbox.setStyle("-fx-padding: 10;");
        Scene scene = new Scene(vbox, 400, 400);
        stage.setScene(scene);
        checkInDatePicker = new DatePicker();
        checkOutDatePicker = new DatePicker();
        checkInDatePicker.setValue(LocalDate.now());
        final Callback<DatePicker, DateCell> dayCellFactory = 
            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);
                            if (item.isBefore(
                                    checkInDatePicker.getValue().plusDays(1))
                                ) {
                                    setDisable(true);
                                    setStyle("-fx-background-color: #ffc0cb;");
                            }
                            long p = ChronoUnit.DAYS.between(
                                    checkInDatePicker.getValue(), item
                            );
                            setTooltip(new Tooltip(
                                "您将要停留 " + p + " 天")
                            );
                    }
                };
            }
        };
        checkOutDatePicker.setDayCellFactory(dayCellFactory);
        checkOutDatePicker.setValue(checkInDatePicker.getValue().plusDays(1));
        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        Label checkInlabel = new Label("入住日期:");
        gridPane.add(checkInlabel, 0, 0);
        GridPane.setHalignment(checkInlabel, HPos.LEFT);
        gridPane.add(checkInDatePicker, 0, 1);
        Label checkOutlabel = new Label("退房日期:");
        gridPane.add(checkOutlabel, 0, 2);
        GridPane.setHalignment(checkOutlabel, HPos.LEFT);
        gridPane.add(checkOutDatePicker, 0, 3);
        vbox.getChildren().add(gridPane);
    }
}

当您编译和运行修改后的DatePickerSample应用程序时,如果将鼠标光标悬停在日期单元格上,您可以看到工具提示。 图26-8 捕捉了将鼠标光标放在9月30日单元格上的时刻。工具提示显示9月19日到9月30日之间的时间间隔为11天。

图26-8 设置日期单元格的工具提示

图26-8的描述如下
"图26-8 设置日期单元格的工具提示"的描述

修改日历系统

JDK 8中引入的Date-Time Java API使开发人员不仅可以使用基于ISO的日历系统,还可以使用其他日历系统,如日本、伊斯兰、民国和泰国佛教。

DatePicker控件还通过渲染适当的年份来支持非ISO日历系统。对于伊斯兰历,其中月份日期与ISO日期不一致,它提供了一个额外的视觉主题来区分ISO和伊斯兰月份的日期。

示例26-8将泰国佛教年表应用于checkInDatePicker,将伊斯兰年表应用于checkOutDatePicker。

示例26-8 应用泰国佛教和伊斯兰年表

import java.time.chrono.*;

checkInDatePicker.setChronology(ThaiBuddhistChronology.INSTANCE);
checkOutDatePicker.setChronology(HijrahChronology.INSTANCE);

当这些代码添加到DatePickerSample应用程序中时,日期选择器控件的外观会发生变化,如图26-9所示。

图26-9 使用替代日历

图26-9的描述
"图26-9 使用替代日历"的描述

您可以在Java教程的相应课程中了解更多关于非ISO日期支持的详细信息。

相关文档 

关闭窗口

目录

JavaFX:使用JavaFX UI组件

展开 折叠