在JavaFX 8中,我可以从String中提供样式表吗?

时间:2014-07-11 18:48:57

标签: css resources stylesheet embedded-resource javafx-8

是否可以将整个样式表包装在字符串中并将其应用于某个节点? 用例是为PseudoClass添加特定(非变化)行为。 我知道我可以使用pane.getStylesheets().add(getClass().getResource("mycss.css").toExternalForm());,但我想知道是否有某种方法可以直接将其嵌入源代码中;一些事情:

pane.getStylesheets().add(
    ".button:ok { -fx-background-color: green; }\n"+
    ".button:ko { -fx-background-color: red; }");

4 个答案:

答案 0 :(得分:9)

我通过定义新的URL连接找到了一种方法:

private String css;

public void initialize() {
    ...
    // to be done only once.
    URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory());
    ...
}

private void updateCss(Node node) {
    // can be done multiple times.
    css = createCSS();
    node.getStylesheets().setAll("internal:"+System.nanoTime()+"stylesheet.css");
}

private class StringURLConnection extends URLConnection {
    public StringURLConnection(URL url){
        super(url);
    }

    @Override public void connect() throws IOException {}

    @Override public InputStream getInputStream() throws IOException {
        return new StringBufferInputStream(css);
    }
}

private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory {
    URLStreamHandler streamHandler = new URLStreamHandler(){
        @Override protected URLConnection openConnection(URL url) throws IOException {
            if (url.toString().toLowerCase().endsWith(".css")) {
                return new StringURLConnection(url);
            }
            throw new FileNotFoundException();
        }
    };
    @Override public URLStreamHandler createURLStreamHandler(String protocol) {
        if ("internal".equals(protocol)) {
            return streamHandler;
        }
        return null;
    }
}

显然协议“internal”可以是任何(非碰撞)格式良好的字符串,并且(在这个简单的示例中)文件路径被强制忽略。

我用它来设置全局.css,所以我不需要记住多个字符串。 似乎Stream只打开了一次,但我不知道在所有情况下是否都适用。

根据需要随意使代码复杂化;)

此方法可归功于Jasper Potts(see this example

答案 1 :(得分:3)

这是我的基于ZioBytre答案的CSS更新程序类(+1效果非常好)。

这是一个自包含的类,可以轻松复制到项目中并按原样使用。

它依赖于公共IO IOUtils类来基于Stream返回String。但如果需要,这可以很容易地被内联或替换为另一个库。

我在一个项目中使用此类,其中CSS可在应用程序内部,服务器端动态编辑,并推送到JavaFX客户端。它可用于CSS字符串不是来自文件或URL而是来自其他来源(服务器应用程序,数据库,用户输入......)的任何场景。

它有一个绑定字符串属性的方法,以便CSS更改一旦发生就会自动应用。

/**
 * Class that handles the update of the CSS on the scene or any parent.
 *
 * Since in JavaFX, stylesheets can only be loaded from files or URLs, it implements a handler to create a magic "internal:stylesheet.css" url for our css string
 * see : https://github.com/fxexperience/code/blob/master/FXExperienceTools/src/com/fxexperience/tools/caspianstyler/CaspianStylerMainFrame.java
 * and : http://stackoverflow.com/questions/24704515/in-javafx-8-can-i-provide-a-stylesheet-from-a-string
 */
public class FXCSSUpdater {

    // URL Handler to create magic "internal:stylesheet.css" url for our css string
    {
        URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory());
    }

    private String css;

    private Scene scene;

    public FXCSSUpdater(Scene scene) {
        this.scene = scene;
    }

    public void bindCss(StringProperty cssProperty){
        cssProperty.addListener(e -> {
            this.css = cssProperty.get();
            Platform.runLater(()->{
                scene.getStylesheets().clear();
                scene.getStylesheets().add("internal:stylesheet.css");
            });
        });
    }

    public void applyCssToParent(Parent parent){
        parent.getStylesheets().clear();
        scene.getStylesheets().add("internal:stylesheet.css");
    }

    /**
     * URLConnection implementation that returns the css string property, as a stream, in the getInputStream method.
     */
    private class StringURLConnection extends URLConnection {
        public StringURLConnection(URL url){
            super(url);
        }

        @Override
        public void connect() throws IOException {}

        @Override public InputStream getInputStream() throws IOException {
            return IOUtils.toInputStream(css);
        }
    }

    /**
     * URL Handler to create magic "internal:stylesheet.css" url for our css string
     */
    private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory {

        URLStreamHandler streamHandler = new URLStreamHandler(){
            @Override
            protected URLConnection openConnection(URL url) throws IOException {
                if (url.toString().toLowerCase().endsWith(".css")) {
                    return new StringURLConnection(url);
                }
                throw new FileNotFoundException();
            }
        };

        @Override
        public URLStreamHandler createURLStreamHandler(String protocol) {
            if ("internal".equals(protocol)) {
                return streamHandler;
            }
            return null;
        }
    }
}

用法:

StringProperty cssProp = new SimpleStringProperty(".root {-fx-background-color : red}");
FXCSSUpdater updater = new FXCSSUpdater(scene);
updater.bindCss(cssProp);

//new style will be applied to the scene automatically
cssProp.set(".root {-fx-background-color : green}");

//manually apply css to another node
cssUpdater.applyCssToParent(((Parent)popover.getSkin().getNode()));

答案 2 :(得分:2)

我查看了文档,但我没有看到内置的方法。 getStylesheetsParent中唯一与样式表相关的方法,它只接受“链接到样式表的字符串URL”,而不是样式表本身。它返回一个通用的ObservableList,因此它的返回值没有针对不同类型的特殊方法;只有通用add。这与getResource返回URL一致,而toExternalForm()只返回该网址对象的字符串版本。

但是,您可以尝试一件事:data URI。不是将生成的URI传递给样式表文件,而是传入其内容为样式表的数据URI。我不知道API是否会接受这种URI,因为getStylesheets文档中链接的CSS Reference Guide说明

  

样式表网址可以是绝对网址或相对网址。

首先尝试一个非常简单的数据URI,看它是否有效。您可以使用this online tool生成一个。如果Java确实接受了数据URI,那么您只需要将包含CSS的String包含一些将String转换为数据URI的方法调用,如下所示:

pane.getStylesheets().add(new DataURI(
    ".button:ok { -fx-background-color: green; }\n"+
    ".button:ko { -fx-background-color: red; }").toString());

班级DataURI是假设的。如果JavaFX接受手动生成的数据URI,那么您必须找到一个自己提供DataURI类的库;我确定某个地方存在。

还有一种方法可以将某个Node的内联CSS指定为String,这几乎就是您要查找的内容。它在CSS Reference Guide中提到:

  

CSS样式可以来自样式表或内联样式。样式表是从Scene对象的stylesheets变量中指定的URL加载的。如果场景图包含Control,则会加载默认的用户代理样式表。内联样式通过Node setStyle API指定。内联样式类似于HTML元素的style="…"属性。

然而,听起来它不支持CSS中的选择器,只支持规则 - 所以不是说.red { color: red; },而是只能写color: red;,它适用于所有孩子那个Node。这听起来不像你想要的。所以数据URI是你唯一的希望。


编辑:虽然这是一个聪明的想法(我以前不知道数据URI)但它不起作用。我有同样的要求所以我试过了。它不会引发异常,但日志中会出现警告,并且不会应用样式:

我用过这种风格:

.root{
    -fx-font-family: "Muli";
    -fx-font-weight: lighter;
    -fx-font-size: 35pt;
    -fx-padding: 0;
    -fx-spacing: 0;
}

使用提供的工具生成以下数据URI:

data:text/css;charset=utf-8,.root%7B%0D%0A%20%20%20%20-fx-font-family%3A%20%22Muli%22%3B%0D%0A%20%20%20%20-fx-font-weight%3A%20lighter%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-padding%3A%200%3B%0D%0A%20%20%20%20-fx-spacing%3A%200%3B%0D%0A%7D

将它应用到我的场景中:

    scene.getStylesheets().add("data:text/css;charset=utf-8,.root%7B%0D%0A%20%20%20%20-fx-font-family%3A%20%22Muli%22%3B%0D%0A%20%20%20%20-fx-font-weight%3A%20lighter%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-padding%3A%200%3B%0D%0A%20%20%20%20-fx-spacing%3A%200%3B%0D%0A%7D");

结果(原谅我的法语,AVERTISSEMENT =警告):

janv. 07, 2015 12:02:03 PM com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged
AVERTISSEMENT: Resource "data:text/css;charset=utf-8,%23header%7B%0D%0A%20%20%20%20-fx-background-color%3A%23002D27%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-text-fill%3A%20%23fff%3B%0D%0A%7D" not found.

很遗憾,JavaFX似乎并不了解数据URI。

答案 3 :(得分:1)

对于正在编写不想用尽全局静态url流工厂的唯一覆盖框架级代码的人来说,你可以改为绑定到内部服务加载器" URL类本身的框架。

为此,您必须创建一个名为getWaveform()的类,并更新系统属性Handler extends URLStreamHandler以指向该类的包,减去最终的包后缀。因此,java.protocol.handler.pkgs会将该属性设置为com.fu,然后所有com.fu.css请求都将路由到此处理程序。

我将粘贴我正在使用的课程;原谅奇怪的收藏品和供应商界面;你可以猜出它们做了什么,并用标准实用程序替换它们而没有太多麻烦。

css:my/path

现在,任何代码都可以调用Handler.registerStylesheet(" my / path",() - >" * {-fx-css:blah}");和您可以通过" css:my / path"在任何地方使用此样式表。

请注意,我只查看网址的路径部分;我打算利用查询参数来进一步增加动态(通过使用接受参数映射的css工厂),但这超出了这个问题的范围。