cssjavafxtableview

JavaFx how to use default and custom column-header css style in tableview?


I want to change the default behavior with a custom css for the header text positioning. However, it must be possible to change the positioning of individual columns.

However, it does not work.

My new default.It should be aligned to the left instead of in the middle. This also works with the following css.

/*Css*/
.table-view .column-header .label{
    -fx-alignment:CENTER_LEFT;
    -fx-padding: 2 0 2 10;
}

However, I want to be able to overwrite this behavior for individual columns. E.g. the column heading alignment centered or to the right.

I have tried this with the following code.

/*Code*/
myColumn.getStyleClass().add("ds-column-header-right");
/*Css*/
.ds-column-header-right .label{
    -fx-alignment: CENTER_RIGHT;
    -fx-padding: 2 10 2 0;
}

If only this custom css (ds-column-header-right) definition exists it works and the header heading is aligned to the right.

However, if both Css definitions exist, the header text is always aligned to the left, even if I have explicitly assigned the style for right alignment to certain columns.

How can I solve the problem without explicitly assigning each column to a style?

This was tested under Java 17, JavaFx 21.0.2

Here a full (minimum) example:

//Main.java
package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;


public class Main extends Application {
    @Override
    public void start(final Stage primaryStage) {
        try {
            TableView<Person> tableView = new TableView<>();

            TableColumn<Person, String> nameColumn = new TableColumn<>("Name");

            TableColumn<Person, String> emailColumn = new TableColumn<>("Email");
            emailColumn.getStyleClass().add("ds-column-header-right");

            tableView.getColumns().add(nameColumn);
            tableView.getColumns().add(emailColumn);

            VBox vbox = new VBox(tableView);

            Scene scene = new Scene(vbox, 400, 400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(final String[] args) {
        launch(args);
    }
}
//Person.java
package application;

import javafx.beans.property.SimpleStringProperty;

public class Person {
    private final SimpleStringProperty name;
    private final SimpleStringProperty email;

    public Person(final String name, final String email) {
        this.name = new SimpleStringProperty(name);
        this.email = new SimpleStringProperty(email);
    }

    public String getName() {
        return name.get();
    }

    public void setName(final String fName) {
        name.set(fName);
    }

    public String getEmail() {
        return email.get();
    }

    public void setEmail(final String fEmail) {
        email.set(fEmail);
    }
}
/*application.css*/
.table-view .column-header .label{
    -fx-alignment:CENTER_LEFT;
    -fx-text-fill: blue;
}

.ds-column-header-right .label{
    -fx-alignment: CENTER_RIGHT;
    -fx-text-fill: red;
}

Solution

  • As I understand it (though I am having trouble finding this documented at the time of writing), when multiple CSS rules apply to a given Node the rule with the "most specific" selector takes priority. If selectors have equal specificity, the one that is applied most recently takes priority. "Specificity" is not clearly defined here, so some trial and error may be necessary.

    In your CSS, the "default" column alignment is specified with the selector

    .table-view .column-header .label {
        /* ... */
    }
    

    which is a selector defined as a three-level hierarchy of CSS class names (i.e. it applies to a node with class label which is a descendent of a node with class column-header which is a descendent of a node with class table-view).

    On the other hand, your "custom" CSS rule is defined with a two-level hierarchy, which is considered "less specific":

    .ds-column-header-right .label {
        /* ... */
    }
    

    So the "more specific" selector always takes priority.

    There are a couple of fixes that appear to work. Adding the table-view to the selector hierarchy for the "custom" CSS rule makes them "equal specificity", and so the one posted last will take priority. Thus the following works:

    .table-view .column-header .label{
        -fx-alignment:CENTER_LEFT;
        -fx-text-fill: blue;
    }
    
    .table-view .ds-column-header-right .label {
        -fx-alignment: CENTER_RIGHT;
        -fx-text-fill: red;
    }
    

    Note that reversing the order in this case will make the "default" (blue) rule take priority in all cases.

    Also note that the column header in your "custom" table column has two CSS classes: the default column-header class and the custom ds-header-right CSS class. You can select a node with two CSS classes by concatenating the two class selectors with no space between them. So the selector

    .column-header.ds-column-header-right .label {
        /* ... */
    }
    

    will match your custom column header, and since it has three class selectors it is also of equal specificity as the selector for the default rule.

    Thus this also works:

    .table-view .column-header .label{
        -fx-alignment:CENTER_LEFT;
        -fx-text-fill: blue;
    }
    
    .column-header.ds-column-header-right .label {
        -fx-alignment: CENTER_RIGHT;
        -fx-text-fill: red;
    }
    

    but again, reversing the order will cause the default rule to apply in all cases.

    Finally, note that if you use

    .table-view .column-header.ds-column-header-right .label {
        /* ... *.
    }
    

    (which selects a node with CSS class label, which is a descendent of a node with both CSS class column-header and CSS class ds-column-header, which in turn is a descendent of a node with CSS class table-view), this is a selector with four class names and is more specific than the selector for your "default" CSS rule. Thus this selector will work regardless of order, and either of the following work as desired:

    .table-view .column-header .label{
        -fx-alignment:CENTER_LEFT;
        -fx-text-fill: blue;
    }
    
    .table-view .column-header.ds-column-header-right .label {
        -fx-alignment: CENTER_RIGHT;
        -fx-text-fill: red;
    }
    

    or

    .table-view .column-header.ds-column-header-right .label {
        -fx-alignment: CENTER_RIGHT;
        -fx-text-fill: red;
    }
    
    .table-view .column-header .label{
        -fx-alignment:CENTER_LEFT;
        -fx-text-fill: blue;
    }
    

    Descendency here is a bit hard to figure because, strictly speaking, the TableColumn itself is not part of the same TableView substructure as the actual column headers. (See the "Substructure" section in the documentation.) The internal code for the TableColumn skin propagates all style classes both to the column header and to cells in the column.