15

TreeTableView (または TableView) がフォーカスを失ったときに値をコミットしようとする簡単な方法はありますか?

残念ながら、私は javafx TableCellFactories のデフォルト実装で成功しませんでした。そのため、独自の TreeTableCell 実装と、Graham Smithのもののようないくつかの異なる tableCell 実装を試しました。これは、既にフックを実装しているため、最も簡単に思えました。フォーカスが失われますが、値はコミットされず、ユーザーの変更は元の値にリセットされます。

私の推測では、フォーカスが失われるたびに、影響を受けるセルの editProperty は常に既に false であるため、セルが 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 などのキーでコミットすると、値が 2 回コミットされます (キー + フォーカスが失われた場合)。さらに、私は自分のアプローチがまったく好きではないので、他の誰かがこれを「より良い」方法で解決したのではないかと思いますか?

4

7 に答える 7

5

私もこの機能が必要で、いくつかの研究を行いました。上記の 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()));
                }
            }
        });
       ...
    }
    ...
}

セル ファクトリでは、コミットされた値をオブジェクトに保存するか、オブジェクトを永続化するために必要なことを行います。

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;
                        });
            }
        });

これまでのところ、この回避策で問題を見つけることはできませんでした。一方、私はまだこれについて広範なテストを行っていません。

編集:まあ、いくつかのテストが気づいた後、回避策はテーブルビューのビッグデータでうまく機能していましたが、空のテーブルビューセルでは、フォーカスが失われた後、再度ダブルクリックしたときにのみ更新されませんでした. テーブルビューを更新する方法はありますが、ハッキングが多すぎます...

EDIT2: setItem(getTextArea().getText()); を追加しました。コールバックを呼び出す前に -> 空のテーブルビューでも動作します。

于 2014-12-08T19:07:21.467 に答える
0

あなたは必要になるでしょう:

  • CellEditor; _
  • TableCellまたはTreeCellサブクラス。と
  • セルファクトリー製法。

以下に示すクラスの詳細については、以下を参照してください。

セルエディター

は、と、およびフォーカスの喪失CellEditorを処理します。EscEnter

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.input.KeyEvent;

import java.util.function.Consumer;

import static javafx.application.Platform.runLater;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.TAB;
import static javafx.scene.input.KeyEvent.KEY_RELEASED;

public class CellEditor {
  private FocusListener mFocusListener;
  private final Property<String> mInputText = new SimpleStringProperty();
  private final Consumer<String> mConsumer;

  /**
   * Responsible for accepting the text when users press the Enter or Tab key.
   */
  private class KeyHandler implements EventHandler<KeyEvent> {
    @Override
    public void handle( final KeyEvent event ) {
      if( event.getCode() == ENTER || event.getCode() == TAB ) {
        commitEdit();
        event.consume();
      }
    }
  }

  /**
   * Responsible for committing edits when focus is lost. This will also
   * deselect the input field when focus is gained so that typing text won't
   * overwrite the entire existing text.
   */
  private class FocusListener implements ChangeListener<Boolean> {
    private final TextField mInput;

    private FocusListener( final TextField input ) {
      mInput = input;
    }

    @Override
    public void changed(
      final ObservableValue<? extends Boolean> c,
      final Boolean endedFocus, final Boolean beganFocus ) {

      if( beganFocus ) {
        runLater( mInput::deselect );
      }
      else if( endedFocus ) {
        commitEdit();
      }
    }
  }

  /**
   * Generalized cell editor suitable for use with {@link TableCell} or
   * {@link TreeCell} instances.
   *
   * @param consumer        Converts the field input text to the required
   *                        data type.
   * @param graphicProperty Defines the graphical user input field.
   */
  public CellEditor(
    final Consumer<String> consumer,
    final ObjectProperty<Node> graphicProperty ) {
    assert consumer != null;
    mConsumer = consumer;

    init( graphicProperty );
  }

  private void init( final ObjectProperty<Node> graphicProperty ) {
    final var keyHandler = new KeyHandler();

    // When the text field is added as the graphics context, we hook into
    // the changed value to get a handle on the text field. From there it is
    // possible to add change the keyboard and focus behaviours.
    graphicProperty.addListener( ( c, o, n ) -> {
      if( o instanceof TextField ) {
        o.removeEventHandler( KEY_RELEASED, keyHandler );
        o.focusedProperty().removeListener( mFocusListener );
      }

      if( n instanceof final TextField input ) {
        n.addEventFilter( KEY_RELEASED, keyHandler );
        mInputText.bind( input.textProperty() );
        mFocusListener = new FocusListener( input );
        n.focusedProperty().addListener( mFocusListener );
      }
    } );
  }

  private void commitEdit() {
    mConsumer.accept( mInputText.getValue() );
  }
}

AltTableCell

AltTableCellanと anの唯一の違いAltTreeCellは、継承階層です。それ以外は同じです:

public class AltTableCell<S, T> extends TextFieldTableCell<S, T> {
  public AltTableCell( final StringConverter<T> converter ) {
    super( converter );

    assert converter != null;

    new CellEditor(
      input -> commitEdit( getConverter().fromString( input ) ),
      graphicProperty()
    );
  }
}

具体的には、次のAltTreeCellように始まります。

public class AltTreeCell<T> extends TextFieldTreeCell<T>

セルファクトリー方式

代替テーブル セルをテーブル列のセル ファクトリに割り当てます。

final var column = new TableColumn<Entry<K, V>, T>( label );

column.setEditable( true );
column.setCellFactory(
  tableColumn -> new AltTableCell<>(
    new StringConverter<>() {
      @Override
      public String toString( final T object ) {
        return object.toString();
      }

      @Override
      public T fromString( final String string ) {
        return (T) string;
      }
    }
  )
);

ツリー セルの場合は、かなり似ています。

final var view = new TreeView<>(); // ...

view.setEditable( true );
view.setCellFactory( treeView -> new AltTreeCell<>( converter ) );
于 2021-12-21T06:40:30.883 に答える