如何将 StringBinding 和 ReadOnlyProperty 作为通用参数传递?

How to pass StringBinding and ReadOnlyProperty as generic arguments?

我的 JavaFX 代码中有两种方法可用于创建带绑定的 TextField。我必须绑定 ReadOnlyPropertyStringBinding。所以我创建了两个方法签名不同但代码块中的代码相同的方法。

如何简化我的代码(使用泛型?)以处理所有不同的属性(String、Long、Object 等)?

createTextField(knkFile.idProperty().asString(), 1, 0); // ReadOnlyIntegerProperty
createTextField(knkFile.dateProperty().asString(), 1, 1); // ReadOnlyObjectProperty
createTextField(knkFile.fileNameProperty(), 1, 2); // ReadOnlyStringProperty
createTextField(knkFile.lastModifiedProperty().asString(), 1, 5); // ReadOnlyLongProperty
private void createTextField(ReadOnlyProperty property, int column, int row) {
    TextField textField = new TextField();
    textField.textProperty().bind(property);
    this.add(textField, column, row); // add to GridPane
}

private void createTextField(StringBinding binding, int column, int row) {
    TextField textField = new TextField();
    textField.textProperty().bind(binding);
    this.add(textField, column, row);
}

编辑:自己的答案(已弃用 - 请参阅已接受的答案)

我刚刚看到 StringBindingReadOnlyProperty 实现了 ObservableValue。所以我现在只需要这个方法:

private void createTextField(ObservableValue property, int column, int row) {
    TextField textField = new TextField();
    textField.textProperty().bind(property);
    this.add(textField, column, row);
}

解决方法:使用StringExpression

传递给您的 createTextField 方法的所有值都是 StringExpressions,因此您可以创建一个将 StringExpression 作为参数的方法。

您可以这样编写示例方法:

private void createTextField(
        StringExpression property,
        int column,
        int row
) {
    TextField textField = new TextField();
    textField.textProperty().bind(property);
    form.add(textField, column, row);
}  

使用 StringExpression 优于使用更新问题中定义的 ObservableValue

说明

文本字段必须绑定到字符串形式的 ObservableValue(类型 ObservableValue<? extends String>)。

如果您在参数的类型规范中不使用泛型而只使用原始类型(不包括 <> 的部分),那么您将失去类型安全性,因此 compile-time 类型不进行检查。例如,编译器将允许您将 Integer-based 而不是 String-based ObservableValue 传递到方法中。如果这样做,在执行时,由于类型不匹配,您会收到运行时错误,因为无法将 Integer 直接视为 String。

A StringExpression 实现了 ObservableValue<String>,这意味着文本字段可以直接绑定到它并且保留编译时的类型安全性。

可执行示例

import javafx.application.Application;
import javafx.beans.binding.StringExpression;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

import java.time.Instant;

public class BindSample extends Application {
    private final FileModel fileModel = new FileModel(
            1, Instant.now(), "test.txt", Instant.now().toEpochMilli()
    );

    private final GridPane form = new GridPane();

    @Override
    public void start(Stage stage) {
        form.setHgap(5);
        form.setVgap(5);
        form.setPadding(new Insets(10));

        form.addColumn(0,
                new Label("Id"),
                new Label("Date"),
                new Label("FileName"),
                new Label(),
                new Label(),
                new Label("Last Modified")
        );

        createTextField(fileModel.idProperty().asString(), 1, 0); // ReadOnlyIntegerProperty
        createTextField(fileModel.dateProperty().asString(), 1, 1); // ReadOnlyObjectProperty
        createTextField(fileModel.fileNameProperty(), 1, 2); // ReadOnlyStringProperty
        createTextField(fileModel.lastModifiedProperty().asString(), 1, 5); // ReadOnlyLongProperty

        stage.setScene(new Scene(form));
        stage.show();
    }

    private void createTextField(
            StringExpression property,
            int column,
            int row
    ) {
        TextField textField = new TextField();
        textField.textProperty().bind(property);
        form.add(textField, column, row);
    }

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

    private static class FileModel {
        private final ReadOnlyIntegerWrapper id;
        private final ReadOnlyObjectWrapper<Instant> date;
        private final ReadOnlyStringWrapper fileName;
        private final ReadOnlyLongWrapper lastModified;

        public FileModel(int id, Instant date, String fileName, long lastModified) {
            this.id = new ReadOnlyIntegerWrapper(id);
            this.date = new ReadOnlyObjectWrapper<>(date);
            this.fileName = new ReadOnlyStringWrapper(fileName);
            this.lastModified = new ReadOnlyLongWrapper(lastModified);
        }

        public int getId() {
            return id.get();
        }

        public ReadOnlyIntegerProperty idProperty() {
            return id.getReadOnlyProperty();
        }

        public Instant getDate() {
            return date.get();
        }

        public ReadOnlyObjectProperty<Instant> dateProperty() {
            return date.getReadOnlyProperty();
        }

        public String getFileName() {
            return fileName.get();
        }

        public ReadOnlyStringProperty fileNameProperty() {
            return fileName.getReadOnlyProperty();
        }

        public long getLastModified() {
            return lastModified.get();
        }

        public ReadOnlyLongProperty lastModifiedProperty() {
            return lastModified.getReadOnlyProperty();
        }
    }
}

次要实施说明

  1. 该示例在演示绑定以匹配您的问题示例代码时使用 ReadOnlyPropertys。但是对于这个简单的例子,如果模型对象是不可变的,那么可以使用没有绑定的记录。如果您知道数据永远不会改变,这可以简化逻辑。

  2. 在示例中,我使用 Instant 将日期表示为精确的时间时刻,但您可以使用 ZonedDateTime 以获得更好的日期表示(或 LocalDateTimeLocalDate 如果歧义是好的),请参阅 如果你想理解这个。