javajavafxjavafx-css

Is there a way to add a style class to the popup of a specific ComboBox?


I want to have a special style class for a ComboBox that I could reuse. For example, I want to create a class yellowed that will provide yellow background. This is my code:

Java:

public class NewMain extends Application {

    @Override
    public void start(Stage primaryStage) {
        ComboBox<String> comboBox = new ComboBox<>();
        comboBox.getItems().addAll("Option 1", "Option 2", "Option 3");
        comboBox.getStyleClass().add("yellowed");

        VBox vbox = new VBox(comboBox);
        Scene scene = new Scene(vbox, 400, 300);
        scene.getStylesheets().add(NewMain.class.getResource("test.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

CSS:

.combo-box.yellowed {
    -fx-background-color: yellow;
}

.combo-box-popup.yellowed > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell {
    -fx-background-color: yellow;
}

The problem is that the popup (of my ComboBox with yellowed style class) that will be shown won't have a yellowed class.

Could anyone say, if there is a way to add a style class to the popup of a specific ComboBox?


Solution

  • The popup is considered a descendant of the ComboBox1. This is documented by the JavaFX CSS Reference Guide:

    ComboBox

    Style class: combo-box

    The ComboBox control has all the properties and pseudo‑classes of ComboBoxBase

    Substructure
    • list-cell — a ListCell instance used to show the selection in the button area of a non-editable ComboBox
    • text-input — a TextField instance used to show the selection and allow input in the button area of an editable ComboBox
    • combo-box-popup — a PopupControl that is displayed when the button is pressed [emphasis added]
      • list-view — a ListView
        • list-cell — a ListCell

    So, all you need to do is:

    .combo-box.yellowed .combo-box-popup .list-cell {
      -fx-background-color: yellow;
    }
    

    Technically, combo-box.yellowed .list-cell is sufficient, but note that will also target the node used to display the actual combo box (i.e., the "button cell", which is also a ListCell). And of course you can make the selector more specific if you want/need to.

    Here's a runnable example:

    import javafx.application.Application;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.ComboBox;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
      // Text blocks require Java 15+
      private static final String STYLESHEET =
          """
          .combo-box.yellow,
          .combo-box.yellow .combo-box-popup .list-cell {
            -fx-background-color: yellow;
          }
    
          .combo-box.red,
          .combo-box.red .combo-box-popup .list-cell {
            -fx-background-color: red;
          }
          """;
    
      @Override
      public void start(Stage primaryStage) {
        var box1 = createComboBox();
        box1.getStyleClass().add("yellow");
    
        var box2 = createComboBox();
        box2.getStyleClass().add("red");
    
        var root = new HBox(10, box1, box2);
        root.setAlignment(Pos.CENTER);
    
        var scene = new Scene(root, 500, 300);
        // Adding stylesheet via data URI requires JavaFX 17+
        scene.getStylesheets().add("data:text/css," + STYLESHEET);
    
        primaryStage.setScene(scene);
        primaryStage.show();
      }
    
      private ComboBox<String> createComboBox() {
        var box = new ComboBox<String>();
        for (int i = 1; i <= 5; i++) {
          box.getItems().add("Option #" + i);
        }
        // The 'getFirst' method requires Java 21+
        box.setValue(box.getItems().getFirst());
        return box;
      }
    
      public static void main(String[] args) {
        launch(Main.class);
      }
    }
    

    Note the example, as written, requires Java 21+ and JavaFX 17+.


    1. For the curious, this is implemented by overriding PopupControl#getStyleableParent() to return the control.