CSS lookups are inherently reliant on implementation details of the rendering framework, and as such they can be unreliable and you should really avoid using them whenever possible. In particular, lookups will not work if a CSS pass has not been made on the scene graph (typically this happens during rendering), or if changes to the CSS state have occurred since the last CSS pass. I think what is happening with your code is that the listener is invoked before the CSS state has been updated with the new selection state, causing you to retrieve the wrong Tab
from the .tab:selected
lookup (though I'm by no means certain this is what's happening).
Note that you already have most of the dynamic behavior you need, because the "selected"
pseudoclass will be updated dynamically.
The best approach for functionality like this is to define an external CSS file which covers all your possible styles, and then to programmatically modify CSS state using one of a number of mechanisms. A couple of ways to do this are:
- Use "looked-up colors". These act like variable names in CSS.
- Use custom pseudoclasses, which you can use as selectors in the CSS file, and whose state you can update programatically.
Here's an example using the first approach. Here we define a looked-up color in the CSS file called "selected-tab-color"
which initially sets the close button to be blue.
tabpane.css:
.tab-pane {
selected-tab-color: blue ;
}
.tab:selected > .tab-container > .tab-close-button {
-fx-background-color: selected-tab-color ;
}
In Java code, we can update this simply with a call to tabPane.setStyle("selected-tab-color: red;")
, which you can do, e.g., in an event handler (but really anywhere, as long as it's on the JavaFX application thread). In this example, we just list to the selected tab, and make one tab have a red close button, the others blue.
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);
}
}
You can use this technique to turn the effect off entirely, by reverting to the default:
tabPane.setStyle("selected-tab-color: -fx-mark-color;");
however, this relies on knowing the details of the default CSS implementation (i.e. the name of the default color for the tab close button).
A slightly better way to turn the functionality on or off programmatically is to use a custom Pseudoclass. These work like any other CSS pseudoclass, and can have their state modified programmatically.
In this example, the color of the close button is red only if the containing tab pane has the warn-on-close
pseudoclass set:
.tab-pane:warn-on-close .tab:selected > .tab-container > .tab-close-button {
-fx-background-color: red ;
}
And in the Java code here, we switch this on when tab 2 is selected, and off when the others are selected. Again, arbitrary logic is possible here.
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);
}
}