初始化后更改 TableView 中的文本字体

Change font of text in TableView after initialize

我想更改 TableView 中文本的字体,但是当我这样尝试时,它没有应用字体,因为它找不到任何节点:

tableView.lookupAll(".table-row-cell").forEach(cell -> {
    applyFont(cell, MainFont.LIGHT, FontStyle.SUBHEAD);
});

其中 applyFont() 是自定义函数。我认为它与未初始化的 TableView 有关,所以我尝试将其包装在 Platform.runLater() 中,但这也不起作用。

但是,唯一有效的方法是将此代码放入侦听器(选择、悬停等):

tableView.hoverProperty().addListener(change -> {

    tableView.lookupAll(".table-row-cell").forEach(cell -> {
        applyFont(cell, MainFont.LIGHT, FontStyle.SUBHEAD);
    });

});

当我现在将鼠标悬停在 TableView 上时,字体会发生变化。但我不明白为什么它不适用于 Platform.runLater().

此代码位于我的应用程序中每个屏幕的超类中,并且是 运行 在子屏幕已构建但尚未显示之后。

我在这里错过了什么?非常感谢任何帮助!

注意:我无法在我的案例中使用 CSS 解决方案。

问题

您想设置 TableView 的字体样式。您要为 rows/cells.

设置字体样式

解决方案

您可以使用 class TableView 的方法 setStyle()。使用 JavaFX CSS Reference 您可以看到哪些设置是可能的。所以这是 CSS 但没有额外的 CSS 文件,所以你可以用任何方式做到这一点。

更改 TableView 单元格的字体样式

一个 Minimal, Complete, and Verifiable example 看起来像这样:

import java.util.Set;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableFontStyle extends Application {

  @Override
  public void start(Stage primaryStage) {
    ObservableList<Person> persons
            = FXCollections.observableArrayList(
                    new Person("Sir", "Tobey"),
                    new Person("Admiral", "von Schneider"),
                    new Person("Mr.", "Pommeroy"),
                    new Person("Mr.", "Winterbottom"));

    TableView<Person> tableView = new TableView<>(persons);

    TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
    firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
    TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
    lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));

    tableView.getColumns().setAll(firstNameCol, lastNameCol);
    tableView.getSelectionModel().clearAndSelect(0);

    Button btn = new Button("Change Font Style");
    btn.setOnAction((e) -> {
      Set<Node> cells = tableView.lookupAll(".table-cell");
      cells.forEach((c) -> {
        c.setStyle("-fx-font-weight:lighter;-fx-font-style:italic;");
      });
    });

    VBox root = new VBox();
    root.getChildren().addAll(tableView, btn);

    Scene scene = new Scene(root, 300, 250);
    primaryStage.setTitle("Font Table");
    primaryStage.setScene(scene);
    primaryStage.show();
  }

  /**
   * @param args the command line arguments
   */
  public static void main(String[] args) {
    launch(args);
  }

  public class Person {

    private final StringProperty firstName
            = new SimpleStringProperty(this, "firstName");

    public void setFirstName(String value) {
      firstNameProperty().set(value);
    }

    public String getFirstName() {
      return firstNameProperty().get();
    }

    public StringProperty firstNameProperty() {
      return firstName;
    }

    private final StringProperty lastName
            = new SimpleStringProperty(this, "lastName");

    ;

    public void setLastName(String value) {
      lastNameProperty().set(value);
    }

    public String getLastName() {
      return lastNameProperty().get();
    }

    public StringProperty lastNameProperty() {
      return lastName;
    }

    public Person(String firstName, String lastName) {
      this.firstName.set(firstName);
      this.lastName.set(lastName);
    }
  }
}

工作应用程序将如下所示:

我发现 CSS 查找本质上是不可行的 table,我的建议是尽可能避免使用它们。除非已应用 CSS,否则它们将找不到任何节点,这通常发生在第一帧渲染脉冲上。此外,对于此用例,您需要确保已创建 table 行和单元格。当然,仅将查找包装在 Platform.runLater(...) 中并不能保证是否满足这些条件。此外,它们不是类型安全的并且依赖于字符串绑定,因此有许多可能会失败但编译器未检查的事情。

对于此处的用例,只需设置 table 行的样式就足够了。您可以直接使用 rowFactory 执行此操作。为了以后能够更新样式,只需创建一个 StringProperty 来保存样式,然后将 TableRowstyleProperty 绑定到这个:

StringProperty style = new SimpleStringProperty();

// ...

table.setRowFatory(tv -> {
    TableRow<MyDataType> row = new TableRow<>();
    row.styleProperty().bind(style);
    return row ;
});

现在只需使用有效的 CSS 样式调用 style.set(...) 即可更新行的样式(以及行包含的所有单元格)。这完全避免了查找的需要。

SSCCE:

import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;

public class ConfigurableFontTable extends Application {

    private StringProperty fontFamily = new SimpleStringProperty(Font.getDefault().getFamily());
    private ObjectProperty<FontWeight> fontWeight = new SimpleObjectProperty<>(FontWeight.NORMAL);
    private BooleanProperty italic = new SimpleBooleanProperty();
    private IntegerProperty fontSize = new SimpleIntegerProperty((int)Font.getDefault().getSize());

    private ObjectProperty<Color> fontFill = new SimpleObjectProperty<>(Color.BLACK);

    private StringProperty style = new SimpleStringProperty();

    @Override
    public void start(Stage primaryStage) {
        style.bind(Bindings.createStringBinding(() -> String.format(
                "-fx-font-family: %s;\n"
                + "-fx-font-weight: %d;\n"
                + "-fx-font-style: %s;\n"
                + "-fx-font-size: %d;\n"
                + "-fx-text-background-color: rgba(%d, %d, %d, %f);\n",
                fontFamily.get(),
                fontWeight.get().getWeight(),
                italic.get()?"italic":"normal",
                fontSize.get(),
                (int)(255 * fontFill.get().getRed()),
                (int)(255 * fontFill.get().getGreen()),
                (int)(255 * fontFill.get().getBlue()),
                fontFill.get().getOpacity()
            ),
            fontFamily,
            fontWeight,
            italic,
            fontSize,
            fontFill
        ));

        TableView<Person> table = new TableView<>();
        table.setRowFactory(tv -> {
            TableRow<Person> row = new TableRow<>();
            row.styleProperty().bind(style);
            return row ;
        });

        table.getColumns().add(column("First Name", Person::firstNameProperty));
        table.getColumns().add(column("Last Name", Person::lastNameProperty));
        table.getColumns().add(column("Email", Person::emailProperty));

        table.getItems().addAll(
                new Person("Jacob", "Smith", "jacob.smith@example.com"),
                new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
                new Person("Ethan", "Williams", "ethan.williams@example.com"),
                new Person("Emma", "Jones", "emma.jones@example.com"),
                new Person("Michael", "Brown", "michael.brown@example.com")
        );

        Button changeStyleButton = new Button("Change style...");
        changeStyleButton.setOnAction(e -> showChangeStyleDialog(primaryStage));

        BorderPane root = new BorderPane(table, null, null, changeStyleButton, null);
        BorderPane.setAlignment(changeStyleButton, Pos.CENTER);
        BorderPane.setMargin(changeStyleButton, new Insets(10));

        primaryStage.setScene(new Scene(root, 800, 600));
        primaryStage.show();
    }

    private void showChangeStyleDialog(Stage owner) {
        GridPane root = new GridPane();
        root.setHgap(5);
        root.setVgap(5);
        root.setPadding(new Insets(10));

        ColumnConstraints leftCol = new ColumnConstraints();
        leftCol.setHalignment(HPos.RIGHT);
        leftCol.setHgrow(Priority.NEVER);

        ColumnConstraints rightCol = new ColumnConstraints();

        root.getColumnConstraints().addAll(leftCol, rightCol);

        ComboBox<String> fontFamilyChoice = new ComboBox<>();
        fontFamilyChoice.getItems().addAll(Font.getFamilies());
        fontFamilyChoice.setValue(fontFamily.get());

        ComboBox<FontWeight> fontWeightChoice = new ComboBox<>();
        fontWeightChoice.getItems().addAll(FontWeight.values());
        fontWeightChoice.setValue(fontWeight.get());

        CheckBox italicCheckBox = new CheckBox("Italic");
        italicCheckBox.setSelected(italic.get());

        ComboBox<Integer> fontSizeChoice = new ComboBox<>();
        fontSizeChoice.getItems().addAll(4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48);
        fontSizeChoice.setValue(fontSize.get());

        ColorPicker colorPicker = new ColorPicker();
        colorPicker.setValue(fontFill.get());

        root.addRow(0,  new Label("Font:"), fontFamilyChoice);
        root.addRow(1, new Label("Weight:"), fontWeightChoice);
        root.addRow(2, new Label("Size:"), fontSizeChoice);
        root.add(italicCheckBox, 1, 3);
        root.addRow(4, new Label("Text Color:"), colorPicker);

        Stage stage = new Stage();

        Button okButton = new Button("OK");
        okButton.setOnAction(e -> {
            fontFamily.set(fontFamilyChoice.getValue());
            fontWeight.set(fontWeightChoice.getValue());
            fontSize.set(fontSizeChoice.getValue());
            italic.set(italicCheckBox.isSelected());
            fontFill.set(colorPicker.getValue());
            stage.hide();
        });

        Button cancelButton = new Button("Cancel");
        cancelButton.setOnAction(e -> stage.hide());

        HBox buttons = new HBox(5, okButton, cancelButton);
        buttons.setAlignment(Pos.CENTER);
        root.add(buttons, 0, 5, 2, 1);

        stage.initOwner(owner);
        stage.setScene(new Scene(root));
        stage.show();
    }

    private static <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
        TableColumn<S,T> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        return col ;
    }

    public class Person {
        private final StringProperty firstName = new SimpleStringProperty(this,"firstName");
        private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
        private final StringProperty email = new SimpleStringProperty(this, "email");

        public Person(String firstName, String lastName, String email) {
            this.firstName.set(firstName);
            this.lastName.set(lastName);
            this.email.set(email);
        }

        public final StringProperty firstNameProperty() {
            return this.firstName;
        }

        public final String getFirstName() {
            return this.firstNameProperty().get();
        }

        public final void setFirstName(final String firstName) {
            this.firstNameProperty().set(firstName);
        }

        public final StringProperty lastNameProperty() {
            return this.lastName;
        }

        public final String getLastName() {
            return this.lastNameProperty().get();
        }

        public final void setLastName(final String lastName) {
            this.lastNameProperty().set(lastName);
        }

        public final StringProperty emailProperty() {
            return this.email;
        }

        public final String getEmail() {
            return this.emailProperty().get();
        }

        public final void setEmail(final String email) {
            this.emailProperty().set(email);
        }

    }

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