TableView.setEditable(true) Is Required
Getting The CheckboxTreeTableItem To Interact
I’m hoping this post will solve a late-night dilemma for some engineer that is trying to bring up their first JavaFX TableView
control that uses checkboxes.
In the end, getting the checkboxes of a CheckBoxTreeTableItem
to respond to user clicks in a TableView
requires calling the setEditable(true)
method on the TableView
Instance.
TableView tableView = new TableView();
tableView.setEditable(true);
I didn’t find this described explicitly in any of the online JavaFX documentation or the highly repetitive blog pages on the use of CheckBoxTreeTableItem
. Fortunately, one of the examples includes this call as a seemingly stray bit of code. In fact, it turned out to be essential.
Summary
The Depan application relies heavily on a data model that represents a list of graph nodes. Each node list is a subset of the nodes from a particular graph. Being able to manipulate lots of node lists is one of Depan’s strengths. The ability to manage and aggregate large sets of node lists provides considerable analytical power.
The development of the node list UI followed a simple evolutionary path. The initial node list viewer borrowed some code from the project viewer. That code was based on TreeView
. The addition of checkboxes transformed the original TreeCell
elements into CheckBoxTreeCell
elements. The final development step was placing the TreeView
into a TableView
. In this final step, the CheckBoxTreeCell
transforms into a CheckBoxTreeTableCell
.
After some refactoring work, the new UI was rendering beautifully. But the checkboxes were unresponsive. Only the tree expand and collapse arrows worked. The checkbox and its label were rendered but lacked the intended user interaction.
Flailing around yielded results. One blog on using the CheckBoxTreeTableCell
had a call to TreeView.setEditable(true)
in the control flow leading to the table’s rendering. Although the line is not well documented, this method to activate editable mode on the TableView
is essential for CheckBoxTreeTableCell
to operate.
The Story Begins
One of the essential tasks for system analysis is the partitioning of large node lists into smaller lists. A common goal for refactoring analysis is to find a collection of node lists that cover and partition a larger set of nodes. The definition of sublists from a large group of nodes often involves manual steps, even with effective automation.
The number of nodes in a complete system graph can be very large. UI presentations are lengthy. The content can be pages and pages lengthy, even with trees to cluster and group nodes that have membership relationships.
Maintaining the intended selection on a large number of scattered nodes can be error-prone. Even with multi-section multi-selection UIs, it can be hard to grow a large selection. A single mistake (not holding the shift key) can instantly undo much careful work. For this reason, the user's selection is maintained as a checkbox, not as the cursor selection state.
The UI With TreeView
The initial node list viewer borrowed some code from the project viewer. The project viewer was based on an internal TreeView
instance. The node list viewer follows a similar structure.
For TreeView
instances, the rendering of visible items is handled by TreeCell
instances that are managed by the TreeView
. With the basic TreeCell
class, the updateItem()
method needs to be overridden. This method sets the text and graphic that are rendered in the cell. It also allows the application to add context menus to cells. Starting with TreeCell
as the base class works fine, but there are no checkboxes.
To add checkboxes, the cells need to extend CheckBoxTreeCell
. This change in the base type meant releasing some control over the rendering. The updateItem()
method from CheckBoxTreeCell
handles the basic rendering of the data. Part of this is setting up the checkbox as the graphic for the cell, and the item’s label as the text.
Supporting this change in behavior required a new StringConverter
type to render the tree item’s label. The assignments of text and graph for the cell needed to be removed, since this behavior was now supplied by CheckBoxTreeCell
. In early revisions, setting the graphic to null was effectively erasing the checkbox. The cell rendering can’t be completely delegated to the CheckBoxTreeCell
class since the updateItem()
method also adds context menus.
Trees Into Table
Trees with checkboxes are great. However, the context is limited to the tree item’s label. With a TableView
, additional columns can provide supplementary information. There is also a column header with options for useful context menu entries (e.g. sort-by capabilities). This transition was more substantial than migrating to tree items with checkboxes.
Both the container and the cell definitions changed their base types. The TreeView
container becomes a TableView
container. The connection from the TableView
to the TableCell
is managed by a new entity, of type TreeTableColumn
. The CheckBoxTreeCell
class transitions to extend the CheckBoxTreeTableCell
class.
Where the migration for CheckBoxTreeCell
required little more than a new StringConverter
implementation, the migration to CheckBoxTreeTableCell
required more pieces:
The
CheckBoxTreeCell
adds a selection state factory for the cell, as aCallback<Integer, ObservableValue>
.The cell factory, and any injected values, migrate from
TreeView
into theTreeTableColumn
.The
TreeTableColumn
needs some additional configuration, primarily a value factory for cells.
Unresponsive CheckBoxes
With these changes, the table view for node lists renders nicely. The label shows, the checkbox shows, and the triangle for tree expansion opens and closes.
But there is no way for the user to check the checkbox.
The first inclination is to assume some error in the selection state factory. Although this is easy to get wrong, that is not the source of the problem.
To enable user interaction with the checkboxes, the TableView
needs to be in editable mode. This requires calling TableView.edit(true)
during the table view’s initialization stages. If you know where to look, it is that simple.
RTFM
The documentation for this interaction is subtle, at best. An after-the-fact review from Google search shows a few tidbits of information. The first one is vague, the second one feels misleading. The description of the TableView editable behavior is precise but it remains challenging to understand.
TreeView
Firstly, cell editing most commonly requires a different user interface than when a cell is not being edited. This is the responsibility of the Cell implementation being used. For TreeView, this is the responsibility of the cell factory. It is your choice whether the cell is permanently in an editing state (e.g. this is common for CheckBox cells), or to switch to a different UI when editing begins (e.g. when a double-click is received on a cell).
This side comment about editing mode for a TreeView
is repeated in the TableView
JavaDoc. It certainly does not seem essential.
BooleanProperty - editable Specifies whether this TreeView is editable - only if the TreeView and the TreeCells within it are both editable will a TreeCell be able to go into their editing state.
An interesting note about the behavior, but not very constructive. Apparently, TreeView
and TreeCell
components are normally initialized as editable. Everything behaved as expected without changes to this property.
Note that the CheckBoxTreeCell renders the CheckBox 'live', meaning that the CheckBox is always interactive and can be directly toggled by the user. This means that it is not necessary that the cell enter its editing state (usually by the user double-clicking on the cell). A side-effect of this is that the usual editing callbacks (such as on edit commit) will not be called. If you want to be notified of changes, it is recommended to directly observe the boolean properties that are manipulated by the CheckBox.
This paragraph provides a lot of details about the programmatic behavior of the checkbox. With all these details on the accessing the checkbox state, it is a surprise that there are so few details about the user interactions.
Firstly, cell editing most commonly requires a different user interface than when a cell is not being edited. This is the responsibility of the Cell implementation being used. For TableView, it is highly recommended that editing be per-TableColumn, rather than per row, as more often than not you want users to edit each column value differently, and this approach allows for editors specific to each column. It is your choice whether the cell is permanently in an editing state (e.g. this is common for CheckBox cells), or to switch to a different UI when editing begins (e.g. when a double-click is received on a cell).
This repeats the notes on cell editing notes from ListView, but the consequences are unclear. Checkboxes work fine in ListView without explicit manipulation of the editable state.
BooleanProperty editable
Specifies whether this TableView is editable - only if the TableView, the TableColumn (if applicable) and the TableCells within it are both editable will a TableCell be able to go into their editing state.
Given the nearly 60 methods that TableView
implements, slipping past the consequences of one method's interactions with the nested structures seems a very easy omission.
Apparently, all the other containing controls were initially created in the editable mode. Only the TableView
is initially created with the editable mode turned off. All the other components in this adventure, the various CheckBox*Cell
classes, the TreeTableColumn
class, and the ListView
class are all configured to be initially editable. Once this peculiar behavior is corrected for TableView
, the checkboxes in the table view have the desired behavior.