Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX: Apply text color to TableCell using custom style sheet?

JavaFX: How can I apply text color to a TableCell using a custom style sheet?

It works fine, when I use setTextFill() in my CellFactory directly, but I want to apply custom style using an external CSS file. I could prove that my CSS class is applied, since the font becomes bold. The CSS file's font color, however, is not applied.

@Override
protected void updateItem(MyObject item, boolean empty) {
    super.updateItem(item, empty);

    if (null != item) {        
        // EITHER:
        this.getStyleClass().add("styleImportant"); // Does NOT set color.

        // OR:
        this.setTextFill(Color.RED); // Does set color.
    }
    else {
        this.getStyleClass().remove("styleImportant");
    }

}

Style sheet:

.styleImportant {
    -fx-font-weight: bold; /** Does work. */
    -fx-text-fill: red; /** Does NOT work. */
}

It is somehow related to specificity of CSS selectors, but I did not manage to find any valid setup.


Edit: I managed to apply both, a custom text color and background color, using CSS. My implementation now uses a Label that is wrapped in a VBox to make the background color fill the entire table cell. However, I still had some issues with background color not being cleared when removing the custom style.

Is there any better solution than applying a clear style?

colExample.setCellFactory(new Callback<TableColumn<Example, Example>, TableCell<Example, Example>>() {
  @Override
  public TableCell<Example, Example> call(TableColumn<Example, Example> tableColumn) {
     return new TableCell<Example, Example>() {
        private VBox container;
        private Label text;

        // Anonymous constructor
        {
           this.container = new VBox();
           this.text = this.createLabel();

           this.container.getChildren().add(this.text);
           this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
           this.setStyle("-fx-padding: -1 -1 -1 -1;"); // Remove padding from cell

           this.setGraphic(this.container);
        }

        private final Label createLabel() {
           Label label = new Label();

           VBox.setVgrow(label, Priority.ALWAYS);
           label.setMaxWidth(Double.MAX_VALUE);
           label.setMaxHeight(Double.MAX_VALUE);
           label.setAlignment(Pos.CENTER);

           return label;
        }

        @Override
        protected void updateItem(Example example, boolean empty) {
           super.updateItem(example, empty);

           // Reset column styles
           if (null != this.text && null != this.text.getStyleClass()) {
              String[] possibleStyles = new String[] { "styleImportant", "clearStyle" };

              for (String style: possibleStyles) {
                 if (this.text.getStyleClass().contains(style)) {
                    // Will not reset background, even though style is removed?
                    this.text.getStyleClass().remove(style);
                 }
              }

              // Apply reset style to clear background color
              this.text.getStyleClass().add("clearStyle");
           }

           if (null != example) {
              this.text.setText(example.getContent());

              if (example.isImportant()) {
                 this.text.getStyleClass().add("styleImportant");
              }
           }
        }
     };
  }
});

My style sheet:

/** Keep black text color, when user selects row */
.table-row-cell:focused {
   -fx-dark-text-color: #000000;
   -fx-mid-text-color: #000000;
   -fx-light-text-color: #000000;
}

/** Style to reset background color */
.clearStyle {
   -fx-background-color: transparent;
}

/** Style for important cells */
.styleImportant {
   /** Red text color on any background */
   -fx-dark-text-color: #FF0000;
   -fx-mid-text-color: #FF0000;
   -fx-light-text-color: #FF0000;

   -fx-background-color: #FF9999;
}
like image 426
user1438038 Avatar asked Jan 09 '23 10:01

user1438038


2 Answers

As José points out in his answer, if you are setting a graphic in your cell, you (probably) need to apply the css style class to the graphic (depending on what that graphic is). If you are simply calling setText(...) your code should work.

The reason that a Label set as the graphic doesn't inherit -fx-text-fill from the table cell is that the Label also has a setting for -fx-text-fill. In the default stylesheet, both TableCell and Label have this set as follows:

-fx-text-fill: -fx-text-background-color ;

fx-text-background-color is a looked-up color that is defined as a ladder, as follows:

-fx-text-background-color: ladder(
    -fx-background,
    -fx-light-text-color 45%,
    -fx-dark-text-color  46%,
    -fx-dark-text-color  59%,
    -fx-mid-text-color   60%
);

This (fairly complex) setting means that the value of -fx-text-background-color depends on the value of -fx-background. If -fx-background is less than 45% of maximum intensity (i.e. it's dark), the value of -fx-text-background-color is set to -fx-light-text-color. If -fx-background is between 46% and 59% intensity, the value is equal to -fx-drak-text-color. If it is 60% or more, it's set to -fx-mid-text-color. The idea here is that the text color will automatically adjust to the background to remain visible. The values of -fx-dark-text-color, -fx-mid-text-color, and -fx-light-text-color are set to black, a dark gray (#333), and white, respectively.

Since Label does not override the value of -fx-text-background-color, you can achieve what you need by just changing the value of that for your table cell:

.styleImportant {
    -fx-text-background-color: red ;
}

Now this overrides the looked-up color value for the table cell, and since the graphic inside the cell doesn't override that value itself, it inherits it from the cell.

A more sophisticated way to do this is to redefine the -fx-[light|mid|dark]-text-colors. The advantage of this is that the colors will adjust appropriately if you change the background: in particular if the cell is selected you can ensure that the text stays visible:

.styleImportant {
    -fx-light-text-color: white ;
    -fx-mid-text-color:   #c00 ;
    -fx-dark-text-color:  red ;
}
like image 52
James_D Avatar answered Jan 21 '23 12:01

James_D


Since I don't know what type of object is in the cell, I'll use a Label.

This is what you are doing:

@Override
public void start(Stage primaryStage) {
    TableView<Label> table = new TableView<>();
    TableColumn<Label,String> column = new TableColumn<>();
    column.setCellValueFactory(param -> param.getValue().textProperty());
    column.setCellFactory((TableColumn<Label, String> param) -> {
        TableCell<Label, String> cell = new TableCell<Label, String>(){

            @Override
            protected void updateItem(String item, boolean empty){
                super.updateItem(item, empty);
                if(!empty){
                    this.getStyleClass().add("styleImportant");
                    Label label = new Label(item);
                    setGraphic(label);
                }
            }
        };
        return cell;
    });
    table.getColumns().add(column);
    ...
}

This will give you bold text, but black.

If you want to have an object (in my case, a Label) with red text, apply the style sheet to the object:

            @Override
            protected void updateItem(String item, boolean empty){
                super.updateItem(item, empty);
                if(!empty){
                    Label label = new Label(item);
                    label.getStyleClass().add("styleImportant");
                    setGraphic(label);
                }
            }

EDIT

This is the full code of the example:

@Override
public void start(Stage primaryStage) {
    TableView<Label> table = new TableView<>();

    ObservableList<Label> data = FXCollections.observableArrayList(
             new Label("Some Text"), new Label("Some More Text"));

    TableColumn<Label,String> column = new TableColumn<>("Column");
    column.setCellValueFactory(param -> param.getValue().textProperty());
    column.setCellFactory((TableColumn<Label, String> param) -> {
        TableCell<Label, String> cell = new TableCell<Label, String>(){

            @Override
            protected void updateItem(String value, boolean empty){
                super.updateItem(value, empty);

                if(!empty){
                    Label label = new Label(value);
                    label.getStyleClass().add("styleImportant");
                    setGraphic(label);
                } else {
                    setGraphic(null);
                } 
            }
        };
        return cell;
    });
    column.setPrefWidth(100);
    table.getColumns().add(column);

    table.setItems(data);

    Scene scene = new Scene(table, 300, 250);
    scene.getStylesheets().add(getClass().getResource("table.css").toExternalForm());
    primaryStage.setScene(scene);
    primaryStage.show();

}

where in table.css:

.styleImportant {
    -fx-font-weight: bold;
    -fx-text-fill: red;
}

Running this short sample should look like this:

Styled label

like image 31
José Pereda Avatar answered Jan 21 '23 14:01

José Pereda