JavaFX программно изменить фон кнопки закрытия выбранной вкладки - PullRequest
0 голосов
/ 22 февраля 2020

Я пытаюсь изменить фон значка закрытия только для выбранной вкладки, проблема в том, что она работает, когда есть одна вкладка, но когда я добавляю больше, ничего не происходит, пока я не закрою первую, вот что я сделал :

tabPane.lookup(".tab:selected:top").lookup(".tab-container").lookup(".tab-close-button").setStyle("-fx-background-color: red");

Теперь проблема в том, что когда я выбираю другую вкладку и пытаюсь изменить ее цвет, ничего не происходит, пока я не закрою предыдущую измененную.

Быстрый пример:
У меня есть 2 вкладки с белыми кнопками закрытия,
Я выбираю tab1: цвет изменился,
Я выбираю tab2: ничего не происходит,
Я закрываю tab1, затем выбираю tab2: кнопка закрытия вкладки tab2 изменена.

Выбранная вкладка верна, потому что, если я распечатаю эти 2 строки для каждой вкладки:

System.out.println(tabPane.getSelectionModel().getSelectedItem());
System.out.println(tabPane.lookup(".tab:selected:top").lookup(".tab-container").lookup(".tab-close-button"));

вот результат:

javafx.scene.control. Tab@6d81182e <---- tab1 <br>TabPaneSkin$TabHeaderSkin$2@7d620380 [styleClass = tab-close-button] <---- tab1 <br>// теперь я переключаюсь на tab2
javafx.scene.control. Tab@416ca04a <---- tab2 <br>TabPaneSkin$TabHeaderSkin$2@7d620380 [styleClass = tab-close-button] <---- этот адрес не должен совпадать с первым. </p>

PS: я не могу сделать это в CSS, потому что цвет должен динамически меняться, когда я переключаю табуляцию, строка выполняется внутри слушателя.

Спасибо за помощь!

1 Ответ

4 голосов
/ 22 февраля 2020

CSS поиски по своей природе зависят от деталей реализации инфраструктуры рендеринга, и, как таковые, они могут быть ненадежными, и вам следует по возможности избегать их использования. В частности, поиск не будет работать, если на графе сцены не был сделан проход CSS (как правило, это происходит во время рендеринга), или если с момента последнего прохода CSS произошли изменения в состоянии CSS. Я думаю, что с вашим кодом происходит то, что слушатель вызывается до того, как состояние CSS будет обновлено новым состоянием выбора, в результате чего вы получите неправильный Tab из поиска .tab:selected (хотя я нет уверенности, что это именно то, что происходит).

Обратите внимание, что у вас уже есть большая часть необходимого динамического поведения c, поскольку псевдокласс "selected" будет обновляться динамически.

Лучший Подход к такой функциональности заключается в том, чтобы определить внешний файл CSS, который охватывает все возможные стили, а затем программно изменить состояние CSS, используя один из нескольких механизмов. Для этого есть несколько способов:

  • Использовать «искомые цвета». Они действуют как имена переменных в CSS.
  • Использование пользовательских псевдоклассов, которые вы можете использовать в качестве селекторов в файле CSS, и состояние которых вы можете обновлять программно.

Вот пример с использованием первого подхода. Здесь мы определяем искомый цвет в файле CSS с именем "selected-tab-color", который изначально устанавливает для кнопки закрытия синий цвет.

tabpane. css:

.tab-pane {
    selected-tab-color: blue ;
}

.tab:selected > .tab-container > .tab-close-button {
    -fx-background-color: selected-tab-color ;
}

В Java коде мы можем обновить это просто вызовом tabPane.setStyle("selected-tab-color: red;"), который вы можете сделать, например, в событии обработчик (но действительно где угодно, если он находится в потоке приложения JavaFX). В этом примере мы просто перечисляем на выбранную вкладку, и на одной вкладке есть красная кнопка закрытия, а на других синяя.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class TabPaneTest extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        TabPane tabPane = new TabPane();
        Tab tab1 = new Tab("Tab 1", new Label("Tab 1"));
        Tab tab2 = new Tab("Tab 2", new Label("This tab has\na custom close button"));
        Tab tab3 = new Tab("Tab 3", new Label("Tab 3"));
        tabPane.getTabs().addAll(tab1, tab2, tab3);

        tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
            if (newTab == tab2) { 
                tabPane.setStyle("selected-tab-color: red;");
            } else {
                tabPane.setStyle("selected-tab-color: blue;");
            }

        });

        BorderPane root = new BorderPane(tabPane);

        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("tabpane.css").toExternalForm());


        primaryStage.setScene(scene);
        primaryStage.setWidth(250);
        primaryStage.setHeight(250);
        primaryStage.show();
    }

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

}

Вы можете использовать эту технику, чтобы полностью отключить эффект, вернувшись к по умолчанию:

tabPane.setStyle("selected-tab-color: -fx-mark-color;");

однако это зависит от знания деталей реализации CSS по умолчанию (т. е. имени цвета по умолчанию для кнопки закрытия вкладки).

A немного Лучший способ включить или отключить эту функцию программно - использовать собственный псевдокласс. Они работают как любой другой псевдокласс CSS, и их состояние может быть изменено программно.

В этом примере цвет кнопки закрытия будет красным, только если на вкладке, содержащей вкладку, установлен псевдокласс warn-on-close:

.tab-pane:warn-on-close .tab:selected > .tab-container > .tab-close-button {
    -fx-background-color: red ;
}

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

import javafx.application.Application;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class TabPaneTest extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        TabPane tabPane = new TabPane();
        Tab tab1 = new Tab("Tab 1", new Label("Tab 1"));
        Tab tab2 = new Tab("Tab 2", new Label("This tab has\na custom close button"));
        Tab tab3 = new Tab("Tab 3", new Label("Tab 3"));
        tabPane.getTabs().addAll(tab1, tab2, tab3);

        PseudoClass warnOnClosePseudoClass = PseudoClass.getPseudoClass("warn-on-close");
        tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> 
            tabPane.pseudoClassStateChanged(warnOnClosePseudoClass, newTab == tab2));

        BorderPane root = new BorderPane(tabPane);

        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("tabpane.css").toExternalForm());


        primaryStage.setScene(scene);
        primaryStage.setWidth(250);
        primaryStage.setHeight(250);
        primaryStage.show();
    }

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

}
...