1

I searched and searched for an answer to my question online before asking but couldn't find anything like this.

I want my application user to be able to pick a color from the JavaFX ColorPicker and the entire application window colors, button colors, font etc. updates based off their choice. There are a LOT of screens in my application and I don't really want to setStyle() on every pane to make sure the colors change I want to have some type of CSS file where the colors in it can change based on the color chosen in the ColorPicker. Is this possible? I mean I realize you can write a text file through Java Code and give it a ".css" extension but is there any other way to accomplish this?

What are the "best practices" for this sort of thing in FX?

0

1 Answer 1

5

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.

Sign up to request clarification or add additional context in comments.

6 Comments

You can also check out Caspian.css if you want to see the exact stylesheet used be default. Here is a link: gist.github.com/tmazeika/c90c03c645d18722ddb0
The default style sheet has been Modena, not Caspian, for a while now. I'll post a link when I am back at the computer.
my bad, youre right. Here's modena: gist.github.com/maxd/63691840fc372f22f470
@Chris You can also get it from the OpenJFX source repo, which means you can get the particular JavaFX version you need (there are some slight changes, mostly bug fixes, between 8 and the current version). Choose "rt" for the version you want, then "browse" -> "modules" -> "controls" -> "src" -> "main" -> "resources/ com/sun/javafx/scene/control/skin" -> "modena" -> "modena.css". (Even the latest version still says "TODO: Explain here how theming works:" though...)
This is awesome. You more than answered my question. I must say it's still annoying to have to set each container but whatever (right?). Anyways this is really good and detailed. I actually am using FXML and it looks as though I may have to create a method in my program Manager class to change a Parent FXML so I only have to call this crap once lolol.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.