【问题标题】:CSS Not Cascaded Properly to Label (JavaFX)CSS 未正确级联到标签 (JavaFX)
【发布时间】:2015-10-02 19:16:48
【问题描述】:

我有一个内置在 SceneBuilder 中的 FXML 类,它通过 FXMLLoader 加载,并带有一个与之关联的控制器。我在小部件的父面板上放置了一个带有伪类的样式类。 ToggleButton 的样式很好,但两个标签不是。

作为测试,我尝试直接为其中一个标签指定 CSS 类。这种行为很有趣;它在初始更改时得到响应,但在伪类更改时没有更新文本颜色。

以下是从生产代码中删除的 SCCEE。所有文件都直接在类路径中。

CustomButton.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="70.0" prefWidth="70.0" styleClass="custom-button" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="CustomButtonController">
   <children>
      <ToggleButton fx:id="selectionToggle" mnemonicParsing="false" prefHeight="70.0" prefWidth="70.0" />
      <Label fx:id="dateLabel" alignment="TOP_LEFT" contentDisplay="TOP" layoutX="5.0" layoutY="5.0" mouseTransparent="true" prefHeight="25.0" prefWidth="60.0" styleClass="custom-button" text="Date" wrapText="true" />
      <Label fx:id="eventLabel" alignment="BOTTOM_LEFT" contentDisplay="BOTTOM" layoutX="5.0" layoutY="40.0" mouseTransparent="true" prefHeight="25.0" prefWidth="60.0" text="Event" wrapText="true" />
   </children>
</Pane>

CustomButtonController.java:

/**
 * Sample Skeleton for 'CustomButton.fxml' Controller Class
 */

import java.net.URL;
import java.util.ResourceBundle;

import javafx.css.PseudoClass;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;

public class CustomButtonController {
    @FXML // ResourceBundle that was given to the FXMLLoader
    private ResourceBundle resources;
    @FXML // URL location of the FXML file that was given to the FXMLLoader
    private URL location;
    @FXML // fx:id="dateLabel"
    private Label dateLabel; // Value injected by FXMLLoader
    @FXML // fx:id="selectionToggle"
    private ToggleButton selectionToggle; // Value injected by FXMLLoader
    @FXML // fx:id="eventLabel"
    private Label eventLabel; // Value injected by FXMLLoader


    private Parent m_parent;


    @FXML // This method is called by the FXMLLoader when initialization is complete
    void initialize() {
        assert dateLabel != null : "fx:id=\"dateLabel\" was not injected: check your FXML file 'CustomButton.fxml'.";
        assert selectionToggle != null : "fx:id=\"selectionToggle\" was not injected: check your FXML file 'CustomButton.fxml'.";
        assert eventLabel != null : "fx:id=\"eventLabel\" was not injected: check your FXML file 'CustomButton.fxml'.";
    }

    public void setParent(Parent parent) {
        m_parent = parent;

        selectionToggle.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                m_parent.pseudoClassStateChanged(PseudoClass.getPseudoClass("state2"), selectionToggle.isSelected());
                m_parent.applyCss();
            }
        });
    }
}

Main.java:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Parent;
import javafx.scene.Scene;

public class Main implements Runnable {

    @Override
    public void run() {
        JFrame frame = new JFrame("Test Frame");
        frame.setSize(800, 600);

        JPanel panel = new JPanel();
        frame.setContentPane(panel);

        FXMLLoader loader = new FXMLLoader(getClass().getResource("/CustomButton.fxml"));
        Parent parent = loader.load();

        String cssString = getClass().getResource("/CustomButton.css").toExternalForm();
        parent.getStylesheets().add(cssString);

        CustomButtonController cont = loader.<CustomButtonController>getController();
        cont.setParent(parent);

        JFXPanel jfxPanel = new JFXPanel();
        jfxPanel.setScene(new Scene(parent));

        panel.add(jfxPanel);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new JFXPanel();
                Platform.runLater(new Main());
            }
        });
    }
}

CustomButton.css:

.custom-button:state2 { 
  -fx-base: cyan;
  -fx-text-fill: red;
  -fx-fill: yellow;
  -fx-stroke: purple;
}

.custom-button { 
  -fx-base: green;
  -fx-text-fill: blue;
}

结果(顶部未选中,底部已选中):

请注意,ToggleButton 服从 -fx-base,日期标签(直接添加了 CSS 类)服从 -fx-text-fill 的初始颜色,但不会在选择时更新),以及事件标签不会从 CSS 中获取任何内容。

【问题讨论】:

  • 有必要使用伪类吗?一个 Node 可以拥有任意数量的样式类,那么为什么不在 CSS 中使用像 .custom-button.state2(句点而不是冒号)这样的选择器呢?
  • @VGR 一般来说,伪类对于这种功能来说要方便得多,因为您不必担心多次检查是否没有添加同一个类。
  • @James_D 我不确定我是否跟随。无论是使用样式类还是伪类,在将控件返回到正常状态时,代码仍然必须将其删除。在最坏的情况下,多次添加样式类没有任何后果(除了小的内存泄漏)。我错过了什么?
  • @VGR 如果您可能无意中多次添加它,则删除它的代码必须确保它删除了 所有 个副本。如果您不控制管理此方法的方法的调用(例如单元实现中的updateItem(...)),则必须涵盖所有可能性。所以你要么需要昂贵的(和丑陋的)if (! getStyleClass().contains("my-style")) getStyleClass().add("my-style"); 来添加,要么需要昂贵的(和丑陋的)while (getStyleClass().contains("my-class")) getStyleClass().remove("my-style");
  • @James_D 或者,getStyleClass().removeAll(Collections.singleton("my-style")),这对我来说似乎并没有那么糟糕。我想这是主观的;对我来说,使用 PseudoClass 似乎更尴尬。

标签: java css javafx fxml fxmlloader


【解决方案1】:

您的 FXML 将样式类 custom-button 应用于窗格(fxml 的根)和dateLabel。因此,您从 CSS 样式类透视的层次结构看起来像

.custom-button .toggle-button.button.labeled .label.labeled.custom-button .label.labeled

从您的侦听器中,选择“切换按钮”时,您可以在根窗格上设置 state2 pseudoclass。所以当被选中时,层次结构看起来像

.custom-button:state2 .toggle-button.button.labeled .label.labeled.custom-button .label.labeled
  • 伪类状态不通过场景图继承。所以state2 伪类状态不会传播到子节点。
  • 来自查找颜色are propagated to the child nodes 的值, 所以-fx-base(这是一种查找颜色)的值被传播 从父节点到子节点。
  • 属性通常不被继承。有几个specific exceptions,不包括-fx-text-fill

因此,根据您的代码,您描述的行为是预期的。 dateLabel 获得蓝色文本填充,因为它具有 custom-button 样式类,并且 -fx-text-fill: blue; 直接应用于该样式类。选择按钮时它不会得到-fx-text-fill: red;,因为它没有设置伪类state2(只有根元素具有该伪类)。 fx-base 的值被应用到根节点,因为它是一种查找颜色,所以它们会通过场景图传播。

你可以试试

.custom-button {
    -fx-base: green ;
}
.custom-button:state2 {
    -fx-base: cyan ;
}
.custom-button .labeled {
    -fx-text-fill: blue ;
}
.custom-button:state2 .labeled {
    -fx-text-fill: red ;
}

获得您想要的效果。

【讨论】:

  • 请注意,样式类应用于包含按钮和两个标签的父窗格,并且伪类正在传播到按钮(但不是事件标签,对于伪类或一般的样式类)。查看 -fx-base 的结果。
  • 我错过了在根节点上也声明了custom-button。相应地更新了答案。传播-fx-base 的值是因为它是一种查找颜色,而不是因为传播了伪类状态。
  • 好的,我明白你对伪类的看法了。非常感谢您的澄清,这是一个很大的帮助。如果我理解正确,我注意到的行为会发生,因为 -fx-base 是一种查找颜色,但 -fx-text-fill 不是(因此为什么按钮基础正在更新,但事件标签不是)?为什么会这样,我以后有什么办法可以验证某物是查找颜色而某物不是?
  • 我应该澄清一下,我确实阅读了您提供给 JavaFX 颜色的链接中的查找颜色部分,但在我看来,他们将查找颜色定义为变量的等价物(IE: color = y vs. color = RED),而不是属性的属性(即:适用于 -fx-base 但不适用于其他颜色属性的东西)。
  • 除了阅读 modena.css 源代码之外,没有真正确定的方法可以判断。但是textFill 有一个对应的属性,所以它是在那里定义的,而不是在 CSS 中; base 没有。
【解决方案2】:

最后,我使用“继承”来确保我的标签从自定义父类读取 -fx-text-fill。生成的 CSS 如下:

CustomButton.css:

.custom-button:state2 { 
  -fx-base: white;
  -fx-text-fill: red;
}

.custom-button { 
  -fx-base: black;
  -fx-text-fill: cyan;
}

.label {
    -fx-text-fill: inherit;
}

也可以通过定义查找颜色来完成。

CustomButton.css:

.custom-button:state2 { 
  -fx-base: white;
  -labelColor: red;
}

.custom-button { 
  -fx-base: black;
  -labelColor: cyan;
}

.label {
    -fx-text-fill: -labelColor;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-20
    • 1970-01-01
    • 2022-11-01
    • 2011-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多