Редактируемая ячейка JavaFX TableView + стиль изменения после редактирования

Мне нужно сделать ячейку в TableView редактируемой и выделить ее жирным шрифтом после фиксации изменений. Я могу сделать эти вещи по отдельности, но не вместе, так как они реализуются через метод setCellFactory.

Вот части кода:

Настройка клеточной фабрики

    ((TableColumn) lpAttributesTable.getColumns().get(1)).setCellFactory(
                new Callback<TableColumn<LPAttributesTableObject,String>, TableCell<LPAttributesTableObject,String>>() {
    @Override
    public TableCell<LPAttributesTableObject,String> call(
        TableColumn<LPAttributesTableObject,String> param) {
        TextFieldTableCell<LPAttributesTableObject, String> editableTableCell = 
            new TextFieldTableCell<LPAttributesTableObject, String>(new StringConverter<String>() {
            @Override
            public String toString(String object) {
                return object;
            }

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

                if (item == null || empty) {
                    setText(null);
//                        setStyle("");
                }
                else
                {
                    setText(item);
                    styleProperty().bind(
                    Bindings.when(getTableRow().selectedProperty()).
                        then("-f-font-weight:bold;").otherwise(""));

                }
            }
        };
        return editableTableCell;
    }
});

LPAttributesTableObject

public class LPAttributesTableObject {
    private String attribute;
    private String value;

    public LPAttributesTableObject(String _attribute, String _value)
    {
        this.attribute = _attribute;
        this.value = _value;
    }

    public final String getAttribute() { return attribute; }
    public final String getValue() { return value; }
    public StringProperty attributeProperty() { return new SimpleStringProperty(attribute); }
    public StringProperty valueProperty() { return new SimpleStringProperty(value); }
    public final void setAttribute(String _attr) { this.attribute = _attr;}
    public final void setValue(String _description) { this.value = _description;}
}

Таблица с модельной привязкой. Обратите внимание, я закомментировал CellFactory только для редактирования.

((TableColumn) lpattrsTable.getColumns().get(1)).setCellValueFactory(new PropertyValueFactory<LPAttributesTableObject,String>("value"));
//        ((TableColumn) lpattrsTable.getColumns().get(1)).setCellFactory(TextFieldTableCell.forTableColumn(new DefaultStringConverter()));

Текущая реализация позволяет только редактирование ячеек.


person Artem Shepelev    schedule 25.08.2016    source источник
comment
В коде под Setting cellFactory попробуйте исправить опечатку в Bindings.when...("-f-font-weight:bold;").otherwise("")); с помощью Bindings.when...("-fx-font-weight:bold;").otherwise(""));.   -  person iMan    schedule 25.08.2016
comment
Привет, помогло, но теперь они всегда выделены жирным шрифтом, до/после редактирования.   -  person Artem Shepelev    schedule 25.08.2016


Ответы (2)


Обратите внимание, что простая привязка к выбранному свойству не работает. Также вам нужно сохранить тот факт, что элемент каким-то образом редактировался. Это можно сделать с помощью свойства элемента или файла ObservableMap.

Кроме того, вам необходимо фактически установить информацию о том, что элемент был отредактирован. Это можно сделать, перезаписав метод commitEdit.

// external storage of edited states by index
ObservableMap<Number, Boolean> edited = FXCollections.observableHashMap();

TableColumn<LPAttributesTableObject, String> column = (TableColumn) lpAttributesTable.getColumns().get(1);

StringConverter<String> converter = new DefaultStringConverter();

column.setCellFactory(t -> new TextFieldTableCell<LPAttributesTableObject, String>(converter) {
    
    private final BooleanBinding editedBinding = Bindings.booleanValueAt(edited, indexProperty());
    
    {
        editedBinding.addListener((a, b, newValue) -> setStyle(newValue ? "-fx-font-weight:bold;" : ""));
    }

    @Override
    public void commitEdit(String newValue) {
        if (!Objects.equals(newValue, getItem())) {
            // save data that this index has been edited
            edited.put(getIndex(), Boolean.TRUE);
        }
        super.commitEdit(newValue);
    }

});

Обратите внимание, что вам может потребоваться изменить карту в случае изменения списка элементов таблицы.


Альтернатива

Использование типа значения, в котором хранится, был ли элемент отредактирован или нет, вместо простого String позволяет сохранить простоту кода, даже если список элементов изменен:

public class EditableString {

    private final boolean edited;
    private final String value;

    public EditableString(boolean edited, String value) {
        this.edited = edited;
        this.value = value;
    }

    public EditableString(String value) {
        this(false, value);
    }

    public boolean isEdited() {
        return edited;
    }

    public String getValue() {
        return value;
    }

}
StringConverter<EditableString> converter = new StringConverter<EditableString>() {

    @Override
    public String toString(EditableString object) {
        return object == null ? null : object.getValue();
    }

    @Override
    public EditableString fromString(String string) {
        return string == null ? null : new EditableString(true, string);
    }

};

TableColumn<LPAttributesTableObject, EditableString> column = (TableColumn) lpAttributesTable.getColumns().get(1);

column.setCellFactory(t -> new TextFieldTableCell<LPAttributesTableObject, EditableString>(converter) {

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

        if (item != null && item.isEdited()) {
            setStyle("-fx-font-weight: bold;");
        } else {
            setStyle("");
        }
    }

    @Override
    public void commitEdit(EditableString newValue) {
        EditableString currentItem = getItem();
        if (currentItem == null || newValue == null || !Objects.equals(newValue.getValue(), currentItem.getValue())) {
            super.commitEdit(newValue);
        } else {
            // if the string is the same, keep the old value
            super.commitEdit(currentItem);
        }
    }

});

Обратите внимание, что для этого необходимо изменить свойство LPAttributesTableObject, содержащее String, на свойство, содержащее EditableString.

person fabian    schedule 25.08.2016
comment
Привет Фабиан. Спасибо за ваш ответ. Я получаю Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.String cannot be cast to sample.EditableString. Боремся с ней между тем несколько дней. Не имею представления. Исключение указывает на col.setCellFactory(t -> new TextFieldTableCell<LPAttributesTableObject, EditableString>(converter) и появляется при вызове метода updateItem, который был переопределен. - person Artem Shepelev; 30.08.2016
comment
@ArtemShepelev Как описано, вам нужно изменить тип свойства с StringProperty на ObjectProperty<String> на ObjectProperty<EditableString> и использовать EditableStrings вместо Strings. - person fabian; 30.08.2016

Итак, наконец, вот как я имел в виду, что это работает.

    StringConverter<EditableString> converter = new StringConverter<EditableString>() {
                @Override
                public String toString(EditableString object) {
                    System.out.println("bla bla");
                    return object == null ? null : object.toString();
                }

                @Override
                public EditableString fromString(String string) {
                    System.out.println("bla");
                    return string == null ? null : new EditableString(true, string);
                }
            };


((TableColumn) lpAttributesTable.getColumns().get(1)).setCellValueFactory(new PropertyValueFactory<LPAttributesTableObject,EditableString>("val"));
            ((TableColumn) lpAttributesTable.getColumns().get(0)).setCellValueFactory(new PropertyValueFactory<LPAttributesTableObject,String>("attribute"));
    TableColumn<LPAttributesTableObject, EditableString> col = (TableColumn) lpAttributesTable.getColumns().get(1);
            col.setCellFactory(TextFieldTableCellEditable.<LPAttributesTableObject, EditableString>forTableColumn(converter));

Мне пришлось расширить существующий объект TextFieldTableCell до этого:

public class TextFieldTableCellEditable<S,EditableString> extends TextFieldTableCell<S,EditableString> {

    public static <S,EditableString> Callback<TableColumn<S,EditableString>, TableCell<S,EditableString>> forTableColumn(
            final StringConverter<EditableString> converter) {
        return list -> new TextFieldTableCellEditable<S, EditableString>(converter);
    }

    public TextFieldTableCellEditable(StringConverter<EditableString> converter) {
        this.getStyleClass().add("text-field-table-cell");
        setConverter(converter);
    }


    @Override
    public final void updateItem(EditableString item, boolean empty) {

        super.updateItem(item, empty);
        if (item != null && ((sample.EditableString)item).isEdited()) {
            setText(item.toString());
            setStyle("-fx-font-weight: bold;");
        } else if (item != null && !((sample.EditableString)item).isEdited()){
            setStyle("");
            setText(item.toString());
        } else {
            setStyle("");
            setText(null);
        }
    }

    @Override
    public void commitEdit(EditableString newValue) {
        EditableString currentItem = getItem();
        if (currentItem == null || newValue == null || !Objects.equals(((sample.EditableString)newValue).getValue(), ((sample.EditableString)currentItem).getValue())) {
            super.commitEdit(newValue);
        } else {
            // if the string is the same, keep the old value
            super.commitEdit(currentItem);
        }
    }

}

LPAttributesTableObject выглядит так:

public class LPAttributesTableObject {
    private String attribute;
    private EditableString val;

    public LPAttributesTableObject(String _attribute, EditableString _value)
    {
        this.attribute = _attribute;
        this.val = _value;
    }

    public String getAttribute() { return attribute.toString(); }
    public EditableString getVal() { return val; }
    public final EditableString getValue() { return val; }
    public StringProperty attributeProperty() { return new SimpleStringProperty(attribute.toString()); }
    public ObjectProperty<EditableString> valProperty() { return new SimpleObjectProperty<EditableString>(this,"value",val); }
    public void setAttribute(String _attr) { this.attribute = _attr;}
    public void setValue(EditableString _value) { this.val = _value;}
    public void setVal(EditableString _val) { this.val = _val; }
    public void setVal(String _val) { this.val.setValue(_val);}


}

И EditableString выглядит так:

public class EditableString { 
    private boolean edited;
    private String string;

    public EditableString(boolean edited, String val)
    {
        this.edited = edited;
        this.string = val;
    }

    public EditableString(String val)
    {
        this.edited = false;
        this.string = val;
    }

    public String getValue() { return string; }

    public void setValue(String val) { this.string = val; }

    public String toString() { return string; }

    public boolean isEdited() { return edited; }

    public void setValue(EditableString estr) { this.setValue(estr.getValue()); this.edited = estr.edited; }
}
person Artem Shepelev    schedule 01.09.2016