15

Is there any simple approach to let the TreeTableView (or TableView) try to commit values on focus lost?

Unfortunatly I didn't succed with any default-implementations of javafx TableCellFactories, which is why I tried my own TreeTableCell implementations and also some different tableCell implementations like the one from Graham Smith, which seemed the most straight forward, since it already implemented a hook for focus lost, but nevertheless the value is never committed and the userchanges are resetted to the original value.

My guess is, whenever focus is lost, the editingProperty of the affected Cell is always already false which causes the Cell never to commit a value on focusLost. Here the relevant part from the original (oracle-)TreeTableCell Implementation (8u20ea), which causes my approaches to fail:

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

I succeded with overriding this method and commiting the value "by hand" before the original commitEdit() method is called, but this causes the commit on keys like enter to commit the value twice (on key + on focus lost). Moreover I dont really like my approach at all, so I wonder, if anyone else has solved this in a "nicer" way?

Uluk Biy
  • 48,655
  • 13
  • 146
  • 153
crusam
  • 6,140
  • 6
  • 40
  • 68
  • The linked tutorial and the official tableview example (Example 12-11 Alternative Solution Of Cell Editing) use the approach of adding listener to textField.focusedProperty(). What do you mean by that approach is not working as I hoped? And which "editingProperty" are you mentioning? – Uluk Biy Jul 11 '14 at 12:50
  • The offical example is unfortunatly not working (at least since 8u5). The value is only commited, when you leave the cell with pressing enter, but not due to focus lost. With not working as I hoped I just mean it doens`t work at all. It just sets the old value back again when focus is lost instead of commiting the value. – crusam Jul 11 '14 at 13:12
  • the reason is (at least I believe that) the editing Property, which is already set to editing=false, when focus is lost. therefore commitEdit can never succeed, since the cell wont be in editing state anymore. Editing is a boolean property of javafx.scene.control.Cell and therefore inherited to all Cell-Implementations (see for example at the first if-check in commitEdit()). – crusam Jul 11 '14 at 13:15
  • I have tested the official Example 12-11 Alternative Solution Of Cell Editing on JavaFX 8.0.5-b13, and it is committing the changes on focus lost (But note that it is not committing if focus is lost by clicking another item of tableview, which another problem). – Uluk Biy Jul 11 '14 at 13:58
  • By the way I was on Win7. What is yours? – Uluk Biy Jul 11 '14 at 13:59
  • First of all thanks for your interest. I am not sure, if I really understand the difference between committing the value due to loosing focus and loosing focus by clicking another item of tableview. Maybe I get something wrong, would you mind explaining the difference? The only thing I try to achieve is, whereever the user clicks in the application, while editing a cell, the tableview should try to commit the current value in the textfield and closing the editmode. I am also using Win7. – crusam Jul 11 '14 at 16:01
  • Did you run Example 12-11? Double click the cell then edit it. Click to empty space (where there is no item) of tableview. The changes should be committed. Just to be sure, check the version of javafx by printing out the com.sun.javafx.runtime.VersionInfo.getRuntimeVersion();. – Uluk Biy Jul 11 '14 at 17:52
  • Yeah, I did run it. But I am pretty sure I clicked a different cell instead of free space, will test it again as soon as I can. So what you are saying is: there is a difference between "the user clicking on a empty space" and "the user clicks on a different cell of the table", while editing an entry of the tableview? Is there any possibility to handle these identical? I'd like the tableview to react the same. In both cases, the table should try to commit the changes in the cell, no matter where the user is going to click next. – crusam Jul 11 '14 at 18:13
  • When one clicks outside the current cell `cancelEdit` gets called and calling `commitEdit` has no effect due to `TableCell.isEditing` returning false. Is your issue related to [mine](http://stackoverflow.com/questions/23632884/how-to-commit-when-clicking-outside-an-editable-tableview-cell-in-javafx)? There is a [jira issue](https://javafx-jira.kenai.com/browse/RT-18492) for something like this dating back to december 2011. – user555 Jul 13 '14 at 23:26
  • I fail to understand why committing when clicking outside the cell is not the default method of the the tableView API. No table implementation I've seen behaves in this way, not to mention how bad this is from a UI and usability perspective. – user555 Jul 13 '14 at 23:29
  • You are right, I also can`t understand, why the default behaviour is chosen to be like this. Unfortunatly the focuslistener approach doest work anymore. I am not sure how to provide a satisfying user experience in this case. My current approach is not really the way to go there I think. Thank you for pointing me to the jira issue. – crusam Jul 14 '14 at 08:34

7 Answers7

13

After some digging, turned out that the culprit (aka: the collaborator that cancels the edit before the textField looses focus) is the TableCellBehaviour/Base in its processing of a mousePressed:

  • mousePressed calls simpleSelect(..)
  • on detecting a single click it calls edit(-1, null)
  • which calls the same method on TableView
  • which sets its editingCell property to null
  • a tableCell listens to that property and reacts by canceling its own edit

Unfortunately, a hackaround requires 3 collaborators

  • a TableView with additional api to terminate an edit
  • a TableCellBehaviour with overridden simpleSelect(...) that calls the additional api (instead of edit(-1..)) before calling super
  • a TableCell that is configured with the extended behaviour and is aware of table's extended properties

Some code snippets (full code) :

// 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's 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);
}

Note: the implementation of TableCellBehaviour changed massively between jdk8u5 and jdk8u20 (joys of hacking - not fit for production use ;-) - the method to override in the latter is handleClicks(..)

BTW: massive votingfor JDK-8089514 (was RT-18492 in old jira) might speed up a core fix. Unfortunately, at least the author role is needed to vote/comment bugs in the new tracker.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • Your approach is far better then mine, so after testing your example, I`ll implement it soon in our application. A real fix (if ever) will proberbly still take some time, so your "hackaround" is really appreciated. Thanks for providing a working solution. – crusam Aug 13 '14 at 16:47
  • @kleopatra I may need to use your code, is your XTableView a dropin replacement for a TableView? I'm new to FX and using FXML and it'd be nice if I could just toss an X infront of things and add and import. – Captain Prinny Jul 09 '15 at 18:41
  • @CaptainPrinny should be, not tested, though :-) And beware: the details of the hack are (most probably) highly version dependent - had already to change it once (see bug report) – kleopatra Jul 10 '15 at 09:32
  • @kleopatra well, I'm currently using a TableView as data interface, but it's clunky because of this... Is there any simpler way to allow for an on_tab_save and move right/wrap, or is it fundamentally reliant on the spirit of your hack? – Captain Prinny Jul 10 '15 at 14:23
  • 1
    @CaptainPrinny as long the issue isn't fixed in core, there's rarely _anything_ to do, and nothing simpler (as far as I know) - vote for the issue and start/keep bickering with the javafx team :-) – kleopatra Jul 10 '15 at 14:34
  • 1
    After JavaFX issue tracker migration this is the new link for [RT-18492](https://bugs.openjdk.java.net/browse/JDK-8089514). I couldn't find a way to register nor comment nor vote so tough luck, this is set as _minor loss of function... where easy workaround is present_, open for 3.5 years and scheduled for java 9, what a joke – Andrea Vacondio Sep 24 '15 at 13:01
  • @AndreaVacondio unfortunately, you'll need the author role in openjdk to be able to vote ... thanks for the new link! – kleopatra Sep 24 '15 at 13:08
  • Is it possible to implement this with an editable list view? – joshLor Nov 29 '17 at 02:25
6

I also needed this functionality and did some study. I faced some stability issues with XTableView hacking mentioned above.

As problem seems to be commitEdit() won't take effect when focus is lost, why you don't just call your own commit callback from TableCell as follows:

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

In cell factory, you just store committed value to the object or do whatever necessary to make it permanent:

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

So far, I have not been able to find any problems with this workaround. On the other hand, I haven't been yet done extensive testing on this either.

EDIT: Well, after some testing noticed, the workaround was working well with big data in tableview but with empty tableview cell was not getting updated after focus lost, only when double clicking it again. There would be ways to refresh table view but that too much hacking for me...

EDIT2: Added setItem(getTextArea().getText()); before calling callback -> works with empty tableview as well.

keyo
  • 91
  • 1
  • 5
1

With reservation of this being a dumb suggestion. Seems too easy. But why don't you just override TableCell#cancelEdit() and save the values manually when it is invoked? When the cell loses focus, cancelEdit() is always invoked to cancel the edit.

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

I don't know. Maybe I'm missing something. But it seems to work for me.

Update: Added cancel edit functionality. You can now cancel the edit by pressing escape while focusing the text field. Also added so that you can save the edit by pressing enter while focusing the textfield.

Jonatan Stenbacka
  • 1,824
  • 2
  • 23
  • 48
  • 1
    well, then the user can't ever _cancel_ an edit :-) – kleopatra Oct 29 '15 at 09:48
  • `super.cancelEdit()` is called, so the cell does transition from an editing to a non-editing state. But If you're talking about literarly canceling the edit: If you save the edit when the cell loses focus, which is the normal way to cancel an edit, how can you cancel an edit without losing focus? I guess you could bind it to the escape key or something, and then just store the original text while editing. Pressing escape would then just set the textfield to the original text and then invoke `cancelEdit()`. – Jonatan Stenbacka Oct 29 '15 at 10:27
  • 1
    guilty not having read the code to the end ... in cancelEdit you by-pass the edit mechanism completely by setting the value manually to the backing item. Doing so leads to having 2 parallel paths of a commit: the "normal" which passes through the editHandlers and the "forced" which doesn't. Might work in a tightly controllable context (as in yours) but might introduce nasty issues in others. That said: as long as we _have to_ rely on hacks, one hack is as bad as another :-) – kleopatra Oct 29 '15 at 14:00
1

I prefer building as much as possible on the existing code, and since this behaviour is still not fixed w/ Java 10, here's a more general approach based on J. Duke's solution from bug: JDK-8089311.

public class TextFieldTableCellAutoCmt<S, T> extends TextFieldTableCell<S, T> {

    protected TextField txtFldRef;
    protected boolean isEdit;

    public TextFieldTableCellAutoCmt() {
        this(null);
    }

    public TextFieldTableCellAutoCmt(final StringConverter<T> conv) {
        super(conv);
    }

    public static <S> Callback<TableColumn<S, String>, TableCell<S, String>> forTableColumn() {
        return forTableColumn(new DefaultStringConverter());
    }

    public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> forTableColumn(final StringConverter<T> conv) {
        return list -> new TextFieldTableCellAutoCmt<S, T>(conv);
    }

    @Override
    public void startEdit() {
        super.startEdit();
        isEdit = true;
        if (updTxtFldRef()) {
            txtFldRef.focusedProperty().addListener(this::onFocusChg);
            txtFldRef.setOnKeyPressed(this::onKeyPrs);
        }
    }

    /**
     * @return whether {@link #txtFldRef} has been changed
     */
    protected boolean updTxtFldRef() {
        final Node g = getGraphic();
        final boolean isUpd = g != null && txtFldRef != g;
        if (isUpd) {
            txtFldRef = g instanceof TextField ? (TextField) g : null;
        }
        return isUpd;
    }

    @Override
    public void commitEdit(final T valNew) {
        if (isEditing()) {
            super.commitEdit(valNew);
        } else {
            final TableView<S> tbl = getTableView();
            if (tbl != null) {
                final TablePosition<S, T> pos = new TablePosition<>(tbl, getTableRow().getIndex(), getTableColumn()); // instead of tbl.getEditingCell()
                final CellEditEvent<S, T> ev  = new CellEditEvent<>(tbl, pos, TableColumn.editCommitEvent(), valNew);
                Event.fireEvent(getTableColumn(), ev);
            }
            updateItem(valNew, false);
            if (tbl != null) {
                tbl.edit(-1, null);
            }
            // TODO ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tbl);
        }
    }

    public void onFocusChg(final ObservableValue<? extends Boolean> obs, final boolean v0, final boolean v1) {
        if (isEdit && !v1) {
            commitEdit(getConverter().fromString(txtFldRef.getText()));
        }
    }

    protected void onKeyPrs(final KeyEvent e) {
        switch (e.getCode()) {
        case ESCAPE:
            isEdit = false;
            cancelEdit(); // see CellUtils#createTextField(...)
            e.consume();
            break;
        case TAB:
            if (e.isShiftDown()) {
                getTableView().getSelectionModel().selectPrevious();
            } else {
                getTableView().getSelectionModel().selectNext();
            }
            e.consume();
            break;
        case UP:
            getTableView().getSelectionModel().selectAboveCell();
            e.consume();
            break;
        case DOWN:
            getTableView().getSelectionModel().selectBelowCell();
            e.consume();
            break;
        default:
            break;
        }
    }
}
Ogmios
  • 646
  • 7
  • 12
0

Since TextFieldTableCell is missing functionality (as reckoned in the JDK bug tracker), an alternative solution may work. Forget about TextFieldTableCell and use a custom TableCell class with a TextField in it. The custom 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 );
        }
    }
}

The UI display might differ from a TextFieldTableCell but at least, it allows for better usability:

UI Display

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
A. Karali
  • 19
  • 4
0

I found a simple solution to this, one just needs to provide the commit function to column specific to data type:

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());
}
});
Grigor Nazaryan
  • 567
  • 5
  • 18
0

You'll need:

  • a CellEditor;
  • a TableCell or TreeCell subclass; and
  • a cell factory method.

For more details on the classes shown below, see:

CellEditor

The CellEditor is responsible for handling the Esc and Enter, as well as focus loss:

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

The only difference between an AltTableCell and an AltTreeCell is the inheritance hierarchy. They are otherwise identical:

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

To be concrete, the AltTreeCell would begin:

public class AltTreeCell<T> extends TextFieldTreeCell<T>

Cell factory method

Assign the alternate table cell to the table column's cell factory:

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

For a tree cell, it's fairly similar:

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

view.setEditable( true );
view.setCellFactory( treeView -> new AltTreeCell<>( converter ) );
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315