在允许选择更改之前如何验证数据?

时间:2018-10-16 14:59:54

标签: java javafx

在我的应用程序中,我有两个部分:数据列表和一个用于编辑该数据详细信息的部分。用户从ListViewTableView中选择一个项目,该项目的属性将显示在窗口右侧,并且可以在窗口右侧进行编辑。 下面的MCVE将对此进行演示。

我需要做的是在更改ListView中的选择之前验证那些“编辑器”控件的值。


MCVE:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        HBox root = new HBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        // Simple ListView
        ListView<Person> listView = new ListView<>();
        listView.getItems().setAll(
                new Person("Jack", "j@email.com"),
                new Person("Bill", "bill@email.com"),
                new Person("Diane", "dd@email.com")
        );

        // TextFields to edit values
        TextField txtName = new TextField();
        TextField txtEmail = new TextField();

        // Add controls to the root layout
        root.getChildren().addAll(
                listView,
                new VBox() {{
                    getChildren().addAll(
                            new Label("Name:"),
                            txtName,
                            new Label("Email:"),
                            txtEmail);
                }}
        );

        // Add listener to update bindings for the TextFields
        listView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {

            // Unbind previously bound values
            if (oldValue != null) {

                // If name or email are missing, prevent the change
                if (!validate(oldValue))
                    return; // *** This is where I need help as this obviously is not correct *** //

                txtName.textProperty().unbindBidirectional(oldValue.nameProperty());
                txtEmail.textProperty().unbindBidirectional(oldValue.emailProperty());
            }

            // Bind the new values
            if (newValue != null) {
                txtName.textProperty().bindBidirectional(newValue.nameProperty());
                txtEmail.textProperty().bindBidirectional(newValue.emailProperty());

                // Refresh the ListView to show changes
                listView.refresh();
            }

        });

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }

    private boolean validate(Person oldValue) {
        return !(oldValue.getName().trim().isEmpty() || oldValue.getName().trim().isEmpty());
    }
}

class Person {

    private StringProperty name = new SimpleStringProperty();
    private StringProperty email = new SimpleStringProperty();

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

    public String getName() {
        return name.get();
    }

    public void setName(String name) {
        this.name.set(name);
    }

    public StringProperty nameProperty() {
        return name;
    }

    public String getEmail() {
        return email.get();
    }

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

    public StringProperty emailProperty() {
        return email;
    }

    @Override
    public String toString() {
        return name.get();
    }
}


当用户从ListView中选择其他项目时,如何首先验证txtNametxtEmail是否包含有效值?

我一直遵循this Q&A来完全防止选择,但是如果值有效,我找不到找到允许选择更改的方法(同时也更新绑定)。

本质上,在此示例中,我要确保在移至新选择之前TextFields不为空。

3 个答案:

答案 0 :(得分:1)

据我所知,您不能从ChangeListener内部使用此事件,因为它是一个侦听器,它会通知您有关已发生的更改。

但是,如果您不满意,则可以添加鼠标过滤器,侦听鼠标单击,验证此事件内所需的数据并使用它。

  listView.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
        if(!validate(listView.getSelectionModel().getSelectedItem())) event.consume();
    });

我还相信您可以实现自己的MultipleSelectionModel,但这会花费更多时间

答案 1 :(得分:0)

当您的条件无效时,我认为重新选择旧项目将满足您的要求。这样,您就不必担心鼠标和键事件的过滤器。

我将侦听器实现更改为以下内容。它正在按预期方式在您的演示中运行。我唯一关心的是使用Platform.runLater。也许有人可以建议我如何不使用它。

// Add listener to update bindings for the TextFields
        listView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
            boolean proceed = true;
            // Unbind previously bound values
            if (oldValue != null) {
                txtName.textProperty().unbindBidirectional(oldValue.nameProperty());
                txtEmail.textProperty().unbindBidirectional(oldValue.emailProperty());

                // If name or email are missing, prevent the change
                if (validate(oldValue)) {
                    Platform.runLater(() -> listView.getSelectionModel().select(oldValue));
                    proceed = false;
                }
            }

            // Bind the new values
            if (newValue != null && proceed) {
                txtName.textProperty().bindBidirectional(newValue.nameProperty());
                txtEmail.textProperty().bindBidirectional(newValue.emailProperty());
                // Refresh the ListView to show changes
                listView.refresh();
            }
        });

更新:Arrggh ...我刚刚注意到我的答案与链接的答案几乎相似,并且您已经与@Kleopatra进行了讨论。但这会解决您的问题:)

@Kleopatra:根据您对refresh()的评论,您能否建议一种方法来在listView中更新详细信息。我被困在那一点。

答案 2 :(得分:0)

这不是对原始问题的答案,只是代码段太长而无法发表评论。

Re:refresh();

为什么不使用它:这是绝对的紧急api,如果无法通过任何方式使FX属性的自动魔术发挥作用,则可以使用粗糙的钩子强制进行更新。不能经常重复:不要不要-如果似乎需要,则您的数据设置有问题。事实总是如此(如99.9999999%)

如何避免:修正数据设置,确保按预期方式发送通知。一个常见的原因是列表中项目的属性已更改。默认情况下,observableList不侦听其项的内部更新,因此无法通知其自己的侦听器,您必须将其显式配置为侦听并触发更新事件-通过提供提取器来完成。

代码段:

ObservableList<Person> persons = FXCollections.observableArrayList(
        p -> new Observable[]{p.nameProperty(), p.emailProperty()});
persons.setAll(new Person("Jack", "j@email.com"),
        new Person("Bill", "bill@email.com"),
        new Person("Diane", "dd@email.com"));

// Simple ListView
ListView<Person> listView = new ListView<>(persons);