繁体   English   中英

在JavaFX中单击外部可编辑的TableView单元格时如何提交?

[英]How to commit when clicking outside an editable TableView cell in JavaFX?

我有一个表格单元工厂负责在JavaFX TableView中创建可编辑单元格。

我正在尝试为tableview实现一些附加功能,以便当用户在可编辑单元格外部单击时进行提交(编辑后的文本将被保存,而不会根据默认的tableview行为进行丢弃。)

我添加了一个textField.focusedProperty()事件处理程序,我从文本字段提交文本。 但是,当在当前单元cancelEdit()单击时,将调用cancelEdit()并调用commitEdit(textField.getText()); 没有效果。

我已经意识到,一旦调用了cancelEdit()TableCell.isEditing()返回false,因此提交永远不会发生。

如何在用户点击可编辑单元格外部时提交文本?

提交setOnEditCommit()事件处理程序后,将处理验证和数据库逻辑。 我没有把它包含在这里,因为它很可能会使事情进一步复杂化。

// EditingCell - for editing capability in a TableCell
public static class EditingCell extends TableCell<Person, String> {
private TextField textField;

public EditingCell() {
}

@Override public void startEdit() {
    super.startEdit();

    if (textField == null) {
        createTextField();
    }
    setText(null);
    setGraphic(textField);
    textField.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 (textField != null) {
                textField.setText(getString());
            }
            setText(null);
            setGraphic(textField);
        } else {
            setText(getString());
            setGraphic(null);
        }
    }
}

private void createTextField() {
    textField = new TextField(getString());
    textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
    textField.setOnKeyReleased(new EventHandler<KeyEvent>() {                
        @Override public void handle(KeyEvent t) {
            if (t.getCode() == KeyCode.ENTER) {
                commitEdit(textField.getText());
            } else if (t.getCode() == KeyCode.ESCAPE) {
                cancelEdit();
            }
        }
    });

    textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
         @Override
         public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
             if (!newValue) {
                    commitEdit(textField.getText());
             }
         }
    });
}

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

你可以通过覆盖下一个方法commitEdit来做到这一点:

@Override
public void commitEdit(T item) {
    // This block is necessary to support commit on losing focus, because 
    // the baked-in mechanism sets our editing state to false before we can 
    // intercept the loss of focus. The default commitEdit(...) method 
    // simply bails if we are not editing...
    if (!isEditing() && !item.equals(getItem())) {
        TableView<S> table = getTableView();
        if (table != null) {
            TableColumn<S, T> column = getTableColumn();
            CellEditEvent<S, T> event = new CellEditEvent<>(
                table, new TablePosition<S,T>(table, getIndex(), column), 
                TableColumn.editCommitEvent(), item
            );
            Event.fireEvent(column, event);
        }
    }

    super.commitEdit(item);
}

此解决方法来自https://gist.github.com/james-d/be5bbd6255a4640a5357#file-editcell-java-L109

由于我找不到kuaw26的源代码(死链接),我为java 8开发了自己的解决方案。我发现上面代码中的TextField从未收到esc-key的keyReleased事件,因此他的代码不起作用。

不幸的是,我需要从TextFieldTableCell和CellUtils复制代码并对其进行调整,因为TextFieldTableCell使用私有TextField,而CellUtils受包保护。 这可能不是最好的OO方式。

这是我的解决方案:

// package yourLib;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.Event;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;

/**
 * A class containing a {@link TableCell} implementation that draws a 
 * {@link TextField} node inside the cell. If the TextField is
 * left, the value is commited.
 * 
 */

public class AcceptOnExitTableCell<S,T> extends TableCell<S,T> {

/***************************************************************************
 *                                                                         *
 * Static cell factories                                                   *
 *                                                                         *
 **************************************************************************/

/**
 * Provides a {@link TextField} that allows editing of the cell content when
 * the cell is double-clicked, or when 
 * {@link TableView#edit(int, javafx.scene.control.TableColumn)} is called. 
 * This method will only  work on {@link TableColumn} instances which are of
 * type String.
 * 
 * @return A {@link Callback} that can be inserted into the 
 *      {@link TableColumn#cellFactoryProperty() cell factory property} of a 
 *      TableColumn, that enables textual editing of the content.
 */
public static <S> Callback<TableColumn<S,String>, TableCell<S,String>> forTableColumn() {
    return forTableColumn(new DefaultStringConverter());
}

/**
 * Provides a {@link TextField} that allows editing of the cell content when
 * the cell is double-clicked, or when 
 * {@link TableView#edit(int, javafx.scene.control.TableColumn) } is called. 
 * This method will work  on any {@link TableColumn} instance, regardless of 
 * its generic type. However, to enable this, a {@link StringConverter} must 
 * be provided that will convert the given String (from what the user typed 
 * in) into an instance of type T. This item will then be passed along to the 
 * {@link TableColumn#onEditCommitProperty()} callback.
 * 
 * @param converter A {@link StringConverter} that can convert the given String 
 *      (from what the user typed in) into an instance of type T.
 * @return A {@link Callback} that can be inserted into the 
 *      {@link TableColumn#cellFactoryProperty() cell factory property} of a 
 *      TableColumn, that enables textual editing of the content.
 */
public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn(
        final StringConverter<T> converter) {
    return list -> new AcceptOnExitTableCell<S,T>(converter);
}


/***************************************************************************
 *                                                                         *
 * Fields                                                                  *
 *                                                                         *
 **************************************************************************/    

private TextField textField;
private boolean escapePressed=false;
private TablePosition<S, ?> tablePos=null;


/***************************************************************************
 *                                                                         *
 * Constructors                                                            *
 *                                                                         *
 **************************************************************************/

/**
 * Creates a default TextFieldTableCell with a null converter. Without a 
 * {@link StringConverter} specified, this cell will not be able to accept
 * input from the TextField (as it will not know how to convert this back
 * to the domain object). It is therefore strongly encouraged to not use
 * this constructor unless you intend to set the converter separately.
 */
public AcceptOnExitTableCell() { 
    this(null);
} 

/**
 * Creates a TextFieldTableCell that provides a {@link TextField} when put 
 * into editing mode that allows editing of the cell content. This method 
 * will work on any TableColumn instance, regardless of its generic type. 
 * However, to enable this, a {@link StringConverter} must be provided that 
 * will convert the given String (from what the user typed in) into an 
 * instance of type T. This item will then be passed along to the 
 * {@link TableColumn#onEditCommitProperty()} callback.
 * 
 * @param converter A {@link StringConverter converter} that can convert 
 *      the given String (from what the user typed in) into an instance of 
 *      type T.
 */
public AcceptOnExitTableCell(StringConverter<T> converter) {
    this.getStyleClass().add("text-field-table-cell");
    setConverter(converter);
}



/***************************************************************************
 *                                                                         *
 * Properties                                                              *
 *                                                                         *
 **************************************************************************/

// --- converter
private ObjectProperty<StringConverter<T>> converter = 
        new SimpleObjectProperty<StringConverter<T>>(this, "converter");

/**
 * The {@link StringConverter} property.
 */
public final ObjectProperty<StringConverter<T>> converterProperty() { 
    return converter; 
}

/** 
 * Sets the {@link StringConverter} to be used in this cell.
 */
public final void setConverter(StringConverter<T> value) { 
    converterProperty().set(value); 
}

/**
 * Returns the {@link StringConverter} used in this cell.
 */
public final StringConverter<T> getConverter() { 
    return converterProperty().get(); 
}  



/***************************************************************************
 *                                                                         *
 * Public API                                                              *
 *                                                                         *
 **************************************************************************/

/** {@inheritDoc} */
@Override public void startEdit() {
    if (! isEditable() 
            || ! getTableView().isEditable() 
            || ! getTableColumn().isEditable()) {
        return;
    }
    super.startEdit();

    if (isEditing()) {
        if (textField == null) {
            textField = getTextField(); 
        }
        escapePressed=false;
        startEdit(textField);
        final TableView<S> table = getTableView();
        tablePos=table.getEditingCell();
    }
}

/** {@inheritDoc} */
@Override public void commitEdit(T newValue) {
    if (! isEditing()) 
        return;

    final TableView<S> table = getTableView();
    if (table != null) {
        // Inform the TableView of the edit being ready to be committed.
        CellEditEvent editEvent = new CellEditEvent(
            table,
            tablePos,
            TableColumn.editCommitEvent(),
            newValue
        );

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

    // we need to setEditing(false):
   super.cancelEdit(); // this fires an invalid EditCancelEvent.

    // 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.
       // requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
    }
}


/** {@inheritDoc} */
@Override public void cancelEdit() {
    if(escapePressed) {
        // this is a cancel event after escape key
        super.cancelEdit();
        setText(getItemText()); // restore the original text in the view
    }
    else {
        // this is not a cancel event after escape key
        // we interpret it as commit.
        String newText=textField.getText(); // get the new text from the view
        this.commitEdit(getConverter().fromString(newText)); // commit the new text to the model
    }
    setGraphic(null); // stop editing with TextField

}

/** {@inheritDoc} */
@Override public void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);
    updateItem();
}

/***************************************************************************
 *                                                                         *
 *  // djw code taken and adapted from package protected CellUtils.        *
 *                                                                         *
 **************************************************************************/

private TextField getTextField() {

    final TextField textField = new TextField(getItemText());

    // Use onAction here rather than onKeyReleased (with check for Enter),
    // as otherwise we encounter RT-34685
    textField.setOnAction(event -> {
        if (converter == null) {
            throw new IllegalStateException(
                    "Attempting to convert text input into Object, but provided "
                            + "StringConverter is null. Be sure to set a StringConverter "
                            + "in your cell factory.");
        }
        this.commitEdit(getConverter().fromString(textField.getText()));
        event.consume();
    });
    textField.setOnKeyPressed(t -> { if (t.getCode() == KeyCode.ESCAPE) escapePressed = true; else escapePressed = false; });
    textField.setOnKeyReleased(t -> {
        if (t.getCode() == KeyCode.ESCAPE) {
            // djw the code may depend on java version / expose incompatibilities: 
            throw new IllegalArgumentException("did not expect esc key releases here.");
        }
    });
    return textField;
}

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

private void updateItem() {
    if (isEmpty()) {
        setText(null);
        setGraphic(null);
    } else {
        if (isEditing()) {
            if (textField != null) {
                textField.setText(getItemText());
            }
            setText(null);
            setGraphic(textField);      
        } else {
            setText(getItemText());
            setGraphic(null);
        }
    }
}

private void startEdit(final TextField textField) {
    if (textField != null) {
        textField.setText(getItemText());
    }
    setText(null);
    setGraphic(textField);
    textField.selectAll();

    // requesting focus so that key input can immediately go into the
    // TextField (see RT-28132)
    textField.requestFocus();
 }
}

这是我如何做到的 - 我将textField的text属性与单元格的text属性(双向)绑定在一起。

class EditingCell<S, T> extends TableCell<S, T> {

        private final TextField mTextField;

        public EditingCell() {

            super();

            mTextField = new TextField();

            mTextField.setOnKeyPressed(new EventHandler<KeyEvent>() {

                @Override
                public void handle(KeyEvent event) {

                    if( event.getCode().equals(KeyCode.ENTER) )
                        commitEdit((T)mTextField.getText());
                }
            });

            mTextField.focusedProperty().addListener(new ChangeListener<Boolean>() {

                @Override
                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {

                    if( !newValue )
                        commitEdit((T)mTextField.getText());
                }

            });

            mTextField.textProperty().bindBidirectional(textProperty());
        }

        @Override
        public void startEdit() {

            super.startEdit();

            setGraphic(mTextField);
        }

        @Override
        public void cancelEdit() {

            super.cancelEdit();

            setGraphic(null);
        }

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

            super.updateItem(item, empty);

            if( empty ) {
                setText(null);
                setGraphic(null);
            }
            else {
                if( item == null ) {
                    setGraphic(null);
                }
                else {
                    if( isEditing() ) {
                        setGraphic(mTextField);
                        setText((String)getItem());
                    }
                    else {
                        setGraphic(null);
                        setText((String)getItem());
                    }
                }
            }
        }
    }

我创建了自己的解决方法(但对于JavaFX 2)。 主要思想 - 将cancelEdit()转换为commitEdit()。 通过验证器可以验证提交的文本。

/** Validator. */
public interface TextColumnValidator<T> {
    boolean valid(T rowVal, String newVal);
}

/**
 * Special table text field cell that commit its content on focus lost.
 */
public class TextFieldTableCellEx<S> extends TextFieldTableCell<S, String> {
    /** */
    private final TextColumnValidator<S> validator;
    /** */
    private boolean cancelling;
    /** */
    private boolean hardCancel;
    /** */
    private String curTxt = "";

    /** Create cell factory. */
    public static <S> Callback<TableColumn<S, String>, TableCell<S, String>>
        cellFactory(final TextColumnValidator<S> validator) {
            return new Callback<TableColumn<S, String>, TableCell<S, String>>() {
                @Override public TableCell<S, String> call(TableColumn<S, String> col) {
                    return new TextFieldTableCellEx<>(validator);
                }
            };
    }

    /**
     * Text field cell constructor.
     *
     * @param validator Input text validator.
     */
    private TextFieldTableCellEx(TextColumnValidator<S> validator) {
        this.validator = validator;
    }

    /** {@inheritDoc} */
    @Override public void startEdit() {
        super.startEdit();

        curTxt = "";

        hardCancel = false;

        Node g = getGraphic();

        if (g != null) {
            final TextField tf = (TextField)g;

            tf.textProperty().addListener(new ChangeListener<String>() {
                @Override public void changed(ObservableValue<? extends String> val, String oldVal, String newVal) {
                    curTxt = newVal;
                }
            });

            tf.setOnKeyReleased(new EventHandler<KeyEvent>() {
                @Override public void handle(KeyEvent evt) {
                    if (KeyCode.ENTER == evt.getCode())
                        cancelEdit();
                    else if (KeyCode.ESCAPE == evt.getCode()) {
                        hardCancel = true;

                        cancelEdit();
                    }
                }
            });

            // Special hack for editable TextFieldTableCell.
            // Cancel edit when focus lost from text field, but do not cancel if focus lost to VirtualFlow.
            tf.focusedProperty().addListener(new ChangeListener<Boolean>() {
                @Override public void changed(ObservableValue<? extends Boolean> val, Boolean oldVal, Boolean newVal) {
                    Node fo = getScene().getFocusOwner();

                    if (!newVal) {
                        if (fo instanceof VirtualFlow) {
                            if (fo.getParent().getParent() != getTableView())
                                cancelEdit();
                        }
                        else
                            cancelEdit();
                    }
                }
            });

            Platform.runLater(new Runnable() {
                @Override public void run() {
                    tf.requestFocus();
                }
            });
        }
    }

    /** {@inheritDoc} */
    @Override public void cancelEdit() {
        if (cancelling)
            super.cancelEdit();
        else
            try {
                cancelling = true;

                if (hardCancel || curTxt.trim().isEmpty())
                    super.cancelEdit();
                else if (validator.valid(getTableView().getSelectionModel().getSelectedItem(), curTxt))
                    commitEdit(curTxt);
                else
                    super.cancelEdit();
            }
            finally {
                cancelling = false;
            }
    }
}

更新:此代码是作为Apache Ignite Schema Import GUI Utility的一部分编写的。 查看TableCell代码的完整版: https//github.com/apache/ignite/blob/ignite-1.9/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/Controls.java

你也可以构建这个实用程序(它是一个非常简单的实用程序,有2个屏幕),并在Java7 / javaFx2和Java8 / JavaFx8下使用它。

我测试过 - 它在两者下运行。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM