Yuck, ‘CheckBoxTreeView’ – what a mouthful. Anywho – I was just looking at a very interesting JavaFX session slidedeck from Jazoon presented by AdNovum Informatik AG titled ‘JavaFX – The Condemned Live Longer‘. It is a great slide deck as they are a company that have invested a lot of time researching the available RIA technologies, and they are pretty upfront with what they like and don’t like in JavaFX, and what their developer experience has been.
One of their comments was that they wish JavaFX had a CheckBoxTreeView. Seeing as I developed the TreeView control in 1.3 I thought I might be in a good position to answer their wish. What I’ve got here today is not perfect, but then it only took 10 minutes to create (in fact this blog post is taking me longer to write!). This is also not a complete example – you’d want to improve it to allow for the state to be set based on a value contained within the cell item, rather than just ‘floating’ as it does here – you’ll get scrolling oddities if you just use the code below as-is.
Here is the code you should add to your project:
[jfx]
import javafx.geometry.Insets;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import com.javafx.preview.control.TreeCell;
import javafx.geometry.VPos;
public class CheckBoxTreeCell extends TreeCell {
public var onSelected:function(item:Object, selected:Boolean):Void;
var checkBox:CheckBox;
var cbSelected = bind checkBox.selected on replace {
onSelected(item, cbSelected);
}
override var node = HBox {
padding: Insets { left: 7 }
nodeVPos: VPos.CENTER
content: [
checkBox = CheckBox { }
Label {
text: bind if (item == null) then "" else
if (item instanceof String)
then item as String else "{item}";
}
]
}
override var onUpdate = function() {
disclosureNode.visible = not empty and not treeItem.leaf;
}
}
[/jfx]
To use this class, just do something like the following:
[jfx]
var tree = TreeView {
root: …
cellFactory: function():TreeCell {
def cell:TreeCell = CheckBoxTreeCell {
onSelected: function(item:Object, selected:Boolean) {
println("Toggled {item} to {selected}");
}
}
}
}
[/jfx]
You’ll see in this code snippet we are using our CheckBoxTreeCell class, and define an onSelected function callback to be informed when the selection of the CheckBox changes. Of course in your implementation you should do something more useful than just doing a println 🙂 .
In my test program, I get the following appear on screen:
So, there we go. It’s not entirely perfect – I haven’t bothered to perfect the alignment and positioning (it could do with some tweaking). I’m also not completely happy with the onSelected
function name, but this is trivial to reconsider at a future date. I hope that perhaps this is helpful to some people, and perhaps we can integrate something like this into a future release of JavaFX – but no promises on that front. Finally, I hope it shows just how powerful the cell architecture is in JavaFX ListView and TreeView controls.
Technically I’m getting more and more impressed with JavaFX. It’s really well thought through with all the lessons learned from Swing. I really hope it will break through in the RIA / TV / Mobile world.
I really like JavaFX, but I find it disturbing that most examples do not include something functional on the page. Many may include a webstart link, but very few embed it as an applet. If they do, it requires a click to load. If the guys writing the examples don’t feel comfortable with page load times when including their examples, will the rest of the world?
Nice example. I have a check box tree view using this cellFactory definition:
var cb:CheckBox;
def cell:TreeCell = TreeCell {
node: cb = CheckBox {text: bind (cell.item as String) visible: bind (cell.item != null)
onMouseClicked: function (e) {
if (cell.treeItem.leaf == true) {
if (cb.selected == true) {
insert (cell.treeItem as RunSetupMediator.SubrequirementTreeItem).subrequirementResourceID into targetCategoryViewSubrequirementsSelected;
}
else {
delete (cell.treeItem as RunSetupMediator.SubrequirementTreeItem).subrequirementResourceID from targetCategoryViewSubrequirementsSelected;
}
}
else {
if (cb.selected == true) {
cb.selected = false;
// Need to go from treeItem to TreeCell ??????
}
}
}
}
onUpdate: function() {
cell.disclosureNode.visible = (cell.item != null) and not cell.treeItem.leaf;
}
}
Question: TreeCell has a reference to TreeItem and TreeView. How does one navigate from TreeView/TreeItem to it’s corresponding TreeCells? What I would like to do is create a TreeNodeCheckBox such that when it is checked, all its children become checked.
Thanks.
@Bobby: You can’t do what you’re wanting to do, as it’s not possible for you to retrieve the TreeCells using public API. This is because internally we reuse TreeCell nodes to minimise memory usage.
One possible workaround would be to bind the CheckBoxTreeCell.selected property (from my example in this post) to be not only related to the checkbox selection, but also the parent selection (assuming you’ve created a TreeItem subclass that contains the selection state – which I don’t show here). This would take a bit of work (with mouse and key events) to ensure the correct value is used, but from it you should be able to get the result you’re wanting.
I may take a further look into this suggestion shortly to see if I can clarify my point. I also know Richard Bair improved upon my proof of concept above, so perhaps he might want to add a comment.
— Jonathan
Jonathan, thanks for the response. I have a simple two level tree view. For this tree view, I have found a solution that works. I subclass TreeItem, like you suggested. The subclass has a reference to the checkbox for that particular cell. The TreeItem instance gets the reference to the checkbox in the onUpdate function of TreeCell.
class SubrequirementTreeItem extends TreeItem {
var subrequirementResourceID:Integer;
var checkBox:CheckBox;
}
override var cellFactory = function():TreeCell {
var cb:CheckBox;
def cell:TreeCell = TreeCell {
node: cb = CheckBox {text: bind (cell.item as String) visible: bind (cell.item != null)
onMouseClicked: function (e) {
if (cell.treeItem.leaf == true) {
if (cb.selected == true) {
insert (cell.treeItem as RunSetupMediator.SubrequirementTreeItem).subrequirementResourceID into targetCategoryViewSubrequirementsSelected;
}
else {
delete (cell.treeItem as RunSetupMediator.SubrequirementTreeItem).subrequirementResourceID from targetCategoryViewSubrequirementsSelected;
}
}
else {
if (cb.selected == true) {
for (child in cell.treeItem.children) {
(child as RunSetupMediator.SubrequirementTreeItem).checkBox.selected = true;
insert (child as RunSetupMediator.SubrequirementTreeItem).subrequirementResourceID into targetCategoryViewSubrequirementsSelected;
}
}
else {
for (child in cell.treeItem.children) {
(child as RunSetupMediator.SubrequirementTreeItem).checkBox.selected = false;
delete (child as RunSetupMediator.SubrequirementTreeItem).subrequirementResourceID from targetCategoryViewSubrequirementsSelected;
}
}
}
}
}
onUpdate: function() {
if (cell.treeItem.leaf) {
(cell.treeItem as RunSetupMediator.SubrequirementTreeItem).checkBox = cell.node as CheckBox;
}
cell.disclosureNode.visible = (cell.item != null) and not cell.treeItem.leaf;
}
};
}
The problem with the code is that if you expand or contract the tree items, the checkbox states change. I suppose that’s because the TreeCells (which contain the checkboxes) are reused as the tree changes. This solution (as well as Bobby’s in the comments) only works if you leave the tree in its expanded or contracted state, and don’t expand or contract any parts of it. Any way around this, Jonathan?
I check JavaFX once every year. I agree with Les – practically all frameworks have online demos. JavaFX – only screenshots. It seems that creators of JavaFX are not interested at all in adoption of their product. I am outta here…