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;
}
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
Preventing System.Exit() from API
What's the Difference Between Session.Persist() and Session.Save() in Hibernate
How to Know Whether I am in a Call on Android
How to Get Timezone from Android Mobile
Android Recyclerview Scrolling Performance
Ksoap2 Org.Xmlpull.V1.Xmlpullparserexception Expected Start_Tag Error
What Kind of Ocr Java Library Should I Use in Android
Android.App.Application Cannot Be Cast to Android.App.Activity
How to Get Count of Number Methods Used in a Jar File
How Big Is an Object Reference in Java and Precisely What Information Does It Contain
Java, Find Intersection of Two Arrays
How to Capitalize the First Letter of a String in Java
Android: Dex Cannot Parse Version 52 Byte Code
How to Change Text of a Textview in Navigation Drawer Header
How to Use Weakreference in Java and Android Development