- How to use JavaFX bindings to set a tooltip
- How to display tooltips if the widget is disbled
Pretty simple so far. Here's the code:
package prv.rli.codetest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class TooltipOnDisabledButton extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
grid.setHgap(20);
Button button = new Button("Mystery");
grid.add(button, 0, 0);
CheckBox checkbox = new CheckBox("enabled");
grid.add(checkbox, 1, 0);
Scene scene = new Scene(grid);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Next, I want the "Mystery" button to be enabled/disabled if the checkbox is checked/unchecked.
Thanks to JavaFX bindings this can be done pretty easily, by adding this line right before the scene is created:
button.disableProperty().bind(checkbox.selectedProperty().not());
What this means is that the disableProperty of the button is now kept in sync with the (negated) selectedProperty of the checkbox.
And now, I also want the Button to display a Tooltip which will show the user how to enable/disable the button. This could be done by installing a ChangeListener to the selectedProperty() of the checkbox, but since we want to show (off) bindings, let's create a binding that will take a BooleanProperty and produce a Tooltip.
The whole thing then looks like this:
package prv.rli.codetest;
import javafx.application.Application;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class TooltipOnDisabledButton extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
grid.setHgap(20);
Button button = new Button("Mystery");
grid.add(button, 0, 0);
CheckBox checkbox = new CheckBox("enabled");
grid.add(checkbox, 1, 0);
button.disableProperty().bind(checkbox.selectedProperty().not());
button.tooltipProperty().bind(new TooltipBinding(checkbox.selectedProperty()));
Scene scene = new Scene(grid);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class TooltipBinding extends ObjectBinding<Tooltip> {
BooleanProperty enabled;
public TooltipBinding(BooleanProperty enabled) {
bind(enabled);
this.enabled = enabled;
}
@Override
protected Tooltip computeValue() {
if (enabled.get()) {
return new Tooltip("To disable this button, uncheck the box to right.");
}
else {
return new Tooltip("To enable this button, check the box to right.");
}
}
}
public static void main(String[] args) {
launch(args);
}
}
The TooltipBinding produces a new Tooltip (we could also just create the two tooltips and return them, saving the garbage collector some work) whenever the bound property is changed.
And it works like a charm -- well almost. When the button is disabled, no tooltip is displayed. The reason for this is documented in [1] and [2]: Disabled nodes are not sent any mouse events.
To circumvent this, we must wrap the button:
Button button = new Button("Mystery");
SplitPane wrapper = new SplitPane();
wrapper.getItems().add(button);
grid.add(wrapper, 0, 0);
...
wrapper.tooltipProperty().bind(new TooltipBinding(checkbox.selectedProperty()));
Note that the wrapper has to be a Control in order to have a tooltipProperty(). The problem with this solution is that we now have an additional border (from the SplitPane) around our button. This can easily be amended by
wrapper.setStyle("-fx-background-color: transparent");
It seems unnatural howeve to introduce a SplitPane as a Tooltip-Wrapper. A simple pane would be the more natural choice. A Pane however has no tooltip property, therefore we must use Tooltip.install() and install a single tooltip whose textProperty() is bound:
package prv.rli.codetest;
import javafx.application.Application;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class TooltipOnDisabledButton extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
grid.setHgap(20);
Button button = new Button("Mystery");
Pane wrapper = new Pane();
wrapper.getChildren().add(button);
grid.add(wrapper, 0, 0);
CheckBox checkbox = new CheckBox("enabled");
grid.add(checkbox, 1, 0);
button.disableProperty().bind(checkbox.selectedProperty().not());
Tooltip tooltip = new Tooltip();
tooltip.textProperty().bind(new TooltipBinding(checkbox.selectedProperty()));
Tooltip.install(wrapper, tooltip);
Scene scene = new Scene(grid);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class TooltipBinding extends ObjectBinding<String> {
BooleanProperty enabled;
public TooltipBinding(BooleanProperty enabled) {
bind(enabled);
this.enabled = enabled;
}
@Override
protected String computeValue() {
if (enabled.get()) {
return "To disable this button, uncheck the box to the right.";
}
else {
return "To enable this button, check the box to the right.";
}
}
}
public static void main(String[] args) {
launch(args);
}
}
Note that the TooltipBinding now generates a String instead of a whole tooltip.
Instead of using the TooltipBinding class, you could use the Bindings.when().then().otherwise() approach:
ReplyDeletetooltip.textProperty().bind(
Bindings.when(checkbox.selectedProperty())
.then("To disable this button, uncheck the box to the right.")
.otherwise("To enable this button, check the box to the right."));
how to disableBinding through BooleanProperty TRUE OR FALSE
ReplyDelete