The CheckBox in JavaFX can be configured for two states (selected, or not) or three states (selected, unselected, or indeterminate). This indeterminate state is often useful when a checkbox is being used in a TreeView, for example. You might be implementing a tree view showing which features are installed, and need to toggle to an indeterminate state for the branch of some of the children are selected, and some are not.
In JavaFX, putting the CheckBox into a mode where it can be indeterminate is quite simple, simply set the allowIndeterminate property to true:
CheckBox cb = new CheckBox("Indeterminate CheckBox"); cb.setAllowIndeterminate(true);
You manipulate the state of the check box through the use of two properties: selected and indeterminate. If allowIndeterminate is true, then as the user clicks on the check box, it will cycle through three state combinations:
- selected = true, indeterminate = false
- selected = false, indeterminate = true
- selected = false, indeterminate = false
Here is a full example:
import javafx.application.Application; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class IndeterminateCheckBox extends Application { Label label; CheckBox cbox; @Override public void start(Stage stage) { label = new Label(); cbox = new CheckBox("Indeterminate CheckBox"); cbox.setIndeterminate(true); cbox.setAllowIndeterminate(true); ChangeListener<Boolean> listener = new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> prop, Boolean old, Boolean val) { updateLabel(); } }; cbox.selectedProperty().addListener(listener); cbox.indeterminateProperty().addListener(listener); updateLabel(); VBox vbox = new VBox(7); vbox.setAlignment(Pos.CENTER); vbox.getChildren().addAll(label, cbox); Scene scene = new Scene(vbox, 400, 400); stage.setTitle("Hello CheckBox"); stage.setScene(scene); stage.setVisible(true); } private void updateLabel() { final String txt = cbox.isIndeterminate() ? "The check box is indeterminate" : cbox.isSelected() ? "The check box is selected" : "The check box is not selected"; label.setText(txt); } public static void main(String[] args) { launch(args); } }
So, whenever either the indeterminate or selected property changes, we update the label’s text to match. This is kind of gross though (now that I’ve typed it out), because I have to attach a listener to two different properties. It would be nicer if there were a proper event handler that I could attach to the CheckBox which would be notified whenever indeterminate or selected changed.
I’ve filed a JIRA RT-13992 to track this.
Meanwhile, I can also use the high level binding APIs to do this. Here is an alternative implementation.
import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class IndeterminateCheckBox2 extends Application { @Override public void start(Stage stage) { CheckBox cbox = new CheckBox("Indeterminate CheckBox"); cbox.setIndeterminate(true); cbox.setAllowIndeterminate(true); Label label = new Label(); label.textProperty().bind( Bindings.when(cbox.indeterminateProperty()). then("The check box is indeterminate"). otherwise( Bindings.when(cbox.selectedProperty()). then("The check box is selected"). otherwise("The check box is not selected")) ); VBox vbox = new VBox(7); vbox.setAlignment(Pos.CENTER); vbox.getChildren().addAll(label, cbox); Scene scene = new Scene(vbox, 400, 400); stage.setTitle("Hello CheckBox"); stage.setScene(scene); stage.setVisible(true); } public static void main(String[] args) { launch(args); } }
And that’s pretty darn slick, IMHO.
It certainly is good to see indeterminate is supported in the core (unlike JCheckBox). But coming from a business model background I see need to write a wrapper on top of it, because in the business model the indeterminate state is not something separate from the actual value; a boolean itself can have three values: true, false and null. So binding the checkbox to a business model directly is not possible with the current implementation in JFX; we need to introduce a virtual Boolean (capital is intentional) property that deligates to selected and indeterminate.
For JCheckBox I chose to extend it and add the logic directly on top, not the conceptually best solution, but a very practical one. And it works great.
If you like, file a JIRA with an API suggestion!
RT-14012
Giving meaning to null Booleans is the same as saying that a null Integer is MAX_INT + 1 or even worse saying that a null String is the same as the empty string. Please do not do this in API code.
Object reference variables in Java can point to nothing, null. You should (in API code) never interpret the lack of an object as a value for an object of that type. That will undoubtedly lead to confusion and someone wishing it didn’t work this way. ๐
I would have to agree with tbee, the current API isn’t good.
Instead use
check.setState(Boolean b)
or
check.setState(State.INDETERMINTATE/SELECTED/UNSELECTED)
Both would throw RuntimeException if the check is set to not support indeterminate.
There can still be a
boolean check.isSelected()
which only returns true for State.SELECTED.
And please don’t make the enum a part of CheckBox, it looks ugly and doesn’t save a file anyway.
Or did you go “no API changes” before you released to public? Nice move if so.
Hi, I did a quick test with the TableView: I created a table with 30 columns and 10000 lines, full of values. When I scroll up and down, the table has trouble keeping track of the current position and drawing appropriately. I can see that it is eating up CPU as well. Doing the same thing with a swing JTable, this was completely fluid. This leads to several questions:
– how do I know if my program does use prism rather than java 2D for rendering (is there a simple test)?
– if I was not using prism, why is the JTable still looking better than the TableView? Is that a problem of optimization in the TableView
– is that a goal for the GA to optimize it in such a way that it (out?)performs at least to the level of the JTable?
– if I was using prism, will the TableView then compete with the JTable?
In short, what I have seen in terms of performance was disappointing, and I am wondering if this is just a matter of not using GPU, or if this is a problem with the control itself, or both. I would hope to have decent performances even without prism, because there old configurations out there, and I cannot tell everybody to change their graphic cards just to do some scrolling ion a table…
Thanks for handwork!
Vince,
Thanks for testing out TableView! Firstly, you can determine if you are using prism by running your application with the following flags: -Djavafx.verbose=true -Dprism.verbose=true
Certainly by the time JavaFX 2.0 GA ships we intend to have considerably better performance than we do now! At this stage we’ve only just (mostly) frozen the APIs, and really have a long road ahead of us to fix bugs and improve performance. You know what they say about premature optimisation ๐
However, I just recently applied a number of performance improvements to TableView that have not yet been put out into a public build. These were based on Jira issues I received and considerably improve performance in a few common cases. It would be great if you could please file a Jira issue and attach a test application I can use to easily reproduce your issue. If you do this I will hopefully reply with the good news that the performance has already improved….and if not then I have something else to work on :-). If you file it against Runtime and set the component to ‘Control’ the bug will end up assigned to me.
Thanks again!
Hi Jonathan, thanks for your answer.
A colleague of mine did create a JIRA: RT-14045. I ran the application with system properties on, and I got:
Prism pipeline init order: d3d j2d
Using openpisces for shapes, t2k for text rasterization
Using dirty region optimizations
Prism pipeline name = com.sun.prism.d3d.D3DPipeline
(X)(1) D3D loading native lib
prism-d3d loaded.
(X)d3dEnabled =false
(X) Got class = class com.sun.prism.d3d.D3DPipeline
D3DPipeline:getInstance(), d3dEnabled=false
Prism pipeline name = com.sun.prism.j2d.J2DPipeline
GraphicsPipeline.createPipeline: error initializing pipeline com.sun.prism.d3d.D3DPipeline(X) Got class = class com.sun.prism.j2d.J2DPipeline
*** Fallback to Prism SW pipeline
Initialized prism pipeline: com.sun.prism.j2d.J2DPipeline
JavaFX: using com.sun.javafx.tk.quantum.QuantumToolkit
RESIZE: 28626752466535 w: 1200 h: 550
Glass native format: 1
I suppose I am using java 2D. even though, I would argue that even in Java 2D, javafx should be able to perform as well as Swing. I read that the JFXtras/XTableView (at the time) did “heavy use of node caching and other scene graph optimizations to ensure that operations do not degrade with the size of the data”. Is the TableView using the same kind of tricks?
Thanks,
Vince
Vince,
Yes, TableView does the same kind of tricks – in fact, it’s likely XTableView from JFXtras used the same approach that was previously used in ListView in JavaFX 1.3.
For JavaFX 2.0, we use the same implementation across TreeView/ListView/TableView – we only create a very, very small number of ‘cells’ in these controls – just enough to show on screen (and maybe one or two more). As you scroll these controls, these cells are reused.
My guess is that the performance issues are related to the number of columns you’re testing with. This will need to be investigated further to ensure good performance.
Thanks to you and your colleague for the bug report – I’ll be looking into it in the comming weeks.
Hey,
I have been trying out the table view, but the recent build changes have affected my code and the previous code base is erroneous now. The cell values are just not able to render themselves. FYI, I read values out of a resultset. Can you please check my code and help me figure out the problem ?
The code is at : http://pastebin.com/E1Cenf3h
I would recommend visiting the OTN forums for JavaFX.