n247s February 2016

JavaFX weird (Key)EventBehavior

So I have been experimenting with it a litle bit with javaFX and I came across some rather weird behavior which might be linked to the TableView#edit() method.

I'll post a working example on the bottom of this post again, so you can see what exactually is happening on which cell (debuging included!).

I'll try to explain all the behavior myself, though its way easier to see it for yourself. Basically the events are messed up when using the TableView#edit() method.

1:

If you are using the contextMenu to add a new item, the keyEvents for the the keys 'escape' and 'Enter' (and propably the arrow keys, though I dont use them right now) are consumed before they fire the events on the Cells (e.g. textField and cell KeyEvents!) Though it is firing the keyEvent on the Parent node. (the AnchorPane in this case).

Now I know for a fact that these keys are captured and consumed by the contextMenu default behavior. Though it shouldn't be happening since the contextMenu is already hidden after the new item is added. further more the textField should recieve the events, especially when it is focused!

2:

When you use the button at the bottom of the TableView to add a new Item, The keyEvents are fired on the Parent node (the AnchorPane) and the Cell. Though the textField (even when focused) recieve no keyEvents at all. I cannot explain why the TextField wouldn't recieve any event even when typed in, so I assume that would definitely be a bug?

3:

When editing a cell through double click, it updates the editingCellProperty of the TableView correctly (which I check for several times). Though when start editing though the contextMenu Item (which only calls startEdit() for testpurpose) It doesnt update the editing state correctly! Funny enough it allows the keyEvents to continue as usual, unlike situation 1 & 2.

4:

When you edit an item, and then add an item (ei

Answers


James_D February 2016

This is kind of the poster child for Josh Bloch's "Inheritance breaks Encapsulation" mantra. What I mean by that is that when you create a subclass of an existing class (TableCell in this case), you need to know a lot about the implementation of that class in order to make the subclass play nicely with the superclass. You make a lot of assumptions in your code about the interaction between the TableView and its cells that are not true, and that (along with some bugs and general weird implementations of event handling in some controls) is why your code is breaking.

I don't think I can address every single issue, but I can give some general pointers here and provide what I think is working code that achieves what you are trying to achieve.

First, cells are reused. This is a good thing, because it makes the table perform very efficiently when there is a large amount of data, but it makes it complicated. The basic idea is that cells are essentially only created for the visible items in the table. As the user scrolls around, or as the table content changes, cells that are no longer needed are reused for different items that become visible. This massively saves on memory consumption and CPU time (if used properly). In order to be able to improve the implementation, the JavaFX team deliberately don't specify how this works, and how and when cells are likely to be reused. So you have to be careful about making assumptions about the continuity of the item or index fields of a cell (and conversely, which cell is assigned to a given item or index), particularly if you change the structure of the table.

What you are basically guaranteed is:

  • Any time the cell is reused for a different item, the updateItem() method is invoked before the cell is rendered.
  • Any time the index of the cell changes (which may be because an item is inserted in the list, or may be because the cell is reused


kleopatra February 2016

My big learn item of the day (freely summarized and slightly extended from James' answer):

view.edit(...) is safe to call only if all cells are in a stable state and the target cell is visible. Most of the time we can force the stable state by calling view.layout()

Below is yet another example to play with:

  • as already mentioned in one of my comments, it differs from James' in starting the edit in a listener to the items: might not always be the best place, has the advantage of a single location (at least as far as list mutations are involved) for the layout call. A drawback is that we need to be certain that the viewSkin's listener to the items is called before ours. To guarantee that, our own listener is re/registered whenever the skin changes.

  • as an exercise in re-use, I extended TextFieldTableCell to additionally handle the button/menu and update the cell's editability based on the row item.

  • there are also buttons outside the table to experiment with: addAndEdit and scrollAndEdit. The latter is to demonstrate that "instable cell state" can be reached by paths different from modifying the items.

Currently, I tend to subclass TableView and override its edit(...) to force the re-layout. Something like:

public static class TTableView<S> extends TableView<S> {

    /**
     * Overridden to force a layout before calling super.
     */
    @Override
    public void edit(int row, TableColumn<S, ?> column) {
        layout();
        super.edit(row, column);
    }

}

Doing, relieves the burden on client code. What's left for them is to make sure the target cell is scrolled into the visible area, though.

The example:

public class TablePersonAddRowAndEdit extends Application {

    private PersonStandIn standIn = new Per 

Post Status

Asked in February 2016
Viewed 3,287 times
Voted 11
Answered 2 times

Search




Leave an answer