2

I have a use case which I would assume is pretty standard, however I haven't been able to find an example on exactly how to do this, or if it's possible.

Let's assume I have the following TableView

First Name    Last Name    Street    NewRecord

Tom           Smith        Main St.     Yes
Mike          Smith        First St.    No

In this case, the grid should have the first three cells editable since the record is new, however when the record is not new then the Last Name cell should be disabled.

I tried this in the CellFactory and RowFactory - but haven't seen a way to accomplish this.

Thanks for your help.

purring pigeon
  • 4,141
  • 5
  • 35
  • 68
  • Both `TableColumn` and `TableCell` have an editable state. Make the column editable and use a cell factory that updates the cell's editable state. If `NewRecord` is immutable, then you can just do this easily and directly in the `updateItem` method; if it's mutable then you will need a bit of trickery with listeners to properly observe the `newRecord` state. – James_D May 03 '16 at 17:28
  • I tried to do that from the CellFactory - however I wasn't able to determine exactly how to figure that out from there? Can you provide a snippet? That might be all I need. Thanks. – purring pigeon May 03 '16 at 17:32
  • Is `newRecord` mutable or not? – James_D May 03 '16 at 17:32
  • The new record is always mutable, and the existing records has only some values that are mutable. I have used a RowFactory to disable the entire row when it's not mutable. In this case depending on state of the record, different fields are mutable. – purring pigeon May 03 '16 at 17:33
  • mutable != editable != disabled – James_D May 03 '16 at 17:53

2 Answers2

2

The easiest way to do this is with a third-party binding library: ReactFX 2.0 has this functionality built-in, as described here. Using that you can do

TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
    TableCell<Person, String> cell = new TextFieldTableCell<>();
    cell.editableProperty().bind(
        // horrible cast needed because TableCell.tableRowProperty inexplicably returns a raw type:
        Val.flatMap(cell.tableRowProperty(), row -> (ObservableValue<Person>)row.itemProperty())
           .flatMap(Person::newRecordProperty)
           .orElseConst(false));
    return cell ;
});

(assumes a Person table model object with the obvious JavaFX properties and methods).

Without the library, you need a pretty miserable nested list of listeners:

TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
    TableCell<Person, String> cell = new TextFieldTableCell<>();
    ChangeListener<Boolean> newRecordListener = (obs, wasNewRecord, isNewRecord) -> updateEditability(cell);
    ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> {
        if (oldPerson != null) {
            oldPerson.newRecordProperty().removeListener(newRecordListener);
        }
        if (newPerson != null) {
            newPerson.newRecordProperty().addListener(newRecordListener);
        }
        updateEditability(cell);
    };
    ChangeListener<TableRow> rowListener = (obs, oldRow, newRow) -> {
        if (oldRow != null) {
            ((ObservableValue<Person>)oldRow.itemProperty()).removeListener(rowItemListener);
            if (oldRow.getItem() != null) {
                ((Person)oldRow.getItem()).newRecordProperty().removeListener(newRecordListener);
            }
        }
        if (newRow != null) {
            ((ObservableValue<Person>)newRow.itemProperty()).addListener(rowItemListener);
            if (newRow.getItem() != null) {
                ((Person)newRow.getItem()).newRecordProperty().addListener(newRecordListener);
            }
        }
        updateEditability(cell);
    };
    cell.tableRowProperty().addListener(rowListener);
    return cell ;
});

and then

private void updateEditability(TableCell<Person, String> cell) {
    if (cell.getTableRow() == null) {
        cell.setEditable(false);
    } else {
        TableRow<Person> row = (TableRow<Person>) cell.getTableRow();
        if (row.getItem() == null) {
            cell.setEditable(false);
        } else {
            cell.setEditable(row.getItem().isNewRecord());
        }
    }
}

An alternative using a "legacy style" API is

TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
    TableCell<Person, String> cell = new TextFieldTableCell<>();
    cell.editableProperty().bind(
        Bindings.selectBoolean(cell.tableRowProperty(), "item", "newRecord"));
    return cell ;
});

I dislike this option, because it lacks any type safety (or indeed any compiler checks at all), and additionally in some earlier versions of JavaFX would generate almost endless warning messages if any of the properties in the "chain" had null values (which they will, frequently, in this case). I believe the latter issue is fixed, but the ReactFX version of this is far better, imho.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks - can't use the library, but this gives me the nudge I need. – purring pigeon May 03 '16 at 18:08
  • I guess there is also `Bindings.selectBoolean(cell.tableRowProperty(), "item", "newRecord")`, to which you could bind `cell.editableProperty()`. I gave up using that a long while ago as it had a habit of churning out warnings whenever anything in the chain was null (which it frequently is, by design, in a table cell), and the lack of typesafety is very "legacy"; but it should work well now. (I assume the spurious warnings are now fixed.) – James_D May 03 '16 at 18:31
  • Does that change replace the listeners? I'm not following how I'd use that logic in the cell factory. – purring pigeon May 03 '16 at 18:52
  • Replace the binding using the third party library (i.e. `Val.flatMap(...).flatMap(...).orElseConst(...)`) with the `Bindings.selectBoolean(...)` expression. – James_D May 03 '16 at 18:58
  • Ok thanks - I'll try them both out. As for the spurious warnings they are all still there. – purring pigeon May 03 '16 at 19:00
  • I was unable to make the second option work, but your main answer seems to handle it for me. It's ugly, but it'll work. – purring pigeon May 03 '16 at 19:31
0

You can always stop an editing by setting event handler to check if the action is legal.

        columnName.setOnEditStart(
            new EventHandler<CellEditEvent<ItemClass, String>>(){
                @Override
                public void handle(CellEditEvent<ItemClass, String> event) {
                    if(event.getTableColumn().getCellData(3).compareTo("yes") == 0) {
                        event.getTableView().edit(-1, null); 
                       //this prevents the editing in "progress"
                    }                       
                }   
            }
    );
Filip
  • 1