0

Is there a way (CSS or Java) to style first and last tab only in a dynamic TabPane?

Example :
example

Thanks!

adxl
  • 829
  • 1
  • 9
  • 19

1 Answers1

3

You can observe the ObservableList<Tab> returned by TabPane#getTabs() and update the style class of each Tab as appropriate. For example:

App.java:

import javafx.application.Application;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.css.Styleable;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TabPane.TabClosingPolicy;
import javafx.scene.control.TabPane.TabDragPolicy;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class App extends Application {

  @Override
  public void start(Stage primaryStage) {
    TabPane pane = new TabPane();
    pane.setTabClosingPolicy(TabClosingPolicy.ALL_TABS);
    pane.setTabDragPolicy(TabDragPolicy.REORDER); // requires JavaFX 10+
    pane.getTabs().addListener(App::tabsChanged);
    pane.getTabs() // add tabs **after** adding ListChangeListener
        .addAll(
            new Tab("Test Tab #1", new StackPane(new Label("Content #1"))),
            new Tab("Test Tab #2", new StackPane(new Label("Content #2"))),
            new Tab("Test Tab #3", new StackPane(new Label("Content #3"))),
            new Tab("Test Tab #4", new StackPane(new Label("Content #4"))),
            new Tab("Test Tab #5", new StackPane(new Label("Content #5"))));
    Scene scene = new Scene(pane, 600, 400);
    scene.getStylesheets().add(getClass().getResource("/App.css").toExternalForm());
    primaryStage.setScene(scene);
    primaryStage.show();
  }

  private static void tabsChanged(Change<? extends Tab> c) {
    while (c.next()) {
      if (c.wasRemoved()) {
        for (Tab removed : c.getRemoved()) {
          removed.getStyleClass().removeAll("first-tab", "last-tab");
        }
      }
    }

    ObservableList<? extends Tab> tabs = c.getList();
    if (tabs.size() == 1) {
      Tab tab = tabs.get(0);
      addStyleClassIfAbsent(tab, "first-tab");
      addStyleClassIfAbsent(tab, "last-tab");
    } else if (!tabs.isEmpty()) {
      Tab first = tabs.get(0);
      addStyleClassIfAbsent(first, "first-tab");
      first.getStyleClass().remove("last-tab");

      Tab last = tabs.get(tabs.size() - 1);
      addStyleClassIfAbsent(last, "last-tab");
      last.getStyleClass().remove("first-tab");

      for (Tab middle : tabs.subList(1, tabs.size() - 1)) {
        middle.getStyleClass().removeAll("first-tab", "last-tab");
      }
    }
  }

  private static void addStyleClassIfAbsent(Styleable styleable, String styleClass) {
    ObservableList<String> styleClasses = styleable.getStyleClass();
    if (!styleClasses.contains(styleClass)) {
      styleClasses.add(styleClass);
    }
  }
}

App.css:

.first-tab,
.last-tab {
  -fx-base: pink;
}

The -fx-base is a looked-up color added by modena.css (i.e. the default user-agent stylesheet in JavaFX 8+). I set that instead of the -fx-background-color property in order to hook into the "theming" provided by modena.css. In the above example you can see the styles change dynamically by reordering the tabs via mouse-dragging (JavaFX 10+) or by closing tabs.

Note I would have preferred to use PseudoClass for this. However, from what I can tell, the Tab class does not allow you to (de)activate pseudo-classes directly. The way it handles the :selected pseudo-class is internal to the TabPane's default skin, meaning we can't access that same functionality reliably for our purposes from the outside.

Slaw
  • 37,820
  • 8
  • 53
  • 80