Reputation: 15632
Having put a "standard" editor on column 0 of a TreeTableView by doing this:
treeTableView.columns.get(0).setCellFactory( TextFieldTreeTableCell.forTreeTableColumn());
... it's nice to find that a couple of things start an edit of a TreeItem
: one is to click the cell, and another is to press F2.
To my slight dismay, however, if I start up the app and, without having selected a TreeItem
using the mouse, but having programmatically selected the first child of the root TreeItem
, if I then (possibly after navigating using keyboard keys only) press F2, the JavaFX internals throw an NPE, which looks like this:
java.lang.NullPointerException: null
at com.sun.javafx.scene.control.behavior.TableViewBehaviorBase.activate(TableViewBehaviorBase.java:890)
at com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$KeyHandler.process(Scene.java:4058)
at javafx.scene.Scene$KeyHandler.access$1500(Scene.java:4004)
at javafx.scene.Scene.processKeyEvent(Scene.java:2121)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2595)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$1(GlassViewEventHandler.java:248)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:390)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
at com.sun.glass.ui.View.handleKeyEvent(View.java:547)
at com.sun.glass.ui.View.notifyKey(View.java:971)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:277)
It can't be that the mouse puts the focus directly on the TreeItem
because TreeItem
's super class is Object
. I put a focus owner listener on the Scene
, which confirms that nothing focus-related changes when you click on the TreeTableView
. And yet, qualitatively, something does change.
Maybe it's something to do with Behavior
, InputMap
, "skins", or any one of many mysterious JavaFX things of which I am ignorant.
Under these circumstances, it seems to me that one option might be to intercept the F2 keystroke somehow, and prevent the default functioning, and also programmatically instigate editing of the TreeItem
which is selected but which doesn't have focus.
NB I put a "key pressed" and "key released" event listeners on the TreeTableView
. These included the following line:
TreeTablePosition pos = treeTableView.getFocusModel().getFocusedCell();
The "anomalous" event I'm describing, where the JavaFX internals throw an NPE, is characterised by, in the "key release" listener, pos.col == -1
and pos.getTableColumn() == null
. I can also tell, from logging, that the NPE happens before the "key released" handler responds.
For information, the "key pressed" listener never logs anything when I press F2 without having first mouse-clicked a TreeItem
(due no doubt to the JavaFX internal Exception
killing off the normal "broadcast" to listeners), leading me to suppose that the JavaFX framework also precedes any user-added key-press listener.
Upvotes: 1
Views: 433
Reputation: 635
The given answer covers only part of the problem: e.g. if you click with the mouse behind the last column and press F2 then startEdit will still throw the NPE.
I solved this problem in my project by installing a focus listener which selects the default column whenever there is no valid column focused (for whatever reason):
//guarantee that always a valid column is selected to avoid NPE during startEdit
tableView.getFocusModel().focusedCellProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.getTableColumn() == null){
tableView.getSelectionModel().select(newValue.getRow(), defaultColumn);
}
});
E.g. if defaultColumn should be the first column you can set
defaultColumn = tableView.getColumns.get(0)
Upvotes: 1
Reputation: 45756
If you select your cell using TableSelectionModel#select(int,TableColumnBase)
then the NPE does not occur.
Selects the cell at the given row/column intersection. If the table control is in its 'cell selection' mode (where individual cells can be selected, rather than entire rows), and if the column argument is null, this method should select all cells in the given row.
For example:
TreeTableColumn col = treeTableView.getColumns().get(0);
treeTableView.getSelectionModel().select(rowIndex, col);
With the above you don't have to worry about the focus model. The reason for that is because the default selection model implementation will focus the given row+column (i.e. cell) for you (not sure if that's guaranteed behavior but it's certainly polite behavior).
Basically, your current code caused a row to be selected/focused but not a specific cell in that row. Thus when you tried to enter "edit mode" there was no way to determine which cell to target. I would consider the NPE to be a bug. Not sure what's meant to happen when no column is selected/focused, but the control's behavior should handle that case gracefully.
If interested, below was my debugging process.
Here's the code responsible for the NPE:
protected void activate(KeyEvent e) {
TableSelectionModel sm = getSelectionModel();
if (sm == null) return;
TableFocusModel fm = getFocusModel();
if (fm == null) return;
TablePositionBase<TC> cell = getFocusedCell();
sm.select(cell.getRow(), cell.getTableColumn());
setAnchor(cell);
// check if we are editable
boolean isEditable = isControlEditable() && cell.getTableColumn().isEditable();
// edit this row also
if (isEditable && cell.getRow() >= 0) {
editCell(cell.getRow(), cell.getTableColumn());
e.consume();
}
}
Specifically, this is the problematic line:
boolean isEditable = isControlEditable() && cell.getTableColumn().isEditable();
Which means either cell
is null or getTableColumn()
is returning null. After some debugging with the help of JEP 358: Helpful NullPointerExceptions we know it's the latter:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException: Cannot invoke "javafx.scene.control.TableColumnBase.isEditable()" because the return value of "javafx.scene.control.TablePositionBase.getTableColumn()" is null
at javafx.controls/com.sun.javafx.scene.control.behavior.TableViewBehaviorBase.activate(TableViewBehaviorBase.java:898)
If getTableColumn()
is returning null then that means that, as far as the focus model is concerned, there is no column focused and thus no specific cell is focused. The fix is to select/focus a specific row+column (i.e. cell) and not just an entire row.
Upvotes: 2
Reputation: 15632
Found the answer (or an answer). There is in fact a focus system internal to the TreeTableView
... see getFocusModel()
. It appears that changes in this are not picked up by Stage
's focus owner mechanism:
stage.getScene().focusOwnerProperty().addListener( new ChangeListener(){
...
So what you have to do is make the internal focus model change whenever selection changes:
treeTableView.getSelectionModel().getSelectedItems().addListener(new ListChangeListener() {
void onChanged(ListChangeListener.Change c) {
TreeItem newSelectedItem = c.list.get(0);
int row = treeTableView.getRow( newSelectedItem );
treeTableView.getFocusModel().focus( row, treeTableView.getColumns().get(0) );
}
}
There are one or two points to be made about this:
c.list.get( 0 )
can always be guaranteed to deliver the newly selected item... and this is particularly true if selection mode is multiple selectionTreeItem
from column 0 returned. In a normal table you can of course select a block of cells at random. I'm wondering now whether you can do this with this control, or whether all selection is really "row selection".Upvotes: 0