JavaFX - 在TableView

时间:2015-07-01 20:22:31

标签: validation tabs tableview javafx-8

我有一个TabPane,用户可以在每个选项卡上输入/编辑数据,并且可以在切换到新选项卡之前自由切换选项卡,而无需保存更改。一个选项卡有一个TableView,我想阻止用户在输入无效数据时离开该选项卡。我原来的方法与this question的方法相同,但不能正常工作 - 标签不能可靠地改回来。我喜欢James_D的answer,并试图实现类似的东西。但是,大多数情况下,输入表中的数据是可选的,因此在用户输入数据之前禁用其他选项卡不是一种选择。

我最终做的是扩展TableColumn以添加一个BooleanProperty'invalid',然后我将其绑定到Tab的disableProperty。在该列的提交事件中,我验证新值,如果未通过,则设置invalid = true,这将禁用相应的选项卡。这也行不通。我有自定义表格单元格,提供失去焦点的编辑。如果单击其他选项卡失去焦点,则提交事件为时已晚 - 首先选择选项卡,然后禁用。我一直在捣乱我的大脑以寻找解决方法,但是我没有想法。如果有人有任何建议,我会非常感激!

简短示例(清除任何姓氏并单击Tab 2):

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TabPaneTableTest extends Application {

  @Override
  public void start(Stage primaryStage) {
    TableView<Person> table = new TableView<>();
    ObservableList<Person> data = FXCollections.observableArrayList();

    table.setEditable(true);

    MyTableColumn<Person, String> firstNameCol = new MyTableColumn<>("First Name");
    firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
    MyTableColumn<Person, String> lastNameCol = new MyTableColumn<>("Last Name");
    lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));

    Callback<TableColumn<Person, String>, TableCell<Person, String>> cellFactory = (TableColumn<Person, String> p) -> new MyEditingCell<Person>();

    firstNameCol.setCellFactory(cellFactory);
    lastNameCol.setCellFactory(cellFactory);

    firstNameCol.setOnEditCommit((CellEditEvent<Person, String> event) -> {
      event.getRowValue().setFirstName(event.getNewValue());
    });
    lastNameCol.setOnEditCommit((CellEditEvent<Person, String> event) -> {
      if(event.getNewValue().trim().isEmpty()) {
        new Alert(AlertType.ERROR, "Last name must be filled out!", ButtonType.OK).showAndWait();
        lastNameCol.setInvalid(true);
      }
      else {
        event.getRowValue().setLastName(event.getNewValue());
        lastNameCol.setInvalid(false);
      }
    });

    table.getColumns().addAll(firstNameCol, lastNameCol);
    table.setItems(data);
    data.add(new Person("Luke", "Skywalker"));
    data.add(new Person("Han", "Solo"));
    data.add(new Person("R2", "D2"));

    TabPane tabPane = new TabPane();
    Tab tab1 = new Tab("Tab 1");
    tab1.setClosable(false);
    tab1.setContent(table);

    Tab tab2 = new Tab("Tab 2");
    tab2.setClosable(false);
    tab2.disableProperty().bind(lastNameCol.invalidProperty());
    tabPane.getTabs().addAll(tab1, tab2);

    Scene scene = new Scene(tabPane, 400, 200);
    primaryStage.setTitle("Tab Pane Table Validation Test");
    primaryStage.setScene(scene);
    primaryStage.show();
  }

  public class MyEditingCell<S> extends TableCell<S, String> {
    private TextField editingField;

    private void createEditingField() {
      editingField = new TextField(getString());
      editingField.focusedProperty().addListener((ov, oldValue, newValue) -> {
        if(!newValue) {
          commitEdit(editingField.getText());
        }
      });
    }

    @Override
    public void startEdit() {
      super.startEdit();
      createEditingField();
      setText(null);
      setGraphic(editingField);
      Platform.runLater(() -> {
        editingField.requestFocus();
        editingField.selectAll();
      });
    }

    @Override
    public void cancelEdit() {
      super.cancelEdit();
      setText((String)getItem());
      setGraphic(null);
    }

    @Override
    public void updateItem(String item, boolean empty) {
      super.updateItem(item, empty);

      if(empty) {
        setText(null);
        setGraphic(null);
      }
      else {
        if(isEditing()) {
          if(editingField != null) {
            editingField.setText(getString());
          }
          setText(null);
          setGraphic(editingField);
        }
        else {
          setText(getString());
          setGraphic(null);
        }
      }
    }

    private String getString() {
      return getItem() == null ? "" : getItem().toString();
    }
  }

  public class MyTableColumn<S, T> extends TableColumn<S, T> {
    private BooleanProperty invalid = new SimpleBooleanProperty(false);

    public MyTableColumn(String header) {
      super(header);
      setEditable(true);
    }

    public BooleanProperty invalidProperty() {
      return invalid;
    }

    public boolean getInvalid() {
      return invalid.get();
    }

    public void setInvalid(boolean value) {
      invalid.set(value);
    }
  }

  public class Person {

    private StringProperty firstName;
    private StringProperty lastName;

    public Person(String first, String last) {
      firstName = new SimpleStringProperty(this, "firstName", first);
      lastName = new SimpleStringProperty(this, "lastName", last);
    }

    public void setFirstName(String value) {
      firstNameProperty().set(value);
    }

    public String getFirstName() {
      return firstNameProperty().get();
    }

    public StringProperty firstNameProperty() {
      if(firstName == null)
        firstName = new SimpleStringProperty(this, "firstName", "First");
      return firstName;
    }

    public void setLastName(String value) {
      lastNameProperty().set(value);
    }

    public String getLastName() {
      return lastNameProperty().get();
    }

    public StringProperty lastNameProperty() {
      if(lastName == null)
        lastName = new SimpleStringProperty(this, "lastName", "Last");
      return lastName;
    }
  }

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

1 个答案:

答案 0 :(得分:0)

如果使用提取器创建可观察列表,例如:

ObservableList<Person> data = FXCollections.observableArrayList(person -> 
    new Observable[] { person.lastNameProperty() });

然后,只要任何元素中的任何指定属性发生更改,列表就会触发更新通知(在这种情况下,只要lastName更改列表中的任何内容)。

现在您可以为invalid

创建绑定
BooleanBinding invalid = Bindings.createBooleanBinding(
    () -> data.stream().anyMatch(person -> person.getLastName().isEmpty()),
    data);

然后你可以观察到这种绑定:

invalid.addListener((obs, wasInvalid, isNowInvalid) -> {
    if (isNowInvalid) {
        // show alert, etc...
    }
});

或通过绑定节点来禁用节点:

someNode.disableProperty().bind(invalid);

您可以类似地将invalid子类中的TableColumn属性(如果您仍然需要)绑定到此绑定。