I am attempting to create a Pane object with JavaFX that has three different colors: A color for the background, a color for the text, and a color for the buttons. Each of these
I agree with the approach of what @Sedrick has mentioned in the comments.
If you want to change only colors without modifying the rest of CSS, you can follow the below approach as well. This can be quite useful if you have a very large css file that needs to be themed.
The basic idea is to have all your css is one base css file. Define all your colors as variables in .root class in that base file. And for each of your theme css, you just need to override the color variables only. And load the theme css file on top of base file. This way you will not encounter any possible copy-paste issues or missing css issues :)
A complete working example is below:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.stream.Stream;
public class DynamicStyling_Demo extends Application {
@Override
public void start(Stage stage) throws Exception {
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.setSpacing(10);
Stream.of("Default", "Type1", "Type2", "Type3").forEach(type -> {
Button button = new Button("Open " + type);
button.setOnAction(e -> {
Stage subStage = buildStage(type);
subStage.initOwner(stage);
if (!type.equalsIgnoreCase("default")) {
subStage.getScene().getStylesheets().add(this.getClass().getResource(type.toLowerCase() + ".css").toExternalForm());
}
subStage.show();
});
root.getChildren().add(button);
});
Scene sc = new Scene(root, 400, 400);
sc.getStylesheets().add(this.getClass().getResource("base.css").toExternalForm());
stage.setScene(sc);
stage.show();
}
private Stage buildStage(String title) {
Label label = new Label("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
label.setWrapText(true);
VBox.setVgrow(label, Priority.ALWAYS);
Button btn = new Button("Sample Button");
VBox pane = new VBox(label, btn);
pane.getStyleClass().add("my-pane");
StackPane subRoot = new StackPane(pane);
subRoot.setPadding(new Insets(10));
Stage subStage = new Stage();
subStage.setTitle(title);
subStage.setScene(new Scene(subRoot, 300, 300));
subStage.getScene().getStylesheets().add(this.getClass().getResource("base.css").toExternalForm());
return subStage;
}
public static void main(String[] args) {
Application.launch(args);
}
}
base.css:
.root{
-fx-window-border: #444444;
-fx-window-color: #999999;
-fx-window-text: #111111;
-fx-button-color: #555555;
}
.my-pane{
-fx-border-width: 2px;
-fx-border-color: -fx-window-border;
-fx-background-color: -fx-window-color;
-fx-padding: 10px;
-fx-spacing: 10px;
}
.my-pane .label{
-fx-text-fill: -fx-window-text;
-fx-font-size: 16px;
}
.my-pane .button{
-fx-base: -fx-button-color;
}
type1.css:
.root{
-fx-window-border: red;
-fx-window-color: yellow;
-fx-window-text: brown;
-fx-button-color: pink;
}
type2.css:
.root{
-fx-window-border: green;
-fx-window-color: lightblue;
-fx-window-text: white;
-fx-button-color: grey;
}
type3.css:
.root{
-fx-window-border: brown;
-fx-window-color: lightgreen;
-fx-window-text: blue;
-fx-button-color: yellow;
}
Here are a couple of concrete example of setting theme colors for given components dynamically using:
Sample using looked-up colors
What the sample is doing is setting some of the standard looked up colors which were found in modena.css
to style the three things you want to style:
-fx-background-color
). This is the standard background used in classes which derive from the class Pane
.-fx-text-background-color
). Yeah, confusingly named I know, but that seems to be what it is, for whatever reason.-fx-base
). In the sample application, the user can pick colors dynamically using JavaFX ColorPicker
controls to modify the colors of items displayed in the preview pane.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ThemeMaker extends Application {
@Override
public void start(Stage stage) throws Exception {
Pane previewPane = createPreviewPane();
Pane controlPane = createControlPane(previewPane);
Pane layout = new VBox(
20,
controlPane,
previewPane
);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
private Pane createControlPane(Pane previewPane) {
ColorPicker backgroundColorPicker = new ColorPicker(Color.web("#b3ccff"));
ColorPicker textColorPicker = new ColorPicker(Color.web("#4d804d"));
ColorPicker controlColorPicker = new ColorPicker(Color.web("#ffe6cc"));
GridPane controlPane = new GridPane();
controlPane.setHgap(5);
controlPane.setVgap(5);
controlPane.addRow(0, new Label("Background color:"), backgroundColorPicker);
controlPane.addRow(1, new Label("Text color:"), textColorPicker);
controlPane.addRow(2, new Label("Control color:"), controlColorPicker);
backgroundColorPicker.valueProperty().addListener((observable, oldColor, newColor) ->
setThemeColors(previewPane, backgroundColorPicker.getValue(), textColorPicker.getValue(), controlColorPicker.getValue())
);
textColorPicker.valueProperty().addListener((observable, oldColor, newColor) ->
setThemeColors(previewPane, backgroundColorPicker.getValue(), textColorPicker.getValue(), controlColorPicker.getValue())
);
controlColorPicker.valueProperty().addListener((observable, oldColor, newColor) ->
setThemeColors(previewPane, backgroundColorPicker.getValue(), textColorPicker.getValue(), controlColorPicker.getValue())
);
setThemeColors(previewPane, backgroundColorPicker.getValue(), textColorPicker.getValue(), controlColorPicker.getValue());
return controlPane;
}
private void setThemeColors(Pane previewPane, Color backgroundColor, Color textColor, Color controlColor) {
previewPane.setStyle(
"-fx-background-color: " + toHexString(backgroundColor) + ";" +
"-fx-text-background-color: " + toHexString(textColor) + ";" +
"-fx-base: " + toHexString(controlColor) + ";"
);
}
private Pane createPreviewPane() {
Label label = new Label(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, " +
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
label.setWrapText(true);
Button btn = new Button("Sample Button");
Pane previewPane = new VBox(10, label, btn);
previewPane.setPadding(new Insets(5));
previewPane.setPrefWidth(200);
return previewPane;
}
// from https://stackoverflow.com/a/56733608/1155209 "How to get hex web String from JavaFX ColorPicker color?"
private String toHexString(Color value) {
return "#" + (format(value.getRed()) + format(value.getGreen()) + format(value.getBlue()) + format(value.getOpacity()))
.toUpperCase();
}
private String format(double val) {
String in = Integer.toHexString((int) Math.round(val * 255));
return in.length() == 1 ? "0" + in : in;
}
public static void main(String[] args) {
launch(args);
}
}
Sample using dynamic stylesheets
So the lookup color solution is very powerful because you can dynamically style the color of all items in a scene. However, CSS in general is much more powerful than just color setting. If your dynamic styling requires more than just color setting or you want very specific rules for how to style a particular item in the scene, then you will need your own custom style sheet.
The stylesheets
property of nodes and scenes is a dynamic observable list. So if you change the stylesheets, you will restyle the node or scene. Each stylesheet is referred to by a URL. So to dynamically create a style sheet, all you need to do is construct the stylesheet contents in code and write it out to a temporary file, grab a URL reference to the temporary file, and then set that as a stylesheet for the thing you wish to style.
To provide an example of this approach, just take the code from the previous example using looked-up colors and replace the setThemeColors method with the method below. This will then accomplish a dynamic styling of the preview pane using a dynamically created CSS file rather than looked-up colors.
Note: In creating the dynamic stylesheet, I tried to use the .root
selector to define styles (similar to Sai's answer), but, for whatever reason, it did not work (perhaps it doesn't work with my version of JavaFX (v13)). So instead I used specific CSS selectors to style items (e.g. label{-fx-text-fill:<custom-color>}
). That worked fine, and, as a bonus, it demonstrates the additional level of control you can get by defining your own stylesheets.
private void setThemeColors(Pane previewPane, Color backgroundColor, Color textColor, Color controlColor) {
try {
Path cssPath = Files.createTempFile("fx-theme-", ".css");
Files.writeString(
cssPath,
".themed{-fx-background-color:"+ toHexString(backgroundColor) +";}" +
".label{-fx-text-fill:"+ toHexString(textColor) +";}" +
".button{-fx-base:" + toHexString(controlColor) + ";}"
);
cssPath.toFile().deleteOnExit();
System.out.println("Wrote " + cssPath);
System.out.println("URL " + cssPath.toUri().toURL().toExternalForm());
previewPane.getStyleClass().setAll("themed");
previewPane.getStylesheets().setAll(
cssPath.toUri().toURL().toExternalForm()
);
} catch (IOException e) {
e.printStackTrace();
}
}
Background on looked-up colors
The following documentation is copied from the linked JavaFX CSS reference section on looked-up colors. This is a powerful technique to accomplish what you wish and the concept is (as far as I know) peculiar to JavaFX CSS processing and does not exist with standard HTML based CSS.
With looked-up colors you can refer to any other color property that is set on the current node or any of its parents. This is a very powerful feature, as it allows a generic palette of colors to be specified on the scene then used thoughout the application. If you want to change one of those palette colors you can do so at any level in the scene tree and it will affect that node and all its decendents. Looked-up colors are not looked up until they are applied, so they are live and react to any style changes that might occur, such as replacing a palette color at runtime with the "style" property on a node.
If you search inside the jar files which come with the JavaFX SDK you are using, you will find a file named modena.css
. This file pre-defines many looked-up colors that you can override as you with to more easily theme your application. These are not documented anywhere, you need to look at the modena.css file to see what they are (the most useful ones are in the .root
section of the file). The most important color is -fx-base
which will set a base color for the entire JavaFX control system.
The looked-up colors are often combined with some other JavaFX CSS concepts such as derivation and laddering to create consistent themes that are still readable when the base lookup color changes. This allows you to change the base color for example from white to black and the text displayed in controls based on the base color will automatically change from back to white so that it is still readable.
What is -fx-base
?
fx-base
is the base color for all controls, so setting it will change the color of all controls in the scene, which is probably what you also want, but perhaps not.
If you only want to change the buttons and not everything in the scene, just set the -fx-base color directly on each of the buttons rather than on an enclosing pane. One tricky way to accomplish that is that you can define your own CSS style for a button and in that, set -fx-base: my-custom-color
, then set the my-custom-color
style to a value dynamically in your program as shown in fabian's answer.
Note that setting the base color is preferred to trying to set the actual button color. Because, the button itself, when you look at it closely, includes a variety of gradients and shading it derives from the base color, so it is in fact made up of multiple colors when it is rendered and not just a single color.
You're lucky, since the only difference are colors. You can use lookedup colors for this puropse: Use my-color-name: <value>;
rules for the node itself or for one of the ancestors. This allows you to specify those values inline css and use them in your CSS stylesheet:
@Override
public void start(Stage primaryStage) {
HBox hBox = new HBox();
hBox.setMaxHeight(Region.USE_PREF_SIZE);
hBox.getStyleClass().add("box");
StackPane root = new StackPane(hBox);
Stream.of("red", "green", "blue").map(c -> {
Button b = new Button(c);
b.setOnAction(evt -> {
root.setStyle("-my-background:" + c);
});
return b;
}).forEach(hBox.getChildren()::add);
Scene scene = new Scene(root, 500, 500);
scene.getStylesheets().add(getClass().getResource("/path/to/my/style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
CSS stlyesheet
.box {
-fx-background-color: -my-background;
}
You can specify multiple colors this way, e.g.
root.setStyle("-my-color: red; -some-other-color: brown; -color-3: yellow;");