在 JavaFX 2 和 8 之间选择 TableView 期间焦点行为发生变化

Change in focus behavior during TableView selection between JavaFX 2 and 8

我有一个管理文档列表的应用程序。在 TableView 中维护的文档的一个视图显示作者、标题等。其他视图具有出版商、页数、注释、摘要等内容,具体取决于文档的类型 selected。用户通过单击 TableView 中的新行select创建活动文档。

当用户在各种视图中编辑文档信息时,随着各个控件失去焦点,更改将提交到数据库。这工作正常,直到尝试从 Java 7/JavaFX 2 切换到 8.

这里有一个SSCCE来说明问题

package focusproblem;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import static javafx.collections.FXCollections.observableArrayList;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class FocusProblem extends Application {

    private TextArea notesArea;
    private TableView docTable;

    private ObservableList<Doc> initDocs() {
        ObservableList<Doc> docList = observableArrayList();
        docList.add(new Doc("Harper Lee", "To Kill a Mockbird",
                "Some notes on mockingbirds"));
        docList.add(new Doc("John Steinbeck", "Of Mice and Men",
                "Some notes about mice"));
        docList.add(new Doc("Lewis Carroll", "Jabberwock",
                "Some notes about jabberwocks"));
        return docList;
    }

    private Parent initGui(ObservableList<Doc> d) {
        notesArea = new TextArea();
        notesArea.setId("notesArea");
        notesArea.setPromptText("Add notes here");
        notesArea.focusedProperty().addListener(new FocusPropertyChangeListener());

        TableColumn<Doc, String> authorCol = new TableColumn<>("Author");
        authorCol.setCellValueFactory(new PropertyValueFactory<Doc, String>("author"));
        authorCol.setMinWidth(100.0d);
        TableColumn<Doc, String> titleCol = new TableColumn<>("Title");
        titleCol.setCellValueFactory(new PropertyValueFactory<Doc, String>("title"));
        titleCol.setMinWidth(250.0d);

        docTable = new TableView<>(d);
        docTable.setPrefHeight(200.0d);
        docTable.getColumns().addAll(authorCol, titleCol);
        docTable.getSelectionModel().selectedItemProperty().addListener(new SelectionChangeListener());
        VBox vb = new VBox();
        vb.getChildren().addAll(docTable, notesArea);
        return vb;
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Focus Problem");
        primaryStage.setScene(new Scene(initGui(initDocs())));
        primaryStage.show();
    }

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

    public class SelectionChangeListener implements ChangeListener<Doc> {

        @Override
        public void changed(ObservableValue<? extends Doc> observable, Doc oldDoc, Doc newDoc) {
            System.out.println("Changing selected row");
            if (oldDoc != null) {
                notesArea.textProperty().unbindBidirectional(oldDoc.notesProperty());
            }
            if (newDoc != null) {
                notesArea.setText(newDoc.getNotes());
                newDoc.notesProperty().bindBidirectional(notesArea.textProperty());
            }
        }
    }

    public class FocusPropertyChangeListener implements ChangeListener<Boolean> {

        @Override
        public void changed(ObservableValue<? extends Boolean> ov,
                Boolean oldb, Boolean newb) {
            if (ov instanceof ReadOnlyBooleanProperty) {
                Object obj = ((ReadOnlyBooleanProperty) ov).getBean();
                if (obj instanceof TextArea) {
                    TextArea ta = (TextArea) obj;
                    if (ta.isVisible() && !ta.isDisabled()) {
                        boolean b = (newb != null && newb == true);
                        if (b) {
                            System.out.println(ta.getId() + " gained focus");
                        } else {
                            System.out.println(ta.getId() + " lost focus");
                            Doc d = (Doc) docTable.getSelectionModel().getSelectedItem();
                            if (d != null) {
                                System.out.println("  Need to update db entry for '"
                                        + d.getTitle() + "' with '" + ta.getText() + "'");
                            }
                        }
                    }
                }
            }
        }
    }

    public class Doc {

        private final SimpleStringProperty author;
        private final SimpleStringProperty title;
        private final SimpleStringProperty notes;

        public Doc(String author, String title, String notes) {
            this.author = new SimpleStringProperty(this, "author", author);
            this.title = new SimpleStringProperty(this, "title", title);
            this.notes = new SimpleStringProperty(this, "notes", notes);
        }

        public void setAuthor(String value) {
            author.set(value);
        }

        public String getAuthor() {
            System.out.println("Trying to get author");
            return author.get();
        }

        public StringProperty authorProperty() {
            return author;
        }

        public void setTitle(String value) {
            title.set(value);
        }

        public String getTitle() {
            return title.get();
        }

        public StringProperty titleProperty() {
            return title;
        }

        public void setNotes(String value) {
            notes.set(value);
        }

        public String getNotes() {
            return notes.get();
        }

        public StringProperty notesProperty() {
            return notes;
        }
    }
}

当 运行 使用任一版本的 Java 编译后,程序看起来像这样(在 Windows 上):

要演示该问题,请执行以下步骤:

程序用Java7编译时,控制台输出为:

Changing selected row
notesArea gained focus
notesArea lost focus
  Need to update db entry for 'Of Mice and Men' with 'Some lengthy notes about mice'
Changing selected row

符合预期。数据库已更新为正确的信息。

程序用Java8编译时,控制台输出为:

Changing selected row
notesArea gained focus
Changing selected row
notesArea lost focus
  Need to update db entry for 'Jabberwock' with 'Some notes about jabberwocks'

在此输出中,更新了错误的文档,并且未将真正的更改写入数据库。看起来 TableView 在 TextArea 失去焦点之前获得了焦点。

这是 Java 8 的错误或预期行为吗?任何已知的解决方法?还是我只是个笨蛋才这样做?

是的,我可以重现此行为,但我既不会将其称为错误,也不会尝试 fiddle 使用焦点系统。相反,你为什么不重写你的 SelectionChangeListener - 没有必要关注 TextArea:

public class SelectionChangeListener implements ChangeListener<Doc> {

    @Override
    public void changed(ObservableValue<? extends Doc> observable, Doc oldDoc, Doc newDoc) {
        System.out.println("Changing selected row");
        if (oldDoc != null) {
            System.out.println("  Need to update db entry for '"
                    + oldDoc.getTitle() + "' with '" + oldDoc.getNotes() + "'");
            notesArea.textProperty().unbindBidirectional(oldDoc.notesProperty());
        }
        if (newDoc != null) {
            notesArea.setText(newDoc.getNotes());
            newDoc.notesProperty().bindBidirectional(notesArea.textProperty());
        }
    }
}

这会为您的测试用例生成以下输出:

Changing selected row
notesArea gained focus
Changing selected row
  Need to update db entry for 'Of Mice and Men' with 'Some lengthy notes about mice'
notesArea lost focus