Dynamically Add CSS Stylesheets in Javafx

Dynamically add CSS stylesheets in JavaFX

Your problem is that you aren't using a URL. Here you can find more documentation on how the CSS is loaded alongside the CSS reference.

If you have the URL as a String you could set the CSS dynamically with an external file like so:

private boolean isANext = true;

public void start(Stage primaryStage) throws Exception {
Button button = new Button("Change CSS");
VBox vbox = new VBox(10);
vbox.setAlignment(Pos.CENTER);
vbox.getChildren().add(button);
scene = new Scene(vbox, 200, 200);

button.setOnAction(ev -> {
// Alternate two stylesheets just for this demo.
String css = isANext ? "file:///C:/temp/a.css" : "file:///C:/temp/b.css";
isANext = !isANext;
System.out.println("Loading CSS at URL " + css);

scene.getStylesheets().clear();
scene.getStylesheets().add(css);
});

primaryStage.setTitle("Title");
primaryStage.setScene(scene);
primaryStage.show();
}

In the a.css

.button {    
-fx-text-fill: white;
-fx-background-color: red;
}

And in b.css

.button {    
-fx-text-fill: white;
-fx-background-color: black;
}

Dynamically change JavaFX css property

The method getStyleClass() returns a list of CSS class names associated with the node. These class names are used to determine which rules in an external CSS file are applied to the node. You cannot pass CSS selectors and rules in here.

Instead, write an external CSS file which contains a rule for the background color for the track. You can use a "looked-up color" here, which basically works like a "CSS variable". Define the "looked-up color" for the slider, and use it in a rule for the track to set the background color:

style.css:

.slider {
-track-color: white ;
}
.slider .track {
-fx-background-color: -track-color ;
}

Now, in the Java code, you can update the value for the looked-up color by calling setStyle("-track-color: /* someColor */") on the slider. Here is a quick example:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class SliderTest extends Application {

@Override
public void start(Stage primaryStage) {
Slider slider = new Slider();
slider.valueProperty().addListener((obs, oldValue, newValue) -> {
double percentage = 100.0 * (newValue.doubleValue() - slider.getMin()) / (slider.getMax() - slider.getMin());
slider.setStyle("-track-color: linear-gradient(to right, #90C7E0 " + percentage+"%, white "+percentage+("%);"));
});
StackPane root = new StackPane(slider);
root.setPadding(new Insets(20));
Scene scene = new Scene(root);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}

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

In your application, you can do the same using the current time of the media player (or just register a listener on the slider's value property, as that is changing too).

How do I dynamically add and remove css for the whole JavaFX application?

According to Global Stylesheet for your GUI application:

// load default global stylesheet
Application.setUserAgentStylesheet(null);
// add custom global stylesheet
StyleManager.getInstance().addUserAgentStylesheet(AQUA_CSS_NAME);

However as Boomah points out, StyleManager.getInstance().addUserAgentStylesheet is not part of the JavaFX API, so this method is really not recommended that it be used directly from user code. Additionally, it only works for adding a global stylesheet and not for removing a such a stylesheet once the stylesheet has been added.


The more adventurous could create a patch to add Boomah's suggested feature by modifying the StyleManager code to support removal of global stylesheets and modifying Application class source code to provide a public API for the new feature which makes use of the updated StyleManager, then submit the patch to openjfx-dev for inclusion in the JavaFX platform.


In the meantime you can manually set your user stylesheet on each of your application's scenes - kind of pain, but there you are . . .

JavaFX CSS Dynamic Styling

There are a handful of colors on which everything in modena is based. I had an example somewhere, which I can't find right now, but basically

  • -fx-base
  • -fx-accent
  • -fx-default-button
  • -fx-focus-color
  • -fx-faint-focus-color (same as -fx-focus-color but with opacity of 0x22)

So setting these on a root node will basically theme the entire root and its descendants.

In the end, there's no way around the fact that you will have to somehow update these when the user changes them on each root node, and you need to provide the wiring to do that. Using a CSS file is probably not a way to go, as it's going to be difficult to ensure that an updated file is reloaded as needed. I would probably wire things up so that the styleProperty() of the root node changes to define these colors when the user changes them.

You could consider creating a Theme class that encapsulates these:

public class Theme {

private final ObjectProperty<Color> base = new SimpleObjectProperty<>(Color.web("#ececec"));
private final ObjectProperty<Color> accent = new SimpleObjectProperty<>(Color.web("#0096c9"));
private final ObjectProperty<Color> defaultButton = new SimpleObjectProperty<>(Color.web("#abd8ed"));
private final ObjectProperty<Color> focusColor = new SimpleObjectProperty<>(Color.web("#039ed3"));
private final ObjectProperty<Color> faintFocusColor = new SimpleObjectProperty<>(Color.web("039ed322"));

public ObjectProperty<Color> baseProperty() {
return base ;
}

public final Color getBase() {
return baseProperty().get();
}

public final void setBase(Color base) {
baseProperty().set(base);
}

// etc etc

private final ReadOnlyStringWrapper css = new ReadOnlyStringWrapper() ;

public Theme() {
css.bind(Bindings.createStringBinding(() -> String.format(
"-fx-base: %s; "
+"-fx-accent: %s; "
+"-fx-default-button: %s; "
+"-fx-focus-color: %s ; "
+"-fx-faint-focus-color: %s ;",
toRgba(getBase()),
toRgba(getAccent()),
toRgba(getDefaultButton()),
toRgba(getFocusColor()),
toRgba(getFaintFocusColor())),
base, accent, defaultButton, focusColor, faintFocusColor));
}

private String toRgba(Color color) {
int r = (int) (255 * color.getRed());
int g = (int) (255 * color.getGreen());
int b = (int) (255 * color.getBlue());
int a = (int) (255 * color.getOpacity());
return String.format("#%02x%02x%02x%02x", r, g, b, a);
}

public ReadOnlyStringProperty cssProperty() {
return css.getReadOnlyProperty();
}

}

Then you can create a single Theme instance that you make available to you app, and bind all the root node's styleProperty to the cssProperty. Alternatively you could add a factory method to Theme for generating root nodes:

public <T extends Parent> T createThemedNode(Supplier<T> factory) {
T node = factory.get();
node.styleProperty().bind(cssProperty());
return node ;
}

which you use as, for example,

BorderPane root = theme.createThemedNode(BorderPane::new);

If you use FXML you could create similar types of factory methods for loading a FXML document and binding the style of the resulting node.

Finally, of course, you would just do something like

ColorPicker baseColorPicker = new ColorPicker();
baseColorPicker.valueProperty().bindBidirectional(theme.baseProperty());

etc, and when the user chooses a new color, everything is updated.

javafx: change style dynamically on pane

The functionality you're looking for is already implemented with the hover pseudoclass. So you can just do

.myPane {
/* do something */
}
.myPane:hover {
/* do something else */
}

In general, though it's not necessary in this case, you can define arbitrary CSS pseudoclasses (which will work like selected and hover). To reproduce the hover functionality, for example, you can do:

PseudoClass moveOver = PseudoClass.getPseudoClass("mouse-over");
Pane myPane = new Pane();
myPane.getStyleClass().add("my-pane");
myPane.setOnMouseEntered(e -> myPane.pseudoClassStateChanged(mouseOver, true));
myPane.setOnMouseExited(e -> myPane.pseudoClassStateChanged(mouseOver, false));

and then you can use the following CSS:

.my-pane {
-fx-background-color: white ;
}
.my-pane:mouse-over {
-fx-background-color: yellow ;
}

Note the pseudoclass name in the CSS (:mouse-over) matches the string passed to the getPseudoClass method (PseudoClass.getPseudoClass("mouse-over")), and can be any string that is valid to use as a CSS identifier.

JavaFX: Dynamically Colored Window with CSS

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;
}

Sample Image

Can you dynamically apply a colour scheme to a single css file?

As A Haworth mentioned, you can probably do something with CSS variables. I don't know how javaFX works, but hopefully you can do something similar to what I wrote below. I set a CSS variable to the body and then have jQuery change that variable when the user clicks the buttons.

$(document).ready(function() {
$("body").on("click", "#blue", function() {
$("body").css("--main-color", "blue");
});
$("body").on("click", "#red", function() {
$("body").css("--main-color", "red");
});
});
:body {
--main-color: blue;
}

.div-1 {
background: var(--main-color);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="div-1">Div 1</div>
<button id='blue'>Blue</button>
<button id='red'>Red</button>
</body>
</html>


Related Topics



Leave a reply



Submit