为了账号安全,请及时绑定邮箱和手机立即绑定

JavaFX:使用 CSS 动态着色窗口

JavaFX:使用 CSS 动态着色窗口

撒科打诨 2023-08-23 17:15:56
我正在尝试使用 JavaFX 创建一个具有三种不同颜色的 Pane 对象:背景颜色、文本颜色和按钮颜色。这三种颜色中的每一种都是在运行时根据传递到该方法的自定义对象的值动态确定的。我相当轻松地弄清楚了如何直接在我的代码中实现此行为,并且似乎我可以利用控制器和初始化方法来使用 FXML 进行设置。但我想知道是否可能或建议使用 CSS 设置类似的内容。据我所知,CSS 并没有真正使用代码中的变量,只是预先设置的硬编码值。考虑到潜在组合的数量之多,为每种组合制作不同的纸张似乎没有多大价值。不过,我听说使用 CSS 是现代实践,所以我想知道是否可以使用单个工作表来制作多个不同类型的窗格,或者是否每个可能的窗格都必须使用自己的工作表进行唯一定义,甚至窗格的其他所有内容都是相同的。
查看完整描述

3 回答

?
largeQ

TA贡献2039条经验 获得超7个赞

你很幸运,因为唯一的区别是颜色。您可以为此目的使用查找颜色:my-color-name: <value>;对节点本身或祖先之一使用规则。这允许您指定这些内联 css 值并在 CSS 样式表中使用它们:


@Override

public void start(Stage primaryStage) {

    HBox hBox = new HBox();

    hBox.setMaxHeight(Region.USE_PREF_SIZE);

    hBox.getStyleClass().add("box");


    StackPane root = new StackPane(hBox);


    Stream.of("red", "green", "blue").map(c -> {

        Button b = new Button(c);

        b.setOnAction(evt -> {

            root.setStyle("-my-background:" + c);

        });

        return b;

    }).forEach(hBox.getChildren()::add);


    Scene scene = new Scene(root, 500, 500);

    scene.getStylesheets().add(getClass().getResource("/path/to/my/style.css").toExternalForm());

    primaryStage.setScene(scene);

    primaryStage.show();

}

CSS 样式表


.box {

    -fx-background-color: -my-background;

}

您可以通过这种方式指定多种颜色,例如


root.setStyle("-my-color: red; -some-other-color: brown; -color-3: yellow;");


查看完整回答
反对 回复 2023-08-23
?
收到一只叮咚

TA贡献1821条经验 获得超4个赞

如果您只想更改颜色而不修改 CSS 的其余部分,您也可以遵循以下方法。如果您有一个非常大的 css 文件需要主题化,这会非常有用。


基本思想是让所有 css 都是一个基本 css 文件。将所有颜色定义为该基本文件中 .root 类中的变量。对于每个主题 css,您只需要覆盖颜色变量即可。并在基本文件之上加载主题 css 文件。这样您就不会遇到任何可能的复制粘贴问题或缺少 css 问题:)


一个完整的工作示例如下:


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

    }

}

基本.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;

}

类型1.css:


.root{

   -fx-window-border: red;

   -fx-window-color: yellow;

   -fx-window-text: brown;

   -fx-button-color: pink;

}

类型2.css:


.root{

   -fx-window-border: green;

   -fx-window-color: lightblue;

   -fx-window-text: white;

   -fx-button-color: grey;

}

类型3.css:


.root{

   -fx-window-border: brown;

   -fx-window-color: lightgreen;

   -fx-window-text: blue;

   -fx-button-color: yellow;

}

https://img1.sycdn.imooc.com//64e5ce7f000130c506510469.jpg

查看完整回答
反对 回复 2023-08-23
?
牧羊人nacy

TA贡献1862条经验 获得超7个赞

以下是动态设置给定组件主题颜色的几个具体示例:

  1. 费边对查找颜色的建议。

  2. 以编程方式创建的 CSS 样式表写入临时文件。

使用查找颜色的示例

该示例正在做的是设置一些标准查找颜色,这些颜色可以为modena.css您想要设置样式的三件事设置样式:

  1. 背景颜色 ( -fx-background-color)。这是从该类派生的类中使用的标准背景Pane

  2. 文本的颜色 ( -fx-text-background-color)。是的,我知道这个名字很令人困惑,但无论出于什么原因,它似乎就是这样。

  3. 按钮的颜色 ( -fx-base)。

https://img1.sycdn.imooc.com/64e5ce8b0001860a02810270.jpg

在示例应用程序中,用户可以使用 JavaFXColorPicker控件动态选择颜色来修改预览窗格中显示的项目的颜色。


import javafx.application.Application;

import javafx.geometry.Insets;

import javafx.scene.Scene;

import javafx.scene.control.*;

import javafx.scene.layout.*;

import javafx.scene.paint.Color;

import javafx.stage.Stage;


public class ThemeMaker extends Application {

    @Override

    public void start(Stage stage) throws Exception {

        Pane previewPane = createPreviewPane();

        Pane controlPane = createControlPane(previewPane);


        Pane layout = new VBox(

                20,

                controlPane,

                previewPane

        );

        layout.setPadding(new Insets(10));


        stage.setScene(new Scene(layout));

        stage.show();

    }


    private Pane createControlPane(Pane previewPane) {

        ColorPicker backgroundColorPicker = new ColorPicker(Color.web("#b3ccff"));

        ColorPicker textColorPicker = new ColorPicker(Color.web("#4d804d"));

        ColorPicker controlColorPicker = new ColorPicker(Color.web("#ffe6cc"));


        GridPane controlPane = new GridPane();

        controlPane.setHgap(5);

        controlPane.setVgap(5);

        controlPane.addRow(0, new Label("Background color:"), backgroundColorPicker);

        controlPane.addRow(1, new Label("Text color:"), textColorPicker);

        controlPane.addRow(2, new Label("Control color:"), controlColorPicker);


        backgroundColorPicker.valueProperty().addListener((observable, oldColor, newColor) ->

                setThemeColors(previewPane, backgroundColorPicker.getValue(), textColorPicker.getValue(), controlColorPicker.getValue())

        );

        textColorPicker.valueProperty().addListener((observable, oldColor, newColor) ->

                setThemeColors(previewPane, backgroundColorPicker.getValue(), textColorPicker.getValue(), controlColorPicker.getValue())

        );

        controlColorPicker.valueProperty().addListener((observable, oldColor, newColor) ->

                setThemeColors(previewPane, backgroundColorPicker.getValue(), textColorPicker.getValue(), controlColorPicker.getValue())

        );


        setThemeColors(previewPane, backgroundColorPicker.getValue(), textColorPicker.getValue(), controlColorPicker.getValue());


        return controlPane;

    }


    private void setThemeColors(Pane previewPane, Color backgroundColor, Color textColor, Color controlColor) {

        previewPane.setStyle(

                "-fx-background-color: " + toHexString(backgroundColor) + ";" +

                "-fx-text-background-color: " + toHexString(textColor) + ";" +

                "-fx-base: " + toHexString(controlColor) + ";"

        );

    }


    private Pane createPreviewPane() {

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

        Button btn = new Button("Sample Button");

        Pane previewPane = new VBox(10, label, btn);


        previewPane.setPadding(new Insets(5));

        previewPane.setPrefWidth(200);


        return previewPane;

    }


    // from https://stackoverflow.com/a/56733608/1155209 "How to get hex web String from JavaFX ColorPicker color?"

    private String toHexString(Color value) {

        return "#" + (format(value.getRed()) + format(value.getGreen()) + format(value.getBlue()) + format(value.getOpacity()))

                .toUpperCase();

    }


    private String format(double val) {

        String in = Integer.toHexString((int) Math.round(val * 255));

        return in.length() == 1 ? "0" + in : in;

    }


    public static void main(String[] args) {

        launch(args);

    }

}

使用动态样式表的示例

更新

JavaFX 17 添加了从数据 URI 加载样式表的功能。这将是下面示例中演示的另一种方法,该方法使用文件 IO 动态创建 CSS 文件。

因此,查找颜色解决方案非常强大,因为您可以动态设置场景中所有项目的颜色样式。然而,CSS 一般来说比颜色设置更强大。如果您的动态样式需要的不仅仅是颜色设置,或者您需要非常具体的规则来设置场景中特定项目的样式,那么您将需要自己的自定义样式表。


stylesheets节点和场景的属性是一个动态的可观察列表。因此,如果更改样式表,您将重新设置节点或场景的样式。每个样式表都由一个 URL 引用。因此,要动态创建样式表,您需要做的就是在代码中构造样式表内容并将其写入临时文件,获取对临时文件的 URL 引用,然后将其设置为您想要的样式表风格。


要提供此方法的示例,只需使用上一个示例中使用查找颜色的代码,并将 setThemeColors 方法替换为以下方法。然后,这将使用动态创建的 CSS 文件而不是查找的颜色来完成预览窗格的动态样式。


注意:在创建动态样式表时,我尝试使用选择.root器来定义样式(类似于 Sai 的答案),但是,无论出于何种原因,它都不起作用(也许它不适用于我的 JavaFX (v13) 版本) 。因此,我使用特定的 CSS 选择器来设置项目的样式(例如label{-fx-text-fill:<custom-color>})。这工作得很好,而且作为奖励,它展示了通过定义自己的样式表可以获得的额外控制级别。


private void setThemeColors(Pane previewPane, Color backgroundColor, Color textColor, Color controlColor) {

    try {

        Path cssPath = Files.createTempFile("fx-theme-", ".css");

        Files.writeString(

                cssPath,

                ".themed{-fx-background-color:"+ toHexString(backgroundColor) +";}" +

                ".label{-fx-text-fill:"+ toHexString(textColor) +";}" +

                ".button{-fx-base:" + toHexString(controlColor) + ";}"

        );

        cssPath.toFile().deleteOnExit();


        System.out.println("Wrote " + cssPath);

        System.out.println("URL " + cssPath.toUri().toURL().toExternalForm());


        previewPane.getStyleClass().setAll("themed");

        previewPane.getStylesheets().setAll(

                cssPath.toUri().toURL().toExternalForm()

        );

    } catch (IOException e) {

        e.printStackTrace();

    }

}

查找颜色的背景


以下文档是从有关查找颜色的链接 JavaFX CSS 参考部分复制的。这是一种实现您愿望的强大技术,并且该概念(据我所知)是 JavaFX CSS 处理所特有的,并且在基于标准 HTML 的 CSS 中不存在。


通过查找颜色,您可以引用在当前节点或其任何父节点上设置的任何其他颜色属性。这是一个非常强大的功能,因为它允许在场景上指定通用颜色调色板,然后在整个应用程序中使用。如果您想更改其中一种调色板颜色,您可以在场景树中的任何级别执行此操作,这将影响该节点及其所有后代。查找到的颜色在应用之前不会被查找,因此它们是实时的,并对可能发生的任何样式更改做出反应,例如在运行时用节点上的“样式”属性替换调色板颜色。


如果您在您正在使用的 JavaFX SDK 附带的 jar 文件中进行搜索,您将找到一个名为modena.css. 该文件预定义了许多查找的颜色,您可以覆盖这些颜色,以便更轻松地为应用程序设置主题。这些没有在任何地方记录,您需要查看 modena.css 文件以了解它们是什么(最有用的文件位于.root文件的 部分)。最重要的颜色-fx-base将为整个 JavaFX 控制系统设置基色。


查找的颜色通常与其他一些 JavaFX CSS 概念(例如派生和阶梯)结合起来,以创建一致的主题,当基本查找颜色发生变化时,这些主题仍然可读。这允许您更改基色,例如从白色更改为黑色,并且基于基色的控件中显示的文本将自动从回更改为白色,以便仍然可读。


什么是-fx-base?


fx-base是所有控件的基色,因此设置它会更改场景中所有控件的颜色,这可能也是您想要的,但也许不是。


如果您只想更改按钮而不是场景中的所有内容,只需直接在每个按钮上而不是在封闭的窗格上设置 -fx-base 颜色即可。实现这一目标的一种棘手方法是,您可以为按钮定义自己的 CSS 样式,然后在程序中将样式-fx-base: my-custom-color设置为动态值,如 fabian 的答案所示。my-custom-color


请注意,设置基色优于尝试设置实际按钮颜色。因为,当您仔细观察时,按钮本身包括从基色派生的各种渐变和阴影,因此在渲染时它实际上是由多种颜色组成的,而不仅仅是单一颜色。


查看完整回答
反对 回复 2023-08-23
  • 3 回答
  • 0 关注
  • 216 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信