Как изменить стиль компонентов по их атрибутам?

#java #css #javafx #pseudo-class

#java #css #javafx #псевдокласс

Вопрос:

Я хочу отображать разные метки в javafx, и я хочу стилизовать их в зависимости от их текста. Я добавил файл css и установил класс меток. Затем я проверил fxml и обнаружил, что текст сохраняется в атрибуте text.

Я заглянул в обычный css и обнаружил, что вы можете там изменить стиль по атрибутам. Для этого вам нужно использовать [] . Я попробовал это в своем коде, и это не сработало.

Мой код: FXML:

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

<?import java.net.URL?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>

<HBox xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="controller">
    <stylesheets>
        <URL value="@../css/loadingScreen.css"/>
    </stylesheets>
    <Label styleClass="field" text="1" />
    <Label styleClass="field" text="2" />
    <Label styleClass="field" text="3" />
</HBox>
  

CSS:

 .field {
    -fx-text-alignment: center;
    -fx-pref-height: 64px;
    -fx-min-width: 64px;
    -fx-pref-width: 64px;
    -fx-min-height: 64px;
    -fx-background-color: blue;
}

.field[text="1"]{
    -fx-background-color: red;
}

.field[text="2"]{
    -fx-background-color: yellow;
}

.field[text="3"]{
    -fx-background-color: green;
}
  

я попробовал то же самое с обычными css и html, и там это сработало.
HTML:

 <!DOCTYPE html>
<html>
<head>
<style>
.field[text="1"]{
    background-color: red;
}

.field[text="2"]{
    background-color: yellow;
}

.field[text="3"]{
    background-color: green;
}
</style>
</head>
<body>

<div class="field" text="1" >1</div>
<div class="field" text="2" >2</div>
<div class="field" text="3" >3</div>

</body>
</html>
  

Что мне нужно сделать, чтобы это работало в fxml?

Комментарии:

1. Не верьте, что JavaFX-CSS поддерживает то, что вы хотите. Почему бы не присвоить каждому Label определенный класс стиля на основе текста? Или, если у вас есть только приблизительно три метки, каждая из которых имеет уникальный стиль, вы могли бы вместо этого установить их ID .

2. спасибо за ответ, я, вероятно, собираюсь использовать стиль для каждого текстового значения. Первоначальная причина, по которой я хотел задать стиль по атрибуту, заключалась в том, что если бы я изменил текст, это также автоматически изменило бы стиль без необходимости менять класс.

3. Slaw прав, смотрите этот раздел руководства по регрессии CSS, начинающийся со списка: openjfx.io/javadoc/11/javafx.graphics/javafx/scene/doc-files/… Прямо сейчас вы, похоже, не обновляете Label s, поэтому я не уверен, почему вы просто не работаете с идентификаторами / классами стилей, чтобы «указать Label s appart» в CSS…

Ответ №1:

Если бы я изменил текст, это также автоматически изменило бы стиль

Вариант 1: управлять стилем по идентификатору
Этого можно добиться, используя пользовательскую метку, которая меняет стиль при изменении текста. Я продемонстрирую это, изменив идентификатор метки. В этом упрощенном примере текст используется в качестве идентификатора :

 import javafx.geometry.Pos;
import javafx.scene.control.Label;

public class CustomLabel extends Label{

    public CustomLabel() {
        setAlignment(Pos.CENTER);
        setPrefSize(50, 25);
    }

    void setTextAndId(String s){
        super.setText(s);
        /*To keep this demo simple and clear id is changed.
          If used, care must be taken to keep id unique. 
          Using setStyle() or PseudoClass should be preferred 
        */
        setId(s); 
    }
}
  

Пользовательскую метку можно использовать в fxml ( Root.fxml ):

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<?import tests.CustomLabel?>

<StackPane fx:id="root" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="tests.Controller">
   <children>
      <CustomLabel fx:id="cLabel" text="amp;quot;amp;quot;" />
   </children>
</StackPane>
  

Простой css, который изменяет цвет фона на основе id ( Root.css ):

 #1{
    -fx-background-color: red;
    -fx-text-fill: white;
}
#2{
    -fx-background-color: yellow;
    -fx-text-fill: red;
}
#3{
    -fx-background-color: green;
    -fx-text-fill: yellow;
}
  

Тестовый класс:

 import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class LabelCssTest extends Application {

    @Override public void start(Stage stage) throws IOException {
        Parent root = FXMLLoader.load(getClass().getResource("Root.fxml"));
        stage.setScene(new Scene(root));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
  

И контроллер тестирования :

 import javafx.animation.PauseTransition;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.util.Duration;

public class Controller {

    @FXML
    CustomLabel cLabel;
    @FXML Parent root;
    private static final int MIN_VALUE = 1, MAX_VALUE = 3;
    private int counter = MIN_VALUE;

    @FXML
    private void initialize() {

        root.getStylesheets().add(getClass().getResource("Root.css").toExternalForm());
        cLabel.setTextAndId(String.valueOf(counter  ));

        PauseTransition pause = new PauseTransition(Duration.seconds(2));
        pause.setOnFinished(event ->{
            cLabel.setTextAndId(String.valueOf(counter  ));
            if(counter > MAX_VALUE) {
                counter = MIN_VALUE;
            }
            pause.play();
        });
        pause.play();
    }
}
  

Вариант 2: управляйте стилем, изменяя style-class
Используйте тот же тестовый класс, что и вариант 1.

Root.fxml :

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>

<StackPane fx:id="root" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tests.Controller">
   <children>
      <Label fx:id="label" alignment="CENTER" contentDisplay="CENTER" prefHeight="20.0" prefWidth="70.0" text="amp;quot; amp;quot;" />
   </children>
</StackPane>
  

Root.css :

 .style1{
    -fx-background-color: red;
    -fx-text-fill: white;
}
.style2{
    -fx-background-color: yellow;
    -fx-text-fill: red;
}
.style3{
    -fx-background-color: green;
     -fx-text-fill: yellow;
}
  

И контроллер:

 import javafx.animation.PauseTransition;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.util.Duration;

public class Controller {

    @FXML Label label;
    @FXML Parent root;

    private static final int MIN_VALUE = 1, MAX_VALUE = 3;
    private SimpleIntegerProperty counter = new SimpleIntegerProperty();

    @FXML
    private void initialize() {

         root.getStylesheets().add(getClass().getResource("Root.css").toExternalForm());
        counter = new SimpleIntegerProperty();
        counter.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
            label.getStyleClass().clear();
            label.getStyleClass().add("style" counter.get());
        });
        label.textProperty().bind(Bindings.createStringBinding(()->String.valueOf(counter.get()), counter));
        counter.set(1);

        PauseTransition pause = new PauseTransition(Duration.seconds(2));
        pause.setOnFinished(event ->{
            counter.set(counter.get() >= MAX_VALUE ? MIN_VALUE : counter.get() 1);
            pause.play();
        });
        pause.play();
    }
}
  

Вариант 3: управляйте стилем с помощью PseudoClass :
Изменения из варианта 2:
Root.css :

 .root:style1 #label{
    -fx-background-color: red;
    -fx-text-fill: white;
}
.root:style2 #label{
    -fx-background-color: yellow;
    -fx-text-fill: red;
}
.root:style3 #label{
    -fx-background-color: green;
    -fx-text-fill: yellow;
}
  

И контроллер:

 import javafx.animation.PauseTransition;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.css.PseudoClass;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.util.Duration;

public class Controller {

    @FXML Label label;
    @FXML Parent root;

    private static final int MAX_VALUE = 3;
    private SimpleIntegerProperty counter = new SimpleIntegerProperty(1);

    @FXML
    private void initialize() {

        root.getStylesheets().add(getClass().getResource("Root.css").toExternalForm());
        counter = new SimpleIntegerProperty();
        counter.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
            updateStates();
        });

        label.textProperty().bind(Bindings.createStringBinding(()->String.valueOf(counter.get()), counter));
        counter.set(1);

        PauseTransition pause = new PauseTransition(Duration.seconds(2));
        pause.setOnFinished(event ->{
            counter.set(counter.get() >= MAX_VALUE ? 1 : counter.get() 1);
            pause.play();
        });
        pause.play();
    }

    private void updateStates() {
        for( int index = 1; index <= MAX_VALUE; index  ){
            PseudoClass pc = PseudoClass.getPseudoClass("style" String.valueOf(index));
            root.pseudoClassStateChanged(pc, index == counter.get()  ? true : false);
        }
    }
}
  

Комментарии:

1. хм… разве идентификатор не должен быть уникальным во всем документе?

2. спасибо за предложение, я реализовал это, и оно отлично работает. я немного изменил это. это изменяет класс вместо идентификатора, потому что пользовательская метка может иметь тот же текст, что и другие пользовательские метки.

3. Изменение класса стиля или использование псевдокласса, безусловно, лучше. Опубликованный метод был выбран для наглядности и простоты.

4. @kleopatra Вы правы. отсюда комментарий «//возможно, вы захотите реализовать здесь некоторую логику». Эта упрощенная демонстрация не нарушает его, но я улучшил формулировку комментария. Спасибо за ваш комментарий.

Ответ №2:

Если вы просто хотите изменить цвет фона Label в зависимости от текста, вы также можете просто создать метод, подобный этому, и вызывать его для каждого Label , когда вам это нужно.

 import javafx.scene.control.Label;
import javafx.geometry.Insets;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color;    

private void backgroundColorTextDependent(Label label) {
        if (label.getText().equals("1")) {
            label.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
        } else if (label.getText().equals("2")) {
            label.setBackground(new Background(new BackgroundFill(Color.YELLOW, CornerRadii.EMPTY, Insets.EMPTY)));
        } else if (label.getText().equals("3")) {
            label.setBackground(new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, Insets.EMPTY)));
        } else {
            label.setBackground(new Background(new BackgroundFill(Color.BLUE, CornerRadii.EMPTY, Insets.EMPTY)));
        }