简体   繁体   中英

JavaFx css for whole application

we are struggling on a problem with custom css for our application. Until now we used workaround to apply our custom css but keep modena style for the rest.

StyleManager.getInstance().addUserAgentStylesheet(css);

In 8u40 java update this behavior was however restricted and is no longer working. Setting stylesheets to all scenes separately is madness, setting custom stylesheet via

Application.setUserAgentStylesheet(css);

works but not as we intended - modena stylesheet is replaced by custom so many controls are not styled at all. We found another workaround: copy whole modena folder to our project, add our custom css styles to modena.css and (very important!) rename modena.css to something else, for example modenaB.css. In JavaFx source code stylesheet is compared and if name remains the same, original modena is used eventhough different url was provided.

My question is, is there some better way to apply one custom stylesheet while application initialization which will be used through whole application in all scenes while keeping default modena styles for all not overriden controls?

The easiest way is to add your css file in User Agent Stylesheet.

@Override
public void start(Stage primaryStage) {
    // ...
    Application.setUserAgentStylesheet(Application.STYLESHEET_MODENA);
    StyleManager.getInstance().addUserAgentStylesheet("example.css");
    // ...
}

Answer inspired from GuiGarage .

How about place all logic related to building scene or control (view) to separate class and use it for building and assigning appropriate stylesheets?

For example in my case I have created FXMLBuilder class. It just load FXML base on controller class, then iterate through hierarchy of controllers and appliy stylesheets related to correspond controller in hierarchy. In file system I have the following hierarchy:

|
+- controllers
|  |
|  +- BaseController.java
|  +- BaseController.fxml
|  +- BaseController.css
|
...
|
+- MyController.java (inherited from BaseController)
+- MyController.fxml
+- MyController.css

So, when I build instance of MyController,

MyController myController = new MyController(...);
Parent myControllerRootNode = FXMLBuilder.build(myController);
...

it appliy stylesheets from MyController.css and BaseController.css files.

Here is my FXMLBuilder :

public class FXMLBuilder {

    public static final String FXML_EXTENSION = "fxml";
    public static final String CSS_EXTENSION = "css";

    public static Parent build(Object controller) throws IOException {
        Class<?> controllerClass = controller.getClass();

        URL fxmlURL = getResourceURL(controllerClass, FXML_EXTENSION);

        FXMLLoader fxmlLoader = new FXMLLoader(fxmlURL);
        fxmlLoader.setControllerFactory(param -> controller);

        Parent rootNode = fxmlLoader.load();

        loadStylesheets(rootNode, controllerClass);

        return rootNode;
    }

    private static void loadStylesheets(Parent rootNode, Class<?> controllerClass) {
        Class<?> currentControllerClass = controllerClass;
        while (!currentControllerClass.equals(Object.class)) {
            if (getResourceURL(currentControllerClass, CSS_EXTENSION) != null) {
                rootNode.getStylesheets().add(getResourcePath(currentControllerClass, CSS_EXTENSION));
            }
            currentControllerClass = currentControllerClass.getSuperclass();
        }
    }

    public static String getResourcePath(Class<?> resourceClass, String extension) {
        return String.format("/%s.%s", resourceClass.getCanonicalName().replace(".", "/"), extension);
    }

    public static URL getResourceURL(Class<?> resourceClass, String extension) {
        return resourceClass.getResource(getResourcePath(resourceClass, extension));
    }

}

I think you can write something like this that will load your base stylesheets applicable for all views and then load custom stylesheets specified for view.

PS Will be better to iterate through hierarchy beginning from base controller.

From my experience the easiest way ist to set the CSS with SceneBuilder in the top parent node. All childs of this node will inherit this CSS. Then for the single cases you can load another one to overwrite it for the view you want. I don't know if it works the same way within the code but it does with SceneBuilder!

If you are the author of the css file in question, you could always add an import at the top of the CSS file. That way you can use it as an userAgentStyleSheet replacement. eg

@import "com/sun/javafx/scene/control/skin/modena/modena.css";
/* rest of CSS code */

If you don't have control over the css file in question, you can always create a 'wrapper' css file which imports both the modena and the additional css file after eachother. eg

@import "com/sun/javafx/scene/control/skin/modena/modena.css";
@import "the/extra/file.css";

In your application you can set the userAgentStyleSheet using:

Application.setUserAgentStylesheet(css);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM