如何在日期选择器中为不在当前月份的单元格设置样式

How do I style a cell in the date picker that is not in the current month

我目前正在为我的 JavaFX 应用程序制作注册表单。问题是,当单元格不在页面的月份上时,我想让日期选择器中的单元格变灰。让我们看看我当前的日期选择器。

我的日期选择器:

如您所见,我希望下个月的 27 号、28 号、...、30 号和 1 号、2 号、3 号...显示为灰色。我该怎么做?

我已经研究了实现它的所有可能方法,我认为它可能在“dayCellFactory”上,我不知道...

final Callback<DatePicker, DateCell> dayCellFactory = new Callback<>() {
    public DateCell call(DatePicker datePicker) {
        return new DateCell() {
            @Override
            public void updateItem(LocalDate item, boolean empty) {
                super.updateItem(item, empty);
                this.getStyleClass().add("form-date-picker-cell");
            }
        };
    }
};
datePicker.setDayCellFactory(dayCellFactory);

我的方法是从日期选择器中查询当前月份并检查该单元格是否在该月,但我不知道如何从日期选择器中获取月份。

你有一个 X/Y problem.

你真正想做的是:

make cells in the date picker grey out when that cell isn't on the page's month

你问的是:

How to get month from DatePicker popup in JavaFX

您无法通过 public API 从日期选择器中获取当前显示的月份,因为它没有公开。不过,这应该无关紧要。

您不需要从日期选择器中获取月份来完成您真正想做的事情。

日期选择器的默认 实现会将不在当月的日期显示为灰色。

但是,从您的屏幕截图来看,不在当月的日子在您的实施中没有变灰。所以你的实现已经做了一些打破默认的事情。

问题出在您未提供的代码中。可能是 CSS.

例子

可以看到不在这个月的单元格背景是灰色的

你要仔细看,这是一个非常微妙的效果。 . .

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.DatePicker;
import javafx.stage.Stage;

import java.time.LocalDate;

public class DatePickApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        DatePicker picker = new DatePicker(LocalDate.now());
        picker.setPadding(new Insets(10));

        stage.setScene(new Scene(picker));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

那么,这些单元格的默认变灰是如何发生的?

正如 kleopatra 在评论中指出的那样,

those off cells already have styles next-month/previous-month ... setting rules for the next/previous month indeed does work - but: unfortunately they are undocumented

您可以在 modena.css 中找到默认样式。使用或替换未记录的样式存在一些风险(升级 JavaFX 库时样式可能无法继续工作)。但是,如果您想要自定义任何 non-trivial 大小的 JavaFX 应用程序外观,那么无论如何您都需要使用自定义未记录的样式。

一种合理的方法是限制未记录样式的使用,并使用您测试过的 JavaFX 版本打包您的应用程序,但仍然在必要时使用未记录的属性执行样式以获得您需要的外观。

假设默认的灰色有点太微妙,你想改变它,你可以查看 modena.css 看看样式是如何完成的,并在你的自定义样式中覆盖它sheet.

默认样式通过以下方式出现:

.date-picker-popup > * > .previous-month,
.date-picker-popup > * > .next-month {
    -fx-background: derive(-fx-control-inner-background, -4%);
}

因此 grayed-out 单元格背景的亮度仅降低 4%。

让我们用不同的颜色覆盖它,mistyrose

public class DatePickApp extends Application {

    public static final String CSS = "data:text/css," + // language=CSS
            """
            .date-picker-popup > * > .previous-month,
            .date-picker-popup > * > .next-month {
                -fx-background: mistyrose;
            }
            """;

    @Override
    public void start(Stage stage) throws Exception {
        DatePicker picker = new DatePicker(LocalDate.now());
        picker.setPadding(new Insets(10));

        Scene scene = new Scene(picker);
        scene.getStylesheets().add(CSS);

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

现在更清楚哪些天不在当月。

但是我们引入了一个问题。现在,如果您将鼠标悬停在这些单元格上,它们将不会像以前那样突出显示。

这是因为更复杂的控件(例如 TableView 和 DatePicker)的 CSS 规则复杂且容易被破坏。

因此,一旦开始为控件的某些状态定义自定义规则,就必须为具有类似样式的控件的其他状态定义或重新定义规则类。例如,要解决突出显示问题,请为这些单元格重新声明悬停 psuedo-state 规则以将其重新映射为默认值。

哦,顺便说一句,如果您选择上个月的日期和更改月份,则有不同的样式。所以你也需要重新映射它们。一旦开始这样做,它会变得非常复杂且容易出错。

要完成所有这些,您需要真正研究一般的 JavaFX CSS reference guide、CSS 标准,尤其是 JavaFX 发行版中的 modena.css 样式 sheet (在 IDE 中搜索它)。从默认 modena.css 样式 sheet 复制粘贴和修改以自定义它。使用从 modena 查找的颜色名称,知道如何覆盖它们以及如何使用颜色派生函数。

所以这是一个更复杂的颜色重映射(它仍然可能在某些状态下有问题,我没有彻底测试它)。

在示例中,我将鼠标悬停在 7 月 7 日上,您可以看到,它是一个较深的灰色,表示它不可选择,因为它不在当前的 6 月。

public class DatePickApp extends Application {

    public static final String CSS = "data:text/css," + // language=CSS
            """
            .date-picker-popup > * > .previous-month,
            .date-picker-popup > * > .next-month {
                -fx-background: mistyrose;
            }
            .date-picker-popup > * > .previous-month.selected,
            .date-picker-popup > * > .next-month.selected {
                -fx-background: -fx-selection-bar;
            }
            .date-picker-popup > * > .previous-month:hover,
            .date-picker-popup > * > .next-month:hover {
                -fx-background: -fx-selection-bar-non-focused;
            }
            .date-picker-popup > * > .previous-month.today,
            .date-picker-popup > * > .next-month.today {
                -fx-background-color: mistyrose, derive(-fx-selection-bar-non-focused, -20%25), mistyrose;
            }
            .date-picker-popup > * > .previous-month.today:hover,
            .date-picker-popup > * > .next-month.today:hover {
                -fx-background-color: -fx-selection-bar-non-focused, derive(-fx-selection-bar-non-focused, -20%25), -fx-selection-bar-non-focused;
            }
            """;

    @Override
    public void start(Stage stage) throws Exception {
        DatePicker picker = new DatePicker(LocalDate.now());
        picker.setPadding(new Insets(10));

        Scene scene = new Scene(picker);
        scene.getStylesheets().add(CSS);

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

诡异的-20%25是因为我使用的是一个数据URL所以我需要对数据URL中的%符号进行编码。如果你使用标准的 CSS 文件,你可以只写 -20%.

I wanted to change it to light blue to match my application scheme.

您可以通过为整个应用程序或个别控件或场景图的部分设置 -fx-base 来实现。

这是一个例子。底色是lightskyblue.

这里所做的是完全删除自定义样式 sheet 并设置用于派生许多事物颜色的基色。

因此,这是一种为主题一致地为 UI 着色而无需覆盖太多详细样式规则的简单方法。您可以为此覆盖一些关键的查找颜色。您可以在 modena.css.

中找到它们的定义

以这种方式应用主题的示例是:

请注意,仅覆盖查找到的颜色可能无法获得您想要的效果。 IMO,灰色 non-month 颜色的默认 -4% 派生(现在略带深蓝色,因为它们也来自基础),太微妙了,你可能想覆盖它反正。所以结合方法覆盖查找颜色的 es,并在必要时覆盖默认的 modena 样式是最好的。

public class DatePickApp extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        DatePicker picker = new DatePicker(LocalDate.now());
        picker.setPadding(new Insets(10));

        picker.setStyle("-fx-base: lightskyblue");

        Scene scene = new Scene(picker);

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}