JavaFx: можно ли использовать привязку данных (текстовое поле) в производственном режиме?

Пользуюсь java 8.0.45. Я реализовал свое первое приложение javafx (очень простое) с привязкой данных. Однако ожидание от пользовательского ввода-> pojo, похоже, работает с ошибками. Проверял около 200 раз. Я ввел новые значения в текстовые поля и после этого проверил значения модели. Тот же код, то же мое поведение. Иногда все работает нормально (в большинстве случаев - около 80-90%) иногда значение модели! = Значение текстового поля. Я заметил следующее. Связывание данных для некоторого определенного текстового поля работает, работает, а затем в какой-то момент эта привязка перестает работать, и все новые значения для этого определенного текстового поля не передаются в модель. Никаких исключений. Никаких предупреждений. Ничего такого. Просто привязка не работает.

У меня есть 4 текстовых файла, которые создаются через fxml. Два для струнного типа модели. Один для целого числа. Один для bigdecimal. Проблема возникает со всеми этими полями (иногда с одним, иногда с несколькими). Поскольку мои числовые поля могут иметь нулевые значения, я использую, например, PropertyObject, но не IntegerProperty (так советовали люди из openjfx).

Так это ошибка JavaFx или что? P.S. Я использую felix osgi, weld cdi и pax - не знаю, имеет ли это значение ...

Мой код следующий:

DTO - модель POJO

public class Task {

    private String name;

    private Integer order;

    private BigDecimal weight;

    private String comment;

    private final PropertyChangeSupport propertyChangeSupport;

    public Task() {
        this.propertyChangeSupport = new PropertyChangeSupport(this);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        String pv = this.name ;
        this.name = name;
        propertyChangeSupport.firePropertyChange("name", pv, name);
    }

    public Integer getOrder() {
        return order;
    }

    public void setOrder(Integer order) {
        Integer pv = this.order;
        this.order = order;
        propertyChangeSupport.firePropertyChange("order", pv, this.order);
    }

    public BigDecimal getWeight() {
        return weight;
    }

    public void setWeight(BigDecimal weight) {
        BigDecimal pv = this.weight;
        this.weight = weight;
        propertyChangeSupport.firePropertyChange("weight", pv, weight);
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        String pv = this.comment;
        this.comment = comment;
        propertyChangeSupport.firePropertyChange("comment", pv, this.comment);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

}

Адаптер

public class TaskAdapter {

    private StringProperty nameProperty;

    private ObjectProperty<Integer> orderProperty;

    private ObjectProperty<BigDecimal> weightProperty;

    private StringProperty commentProperty;

      public TaskAdapter(Task task) {
        try {
            nameProperty=new JavaBeanStringPropertyBuilder().bean(task).name("name").build();
            orderProperty=new JavaBeanObjectPropertyBuilder<Integer>().bean(task).name("order").build();
            weightProperty=new JavaBeanObjectPropertyBuilder<BigDecimal>().bean(task).name("weight").build();
            commentProperty=new JavaBeanStringPropertyBuilder().bean(task).name("comment").build();
        } catch (NoSuchMethodException ex) {
            Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
        }
    }

    public StringProperty getNameProperty() {
        return nameProperty;
    }

    public ObjectProperty<Integer> getOrderProperty() {
        return orderProperty;
    }

    public ObjectProperty<BigDecimal> getWeightProperty() {
        return weightProperty;
    }

    public StringProperty getCommentProperty() {
        return commentProperty;
    }

}

Конвертер BigDecimal

public class SimpleBigDecimalStringConverter extends StringConverter<BigDecimal>{

    @Override
    public String toString(BigDecimal i) {
        if (i == null) {
            return "" ;
        } else {
            return i.toString();
        }
    }

    @Override
    public BigDecimal fromString(String string) {
        if (string.trim().length() == 0) {
            return null ;
        } else {
            try {
                return new BigDecimal(string);
            } catch (NumberFormatException nfe) {
                return null ;
            } 
        }
    }
}

IntegerConverter

public class SimpleIntegerStringConverter extends StringConverter<Integer>{

    @Override
    public String toString(Integer i) {
        if (i == null) {
            return "" ;
        } else {
            return i.toString();
        }
    }

    @Override
    public Integer fromString(String string) {
        if (string.trim().length() == 0) {
            return null ;
        } else {
            try {
                return Integer.valueOf(string);
            } catch (NumberFormatException nfe) {
                return null ;
            } 
        }
    }
}

Инициализирующий код

Task task=new Task();
TaskAdapter adapter=new TaskAdapter(task);
nameTextField.textProperty().bindBidirectional(adapter.getNameProperty());
orderTextField.textProperty().bindBidirectional(adapter.getOrderProperty(),new SimpleIntegerStringConverter());
weightTextField.textProperty().bindBidirectional(adapter.getWeightProperty(),new BigDecimalStringConverter());
commentTextField.textProperty().bindBidirectional(adapter.getCommentProperty());

person Community    schedule 13.06.2015    source источник


Ответы (1)


Что происходит

Привязки JavaFX используют WeakChangeListener за сцены для реализации привязки. Это означает, что сама привязка может быть обработана сборщиком мусора, если в области нет других ссылок на нее. В вашем коде adapter определяется как локальная переменная, поэтому она преждевременно собирает мусор в произвольный момент при запуске gc.

Демо

Вот демонстрация вашего кода, показывающая проблему. Он имеет те же текстовые поля, которые вы определяете, плюс две кнопки. Одна кнопка выгружает значение task на консоль, другая заставляет запускать сборщик мусора. Вы увидите, что привязка перестает работать, как только вы запустите gc.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.converter.BigDecimalStringConverter;

public class POJOBindingExample extends Application {

    private TextField nameTextField = new TextField();
    private TextField orderTextField = new TextField();
    private TextField weightTextField = new TextField();
    private TextField commentTextField = new TextField();

    @Override
    public void start(Stage primaryStage) {
        Task task = new Task();
        TaskAdapter adapter = new TaskAdapter(task);
        nameTextField.textProperty().bindBidirectional(adapter.getNameProperty());
        orderTextField.textProperty().bindBidirectional(adapter.getOrderProperty(),new SimpleIntegerStringConverter());
        weightTextField.textProperty().bindBidirectional(adapter.getWeightProperty(),new BigDecimalStringConverter());
        commentTextField.textProperty().bindBidirectional(adapter.getCommentProperty());

        GridPane grid = new GridPane();
        grid.addRow(0, new Label("Name:"), nameTextField);
        grid.addRow(1, new Label("Order:"), orderTextField);
        grid.addRow(2, new Label("Weight:"), weightTextField);
        grid.addRow(3, new Label("Comment:"), commentTextField);

        Button showButton = new Button("Show Task");
        showButton.setOnAction(e -> {
            System.out.println(task.getName());
            System.out.println(task.getOrder());
            System.out.println(task.getWeight());
            System.out.println(task.getComment());
            System.out.println();
        });

        Button gcButton = new Button("Run GC");
        gcButton.setOnAction(e -> System.gc());

        HBox buttons = new HBox(10, showButton, gcButton);

        BorderPane.setAlignment(grid, Pos.CENTER);
        BorderPane.setAlignment(buttons, Pos.CENTER);
        BorderPane.setMargin(grid, new Insets(10));
        BorderPane.setMargin(buttons, new Insets(10));

        BorderPane root = new BorderPane(grid, null, null, buttons, null);

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

Исправить

Чтобы решить эту проблему, убедитесь, что ссылка на TaskAdapter сохраняется столько, сколько вам нужно. В приведенном выше коде, если вы переместите ссылку на TaskAdapter так, чтобы это было поле экземпляра, все будет работать, как требуется:

public class POJOBindingExample extends Application {

    private TextField nameTextField = new TextField();
    private TextField orderTextField = new TextField();
    private TextField weightTextField = new TextField();
    private TextField commentTextField = new TextField();
    private TaskAdapter adapter;

    @Override
    public void start(Stage primaryStage) {
        Task task = new Task();
        adapter = new TaskAdapter(task);

        // ... etc
    }
}

Возможно, вам будет интересно прочитать блог Томаса Микулы, хотя я не думаю, что вы можете использовать его библиотеку напрямую для реализации привязки к POJO.

person James_D    schedule 13.06.2015