Подтвердить что ты не робот

Как включить фиксацию на focusLost для TableView/TreeTableView?

Есть ли какой-либо простой подход, позволяющий TreeTableView (или TableView) пытаться зафиксировать значения потерянного фокуса?

К сожалению, я не преуспел в каких-либо реализациях javafx TableCellFactories по умолчанию, поэтому я попробовал свои собственные реализации TreeTableCell, а также некоторые другие реализации tableCell, такие как: Graham Smith, который казался наиболее прямым, так как он уже реализовал крючок для фокуса, но тем не менее значение никогда не выполняется, а пользовательские настройки возвращаются к исходному значению.

Моя догадка заключается в том, что всякий раз, когда фокус теряется, свойство editProperty затронутой ячейки всегда является ложным, что заставляет Cell никогда не фиксировать значение на focusLost. Здесь соответствующая часть из оригинальной (oracle-) реализации TreeTableCell (8u20ea), которая приводит к сбою моих подходов:

 @Override public void commitEdit(T newValue) {
        if (! isEditing()) return; // <-- here my approaches are blocked, because on focus lost its not editing anymore.

        final TreeTableView<S> table = getTreeTableView();
        if (table != null) {
            @SuppressWarnings("unchecked")
            TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell();

            // Inform the TableView of the edit being ready to be committed.
            CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>(
                table,
                editingCell,
                TreeTableColumn.<S,T>editCommitEvent(),
                newValue
            );

            Event.fireEvent(getTableColumn(), editEvent);
        }

        // inform parent classes of the commit, so that they can switch us
        // out of the editing state.
        // This MUST come before the updateItem call below, otherwise it will
        // call cancelEdit(), resulting in both commit and cancel events being
        // fired (as identified in RT-29650)
        super.commitEdit(newValue);

        // update the item within this cell, so that it represents the new value
        updateItem(newValue, false);

        if (table != null) {
            // reset the editing cell on the TableView
            table.edit(-1, null);

            // request focus back onto the table, only if the current focus
            // owner has the table as a parent (otherwise the user might have
            // clicked out of the table entirely and given focus to something else.
            // It would be rude of us to request it back again.
            ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
        }
    }

Мне удалось переопределить этот метод и передать значение "вручную" до того, как вызывается оригинальный метод commitEdit(), но это вызывает фиксацию ключей, таких как enter, для фиксации значения дважды (по клавише + при утере фокуса). Кроме того, мне совсем не нравится мой подход, поэтому я задаюсь вопросом, может ли кто-нибудь еще решить это "лучше"?

4b9b3361

Ответ 1

После некоторого копания выяснилось, что виновник (ака: соавтор, который отменяет редактирование до того, как textField теряет фокус) является TableCellBehaviour/Base при обработке мыши. Подписал:

  • mousePressed calls simpleSelect(..)
  • при обнаружении одного щелчка он вызывает edit(-1, null)
  • который вызывает тот же метод в TableView
  • который устанавливает для свойства editingCell значение null
  • tableCell прослушивает это свойство и реагирует, отменяя его собственное редактирование

К сожалению, для hackaround требуется 3 сотрудника

  • TableView с дополнительным api для завершения редактирования
  • TableCellBehaviour с переопределенным simpleSelect(...), который вызывает дополнительный api (вместо редактирования (-1..)) перед вызовом super
  • TableCell, который настроен с расширенным поведением и знает расширенные свойства таблицы.

Некоторые фрагменты кода (полный код):

// on XTableView:
public void terminateEdit() {
    if (!isEditing()) return;
    // terminatingCell is a property that supporting TableCells can listen to
    setTerminatingCell(getEditingCell());
    if (isEditing()) throw new IllegalStateException(
          "expected editing to be terminated but was " + getEditingCell());
    setTerminatingCell(null);
}

// on XTableCellBehaviour: override simpleSelect
@Override
protected void simpleSelect(MouseEvent e) {
    TableCell<S, T> cell = getControl();
    TableView<S> table = cell.getTableColumn().getTableView();
    if (table instanceof XTableView) {
        ((XTableView<S>) table).terminateEdit();
    }
    super.simpleSelect(e);
}

// on XTextFieldTableCell - this method is called from listener
// to table terminatingCell property
protected void terminateEdit(TablePosition<S, ?> newPosition) {
    if (!isEditing() || !match(newPosition)) return;
    commitEdit();
}

protected void commitEdit() {
    T edited = getConverter().fromString(myTextField.getText());
    commitEdit(edited);
}

/**
 * Implemented to create XTableCellSkin which supports terminating edits.
 */
@Override
protected Skin<?> createDefaultSkin() {
    return new XTableCellSkin<S, T>(this);
}

Примечание: реализация TableCellBehaviour значительно изменилась между jdk8u5 и jdk8u20 (радости взлома - не подходят для использования в производстве;-) - метод переопределения в последнем случае handleClicks(..)

BTW: массовое голосование JDK-8089514 (было RT-18492 в старой джире) может ускорить исправление ядра. К сожалению, по крайней мере роль автора необходима для голосования/комментариев в новом трекере.

Ответ 2

Я также нуждался в этой функции и провел некоторое исследование. Я столкнулся с некоторыми проблемами стабильности при взломе XTableView, упомянутых выше.

Поскольку проблема, кажется, commitEdit() не будет действовать, когда фокус потерян, почему вы не просто вызываете свой собственный код отзыва из TableCell следующим образом:

public class SimpleEditingTextTableCell extends TableCell {
    private TextArea textArea;
    Callback commitChange;

    public SimpleEditingTextTableCell(Callback commitChange) {
        this.commitChange = commitChange;
    }

    @Override
    public void startEdit() {
         ...

        getTextArea().focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) {
                if (!arg2) {
                    //commitEdit is replaced with own callback
                    //commitEdit(getTextArea().getText());

                    //Update item now since otherwise, it won't get refreshed
                    setItem(getTextArea().getText());
                    //Example, provide TableRow and index to get Object of TableView in callback implementation
                    commitChange.call(new TableCellChangeInfo(getTableRow(), getTableRow().getIndex(), getTextArea().getText()));
                }
            }
        });
       ...
    }
    ...
}

В ячейке factory вы просто сохраняете зафиксированное значение для объекта или делаете все необходимое, чтобы сделать его постоянным:

col.setCellFactory(new Callback<TableColumn<Object, String>, TableCell<Object, String>>() {
            @Override
            public TableCell<Object, String> call(TableColumn<Object, String> p) {
                return new SimpleEditingTextTableCell(cellChange -> {
                            TableCellChangeInfo changeInfo = (TableCellChangeInfo)cellChange;
                            Object obj = myTableView.getItems().get(changeInfo.getRowIndex());
                            //Save committed value to the object in tableview (and maybe to DB)
                            obj.field = changeInfo.getChangedObj().toString();
                            return true;
                        });
            }
        });

До сих пор я не мог найти никаких проблем с этим обходным решением. С другой стороны, я еще не прошел обширные испытания на этом.

EDIT: Ну, после некоторого тестирования заметили, что обходной путь хорошо работал с большими данными в tableview, но с пустой ячейкой tableview не обновлялся после потери фокуса, только когда двойной щелчок снова. Были бы способы обновить представление таблицы, но это слишком сильно взломало меня...

EDIT2: добавлен setItem (getTextArea(). getText()); перед вызовом callback → работает также с пустой таблицей.

Ответ 3

С оговоркой это было глупое предложение. Кажется, слишком легко. Но почему бы вам просто не переопределить TableCell#cancelEdit() и сохранить значения вручную при его вызове? Когда ячейка теряет фокус, cancelEdit() всегда вызывается для отмены редактирования.

class EditableCell extends TableCell<ObservableList<StringProperty>, String> {

    private TextField textfield = new TextField();
    private int colIndex;
    private String originalValue = null;

    public EditableCell(int colIndex) {
        this.colIndex = colIndex;
        textfield.prefHeightProperty().bind(heightProperty().subtract(2.0d));
        this.setPadding(new Insets(0));
        this.setAlignment(Pos.CENTER);

        textfield.setOnAction(e -> {
            cancelEdit();
        });

        textfield.setOnKeyPressed(e -> {
            if (e.getCode().equals(KeyCode.ESCAPE)) {
                textfield.setText(originalValue);
            }
        });
    }

    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (isEmpty()) {
            setText(null);
            setGraphic(null);
        } else {
            if (isEditing()) {
                textfield.setText(item);
                setGraphic(textfield);
                setText(null);
            } else {
                setText(item);
                setGraphic(null);
            }
        }
    }

    @Override
    public void startEdit() {
        super.startEdit();
        originalValue = getItem();
        textfield.setText(getItem());
        setGraphic(textfield);
        setText(null);
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setGraphic(null);
        setText(textfield.getText());
        ObservableList<StringProperty> row = getTableView().getItems().get(getIndex());
        row.get(colIndex).set(getText());
    }
}

Я не знаю. Может быть, я что-то упустил. Но, похоже, это работает для меня.

Обновление: Добавлена ​​функция отмены редактирования. Теперь вы можете отменить редактирование, нажав кнопку escape, когда вы фокусируете текстовое поле. Также добавлено так, что вы можете сохранить редактирование, нажав enter, когда вы фокусируете текстовое поле.

Ответ 4

Так как TextFieldTableCell страдает от значительной потери функции (как указано в https://bugs.openjdk.java.net/browse/JDK-8089514), который планируется для исправления в Java 9, я решил пойти с альтернативным решением. Примите мои извинения, если это вне цели, но вот оно:

Основная идея - забыть TextFieldTableCell и использовать собственный класс TableCell с TextField в нем.

Пользовательский TableCell:

public class CommentCell extends TableCell<ListItem, String> {

    private final TextField comment = new TextField();

    public CommentCell() {
        this.comment.setMaxWidth( Integer.MAX_VALUE );
        this.comment.setDisable( true );
        this.comment.focusedProperty().addListener( new ChangeListener<Boolean>() {
            @Override
            public void changed( ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue,
                    Boolean newPropertyValue ) {
                if ( !newPropertyValue ) {
                    // Binding the TextField text to the model
                    MainController.getInstance().setComment( getTableRow().getIndex(), comment.getText() );
                }
            }
        } );
        this.setGraphic( this.comment );
    }

    @Override
    protected void updateItem( String s, boolean empty ) {
        // Checking if the TextField should be editable (based on model condition)
        if ( MainController.getInstance().isDependency( getTableRow().getIndex() ) ) {
            this.comment.setDisable( false );
            this.comment.setEditable( true );
        }
        // Setting the model value as the text for the TextField
        if ( s != null && !s.isEmpty() ) {
            this.comment.setText( s );
        }
    }
}

Дисплей пользовательского интерфейса может отличаться от TextFieldTableCell, но, по крайней мере, он позволяет улучшить удобство использования: Пользовательский интерфейс

Ответ 5

Я нашел простое решение, просто нужно предоставить функцию фиксации для столбца, специфичного для типа данных:

TableColumn msgstr = new TableColumn("msgstr");
msgstr.setMinWidth(100);
msgstr.prefWidthProperty().bind(widthProperty().divide(3));
msgstr.setCellValueFactory(
        new PropertyValueFactory<>("msgstr")
);
msgstr.setOnEditCommit(new EventHandler<CellEditEvent<PoEntry, String>>() {
@Override
public void handle(CellEditEvent<PoEntry, String> t) {
    ((PoEntry)t.getTableView().getItems().get(t.getTablePosition().getRow())).setMsgstr(t.getNewValue());
}
});