Issue
I'm currently working on a registration form for my JavaFX application. The thing is, I want to make cells in the date picker grey out when that cell isn't on the page's month. Let's look at my current date picker.
My date picker:
href="https://i.stack.imgur.com/Q4NXs.png" rel="nofollow noreferrer">
As you can see I want the dates 27th, 28th, ..., 30th and 1st, 2nd, 3rd, ... on the next month to be grey out. How can I do it?
I've looked at all of the possible ways to make it happen, I think it might be on "dayCellFactory" I don't know...
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);
My approach was to query a current month from the date picker and check if that cell is on that month, but I don't know how to get a month from the date picker.
Solution
You have an X/Y problem.
What you actually want to do is:
make cells in the date picker grey out when that cell isn't on the page's month
What you ask is:
How to get month from DatePicker popup in JavaFX
You can't get the currently displayed month from the date picker via public API, as it is not exposed. However, that should not matter.
You don't need to get the month from the date picker to accomplish what you actually want to do.
The default implementation of the date picker will gray out the days which are not in the current month.
However, from your screenshot, the days not in the current month are not grayed out for your implementation. So your implementation has done something to break the default.
The issue is in the code you don't supply. Probably the CSS.
Example
You can see that the cells not in this month have gray backgrounds.
You have to look closely, it is a pretty subtle effect . . .
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);
}
}
So, how does the default graying of these cells occur?
As noted by kleopatra in comments,
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
You can find default styles in modena.css
. There is some risk in using or replacing styles that are not documented (the styling may not continue to work when the JavaFX library is upgraded). However, if you want a custom look for your JavaFX app of any non-trivial size, then you will need to make use of customizing undocumented styles anyway.
A reasonable approach is to limit the use of undocumented styles and package your app with a JavaFX version you tested, but still, when necessary, perform styling using the undocumented properties to get the look you need.
Let's say for instance the default gray is a bit too subtle and you want to change it, you can look in modena.css to see how the styling is done and override it in your custom stylesheet.
Default styling occurs via:
.date-picker-popup > * > .previous-month,
.date-picker-popup > * > .next-month {
-fx-background: derive(-fx-control-inner-background, -4%);
}
So just a 4% decrease in brightness for the grayed-out cell backgrounds.
Let's override this with a different color, 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);
}
}
Now it is much clearer which days are not in the current month.
But we have introduced a problem. Now if you hover over those cells, they don't highlight like they used to.
This is because the CSS rules for the more complex controls such as TableView and DatePicker are complex and easy to break.
So, once you start defining custom rules for some states of the controls, you have to define or redefine the rules for other states of the controls with similar style classes. For example, to fix the highlighting issue, redeclare the hover psuedo-state rule for those cells to remap it to the default.
Oh, and by the way, there are different styles for if you selected the day in the previous month than changed the month. So you need to remap those as well. It can get really complex and easy to make errors once you start doing this.
To accomplish all of this you need to really study the JavaFX CSS reference guide, CSS standards in general and, especially the modena.css
stylesheet inside your JavaFX distribution (search for it in your IDE). Copy and paste and modify from the default modena.css stylesheet to customize it. Make use of the looked up color names from modena, know how to override them and how to use the color derivation functions.
So here is a more sophisticated color remap (it still may have issues with certain states, I didn't thoroughly test it).
In the example, I have hovered over July 7th and you can see, it is a darker grey to indicate it is not selectable because it is not in the current month of June.
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);
}
}
The weird -20%25
is because I am using a data URL so I need to encode the %
symbol in the data URL. If you use a standard CSS file, you can just write -20%
.
I wanted to change it to light blue to match my application scheme.
You can do this by setting -fx-base
for the entire application or individual controls or portions of the scene graph.
Here is an example. The base color is lightskyblue
.
What is done here is to remove the custom style sheet altogether and set the base color which is used to derive the colors of many things.
So this is an easy way to consistently color your UI for a theme without overriding too much of the detailed styling rules. There are a few key looked up colors you can override for this. You can find them defined in modena.css
.
An example applying themes this way is:
Note that just overriding looked up colors probably won't get you exactly what you want. IMO, the default -4%
derivation for the grayed out non-month colors (which are now tinged a slightly darker shade of blue because they also derive from the base), is too subtle and you probably want to override that anyway. So a combination of approaches of overriding looked up colors and, where necessary, overriding the default modena styles can be best.
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);
}
}
Answered By - jewelsea
Answer Checked By - David Goodson (JavaFixing Volunteer)