2

I have two methods in my JavaFX-code for creating TextFields with Bindings. I have to bind ReadOnlyProperty or StringBinding. So I created two methods with different method signatures but with the same code in the block.

How can I simplify my code (with generics?) to handle all the different Properties (String, Long, Object etc.)?

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);
}

Edit: own answer (deprecated - see accepted answer)

I just saw that StringBinding and ReadOnlyProperty implement ObservableValue. So I only need this method now:

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

1 Answers1

2

Solution: Use StringExpression

All of the values passed to your createTextField methods are StringExpressions, so you can create a single method that takes a StringExpression as a parameter.

You can write your example method like this:

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

Using a StringExpression is preferable to using an ObservableValue as defined in your updated question.

Explanation

A text field must be bound to an ObservableValue which is a string (type ObservableValue<? extends String>).

If you don't use generics in the type specification for your parameter and just use a raw type (without the part including <>), then you lose type safety, so compile-time type checks are not made. For example, the compiler would allow you to pass an Integer-based rather than String-based ObservableValue into the method. If you did that, on execution, you would get a runtime error due to a type mismatch because an Integer cannot be directly treated as a String.

A StringExpression implements ObservableValue<String>, so that means that a text field can bind to it directly and type safety at compile time is retained.

Executable example

screenshot

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();
        }
    }
}

Minor implementation notes

  1. The example uses ReadOnlyPropertys when demonstrating the binding to match your question sample code. But for this simple example, if the model object were immutable, then a Record without Bindings could be used instead. This can simplify the logic if you know the data never changes.

  2. In the example, I use an Instant to represent a date as a precise moment in time, but you could use a ZonedDateTime for better date representation (or LocalDateTime or LocalDate if the ambiguity was OK), see Java date representations if you want to understand this.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • hmm .. wondering why you over-specify the parameter? ObservableValue should suffice, why narrow it to StringExpression? – kleopatra Jan 25 '22 at 10:12
  • 1
    @kleopatra because I like StringExpression :-). I like the name better and I like the additional functionality it provides, so I would prefer to use it. But, yes, `ObservableValue` would also work fine and allow a wider variety of acceptable inputs if that were more desirable or required. – jewelsea Jan 25 '22 at 12:17
  • thanks for explanation - yeah, we all have personal preferences :) – kleopatra Jan 25 '22 at 12:34