JavaFX Checkbox doesn't follow bound property's value - java

I have a model that has a boolean property that can be toggled. However depending on the conditions, the property toggle may fail.
I want to bind this property to a check box in my UI, moreover if the property fails to toggle, I want the check box to remain in its previous state.
I have created a SSCCE for this given below. I have a boolean property that emulates a failed toggle from false to true by simply setting to false regardless of the argument. The check box is initially false, as is the boolean property.
I expect that when I click the check box, it will remain unset because that is the state of the booleanproperty to which it is bound. However this is not the case, it will happily toggle on.
Is there something I can do about this in the boolean property or do I need to work around this by not binding the property at all and use listeners and event handlers?
SSCCE:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.stage.Stage;
public class PropertySSCCE extends Application {
BooleanProperty property = new SimpleBooleanProperty(false) {
#Override
public void set(boolean newValue) {
super.set(false);
fireValueChangedEvent();
}
#Override
public void setValue(Boolean v) {
super.setValue(false);
fireValueChangedEvent();
}
};
#Override
public void start(Stage aStage) throws Exception {
CheckBox cb = new CheckBox();
cb.selectedProperty().bindBidirectional(property);
Scene scene = new Scene(cb);
aStage.setScene(scene);
aStage.show();
}
public static void main(String[] args) {
launch();
}
}

As per #sillyfly's comment this seems to be impossible with a binding so I created the following helper to bind a CheckBox's displayed value to a boolean property and (ab)used the Predicate class to set and tell if the setting was OK.
public static void bindTogglable(CheckBox aCheckBox, BooleanExpression aBooleanExpression,
Predicate<Boolean> aSuccess) {
aCheckBox.setSelected(aBooleanExpression.get());
aBooleanExpression.addListener((aObservable, aOld, aNew) -> {
aCheckBox.setSelected(aNew);
});
aCheckBox.setOnAction(e -> {
boolean value = aCheckBox.isSelected();
boolean oldValue = aBooleanExpression.get();
if (value != oldValue && !aSuccess.test(value)) {
aCheckBox.setSelected(oldValue);
}
});
}
For OP's SSCCE the use would be as follows:
bindTogglable(cb, property, (value) -> false);
instead of:
cb.selectedProperty().bindBidirectional(property);

Related

JavaFX ChoiceBox - How can you update the text of the popup items?

I have a ChoiceBox where I can select the language for my program. When I select another language, the label gets translated as desired (because it is recomputed using ChoiceBoxSkin#getDisplayText and my StringConverter takes the language into account), but the elements in the popup list stay the same.
Now, I could do something like
public void updateStrings() {
var converter = getConverter();
setConverter(null);
setConverter(converter);
var selected = valueProperty().getValue();
valueProperty().setValue(null);
valueProperty().setValue(selected);
}
in my ChoiceBox-subclass. This will re-populate the popup list with the correctly translated texts. Setting the value again is necessary beacause ChoiceBoxSkin#updatePopupItems (which is triggered when changing the converter) also resets the toggleGroup. That means that the selected item would no longer be marked as selected in the popup list.
Despite being kind of ugly, this actually works for my current use case. However, it breaks if any listener of the valueProperty does something problematic on either setting it to null or selecting the desired item a second time.
Am I missing a cleaner or just all-around better way to achieve this?
Another approach might be to use a custom ChoiceBoxSkin. Extending that, I'd have access to ChoiceBoxSkin#getChoiceBoxPopup (although that is commented with "Test only purpose") and could actually bind the text properties of the RadioMenuItems to the corresponding translated StringProperty. But that breaks as soon as ChoiceBoxSkin#updatePopupItems is triggered from anywhere else...
A MRP should be:
import javafx.scene.control.ChoiceBox;
import javafx.util.StringConverter;
public class LabelChangeChoiceBox extends ChoiceBox<String> {
private boolean duringUpdate = false;
public LabelChangeChoiceBox() {
getItems().addAll("A", "B", "C");
setConverter(new StringConverter<>() {
#Override
public String toString(String item) {
return item + " selected:" + valueProperty().getValue();
}
#Override
public String fromString(String unused) {
throw new UnsupportedOperationException();
}
});
valueProperty().addListener((observable, oldValue, newValue) -> {
if(duringUpdate) {
return;
}
duringUpdate = true;
updateStrings();
duringUpdate = false;
});
}
public void updateStrings() {
var converter = getConverter();
setConverter(null);
setConverter(converter);
var selected = valueProperty().getValue();
valueProperty().setValue(null);
valueProperty().setValue(selected);
}
}
And an Application-class like
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import ui.LabelChangeChoiceBox;
public class Launcher extends Application {
#Override
public void start(Stage stage) {
Scene scene = new Scene(new LabelChangeChoiceBox());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
This works but needs the duringUpdate variable and can break if there is another change listener.
I’m not sure if this meets your needs, as your description of the problem is unclear in a few places.
Here’s a ChoiceBox which updates its converter using its own chosen language, and also retains its value when that change occurs:
import java.util.Locale;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.layout.BorderPane;
import javafx.util.StringConverter;
public class FXLocaleSelector
extends Application {
#Override
public void start(Stage stage) {
ChoiceBox<Locale> choiceBox = new ChoiceBox<>();
choiceBox.getItems().addAll(
Locale.ENGLISH,
Locale.FRENCH,
Locale.GERMAN,
Locale.ITALIAN,
Locale.CHINESE,
Locale.JAPANESE,
Locale.KOREAN
);
choiceBox.converterProperty().bind(
Bindings.createObjectBinding(
() -> createConverter(choiceBox.getValue()),
choiceBox.valueProperty()));
BorderPane pane = new BorderPane(choiceBox);
pane.setPadding(new Insets(40));
stage.setScene(new Scene(pane));
stage.setTitle("Locale Selector");
stage.show();
}
private StringConverter<Locale> createConverter(Locale locale) {
Locale conversionLocale =
(locale != null ? locale : Locale.getDefault());
return new StringConverter<Locale>() {
#Override
public String toString(Locale value) {
if (value != null) {
return value.getDisplayName(conversionLocale);
} else {
return "";
}
}
#Override
public Locale fromString(String s) {
return null;
}
};
}
public static void main(String[] args) {
launch(FXLocaleSelector.class, args);
}
}
Not entirely certain whether or not I understand your requirement correctly, my assumptions:
there's a ChoiceBox which contains the "language" for your ui, including the itself: lets say it contains the items Locale.ENGLISH and Locale.GERMAN, the visual representation of its items should be "English", "German" if its value is Locale.ENGLISH and "Englisch", "Deutsch" if its value is Locale.GERMAN
the visual representation is done by a StringConverter configurable with the value
If so, the solution is in separating out concerns - actually, it's not: the problem described (and hacked!) in the question is JDK-8088507: setting the converter doesn't update the selection of the menu items in the drop down. One hack is as bad or good as another, my personal preferenced would go for a custom skin which
adds a change listener to the converter property
reflectively calls updateSelection
Something like:
public static class MyChoiceBoxSkin<T> extends ChoiceBoxSkin<T> {
public MyChoiceBoxSkin(ChoiceBox<T> control) {
super(control);
registerChangeListener(control.converterProperty(), e -> {
// my local reflection helper, use your own
FXUtils.invokeMethod(ChoiceBoxSkin.class, this, "updateSelection");
});
}
}
Note: the hacks - this nor the OP's solution - do not solve the missing offset of the popup on first opening (initially or after selecting an item in the popup).
Not a solution to the question, just one way to have a value-dependent converter ;)
have a StringConverter with a fixed value (for simplicity) for conversion
have a converter controller having that a property with that value and a second property with a converter configured with the value: make sure the converter is replaced on change of the value
bind the controller's value to the box' value and the box' converter to the controller's converter
In (very raw) code:
public static class LanguageConverter<T> extends StringConverter<T> {
private T currentLanguage;
public LanguageConverter(T language) {
currentLanguage = language;
}
#Override
public String toString(T object) {
Object value = currentLanguage;
return "" + object + (value != null ? value : "");
}
#Override
public T fromString(String string) {
return null;
}
}
public static class LanguageController<T> {
private ObjectProperty<StringConverter<T>> currentConverter = new SimpleObjectProperty<>();
private ObjectProperty<T> currentValue = new SimpleObjectProperty<>() {
#Override
protected void invalidated() {
currentConverter.set(new LanguageConverter<>(get()));
}
};
}
Usage:
ChoiceBox<String> box = new ChoiceBox<>();
box.getItems().addAll("A", "B", "C");
box.getSelectionModel().selectFirst();
LanguageController<String> controller = new LanguageController<>();
controller.currentValue.bind(box.valueProperty());
box.converterProperty().bind(controller.currentConverter);

How to make a SimpleProperty inform listeners on the first set?

When I write this:
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
SimpleBooleanProperty foo = new SimpleBooleanProperty();
foo.addListener(
new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue
) {
System.out.println(oldValue + " " + newValue);
}
}
);
foo.set(false);
foo.set(true);
The output is
false true
However, if I change the last two lines to
foo.set(true);
foo.set(false);
The output is
false true
true false
I find this rather inconsistent. How can I ensure that the Listener is always notified on the first set ?
... or is it common sense that the listener should explicitly call foo.get() to get the initial value and then be informed only on changes? In that case I still wouldn't know how to distinguish between the default false and a false that has been set before the listener registered.
It's perfectly consistent.
The problem is that SimpleBooleanProperty is already initialised to false. When you call foo.set(false) first then the value hasn't actually changed, so the listener is not called.
When you call foo.set(true) first then the value changes twice (false -> true -> false) which invokes the listener twice.
If you want the initial value to be true, use the constructor which takes a boolean.
If you want to fire an event when attaching the listener, you could extend SimpleBooleanProperty quite easily, though it's not clear to me what you would use for the old value (null, I guess?):
class FireWhenAttachBooleanProperty extends SimpleBooleanProperty
{
#Override
public void addListener(ChangeListener<? super Boolean> listener)
{
super.addListener(listener);
listener.changed(this, null, getValue());
}
}
Maybe it wasnt clear from the question (if it was then I guess I didnt have to ask the question ;). My mistake was that I actually want to react on two different things: One kind of listener should be notified each time the value is set, and a different kind of listener only when the value is changed. So I actually need something like this:
import javafx.event.EventHandler;
import java.util.ArrayList;
import javafx.event.ActionEvent;
import javafx.event.Event;
class Foo {
private SimpleBooleanProperty foo = new SimpleBooleanProperty();
private ArrayList<EventHandler> listener = new ArrayList<>();
public void set(Boolean value) {
foo.set(value);
ActionEvent e = new ActionEvent();
for(EventHandler l : listener) l.handle(e);
}
public void addChangeListener(ChangeListener<Boolean> x) {
foo.addListener(x);
}
public void addEventHandler(EventHandler a){
listener.add(a);
}
}
Such that I can write this:
Foo foo = new Foo();
foo.addChangeListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable,
Boolean oldValue,
Boolean newValue) {
System.out.println("value changed");
}
});
foo.addEventHandler(new EventHandler() {
#Override
public void handle(Event event) {
System.out.println("value was set");
}
});
foo.set(false);
where even thought the value does not change, the eventhandler is notified.

Refreshing a column of comboboxes in a table view, javafx

so i have a table view with 3 columns and one of them is a column of comboboxes, the way i create the column of combobox is as so
Source = new TableColumn<>("Configure Interface as..");
Source.setCellValueFactory(i -> {
final StringProperty value = i.getValue().optionProperty();
// binding to constant value
return Bindings.createObjectBinding(() -> value);
});
Source.setCellFactory(col -> {
TableCell<TableViewTest, StringProperty> c = new TableCell<>();
ComboBox<String> comboBox = new ComboBox<>(options);
c.itemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
comboBox.valueProperty().unbindBidirectional(oldValue);
}
if (newValue != null) {
comboBox.valueProperty().bindBidirectional(newValue);
}
});
c.graphicProperty().bind(Bindings.when(c.emptyProperty()).then((Node) null).otherwise(comboBox));
return c;
});
the column gets its values from the getter method optionProperty() which resides within my TableViewTest class.
So the problem i'm having is I have another combobox (comboBoxA) that is above my tableview table in my gui, and when ever i change the value of comboBoxA i want to change the values of the comboboxes with the column.
I can do this by calling the following code within the method that is listening for the selection change of comboboxA
Source.setCellValueFactory(i -> {
final StringProperty value = i.getValue().optionTwoProperty();
// binding to constant value
return Bindings.createObjectBinding(() -> value);
});
but the values don't change unless is start scrolling down to near the bottom of the table. is there a way to force the comboboxes to change to the new values within the getter method optionTwoProperty() without me having to scroll down?.
EDIT
Okay so the line
final StringProperty value = i.getValue().optionTwoProperty();
doesnt actaully get called until i start scrolling down.
So, with help from fabian, I think I understand that you want the combo box above the table to change the property in your model class that is represented in the cells in the table column.
One way to do this is to make the type of the combo box function that maps the model class to a property, and populate it with functions mapping to each of the properties you want.
Then you can represent the cell value factory for the table column with a binding that observes all the possible properties that could be represented, along with the selected value in the combo box, and returns the value computed by applying the function from the combo box to the model instance (and retrieving its wrapped value).
For the cell factory for the column, you can observe the selected value in the cell's combo box. When it changes, use the selected item in the combo box above the table to figure out which property to update.
Here's a SSCCE:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableWithSetAllComboBox extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
TableColumn<Item, String> itemCol = new TableColumn<>("Item");
itemCol.setCellValueFactory(cellData -> Bindings.createStringBinding(() -> cellData.getValue().getName()));
table.getColumns().add(itemCol);
TableColumn<Item, String> choiceCol = new TableColumn<>("Choice");
ComboBox<Function<Item, StringProperty>> option = new ComboBox<>();
option.getItems().add(Item::choiceProperty);
option.getItems().add(Item::choice2Property);
option.setCellFactory(lv -> createListCell());
option.setButtonCell(createListCell());
option.getSelectionModel().select(0);
ObservableList<String> choices = FXCollections.observableArrayList("First choice", "Second choice", "Third choice");
choiceCol.setCellFactory(col -> {
TableCell<Item, String> cell = new TableCell<>();
ComboBox<String> combo = new ComboBox<>(choices);
cell.graphicProperty().bind(Bindings.when(cell.emptyProperty()).then((Node)null).otherwise(combo));
combo.valueProperty().addListener((obs, oldValue, newValue) -> {
if (! cell.isEmpty() && newValue != null) {
Item item = table.getItems().get(cell.getIndex()) ;
StringProperty property = option.getValue().apply(item);
property.set(newValue);
}
});
cell.itemProperty().addListener((obs, oldItem, newItem) -> combo.setValue(newItem));
return cell ;
});
choiceCol.setPrefWidth(150);
table.getColumns().add(choiceCol);
choiceCol.setCellValueFactory(cellData -> Bindings.createStringBinding(
() -> option.getValue().apply(cellData.getValue()).get(),
cellData.getValue().choiceProperty(),
cellData.getValue().choice2Property(),
option.valueProperty()));
choiceCol.setGraphic(option);
choiceCol.setPrefWidth(200);
for (int i = 1; i <= 30 ; i++) table.getItems().add(new Item("Item "+i ,choices.get(0)));
Button debug = new Button("Debug");
debug.setOnAction(e -> table.getItems().stream().
map(item -> String.format("%s (%s, %s)", item.getName(), item.getChoice(), item.getChoice2())).
forEach(System.out::println));
BorderPane root = new BorderPane(table);
BorderPane.setMargin(debug, new Insets(5));
root.setBottom(debug);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
private ListCell<Function<Item, StringProperty>> createListCell() {
return new ListCell<Function<Item, StringProperty>>() {
#Override
public void updateItem(Function<Item, StringProperty> item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item.apply(new Item("", "")).getName());
}
};
}
public static class Item {
private final String name ;
private final StringProperty choice ;
private final StringProperty choice2 ;
public Item(String name, String choice) {
this.choice = new SimpleStringProperty(this, "Choice", choice);
this.choice2 = new SimpleStringProperty(this, "Choice 2", "Second choice");
this.name = name ;
}
public final StringProperty choiceProperty() {
return this.choice;
}
public final java.lang.String getChoice() {
return this.choiceProperty().get();
}
public final void setChoice(final java.lang.String choice) {
this.choiceProperty().set(choice);
}
public String getName() {
return name;
}
public final StringProperty choice2Property() {
return this.choice2;
}
public final java.lang.String getChoice2() {
return this.choice2Property().get();
}
public final void setChoice2(final java.lang.String choice2) {
this.choice2Property().set(choice2);
}
}
public static void main(String[] args) {
launch(args);
}
}
The issue is the TableView not listening to modifications of the cellValueFactory property of the elements of it's columns. Therefore the TableView doesn't know it should redraw it's cells. In JavaFX 8u60 the refresh() method was added for this purpose (for some reason I can't find it in the online javadoc though), which allows you to change the code of your method changing the cellValueFactory like this:
Source.setCellValueFactory(i -> {
final StringProperty value = i.getValue().optionTwoProperty();
// binding to constant value
return Bindings.createObjectBinding(() -> value);
});
tableview.refresh();
In older versions you have to use the workaround of setting the column value to trigger a change in the list:
List<TableColumn<TableViewTest, ?>> columns = tableview.getColumns();
columns.set(columns.indexOf(Source), Source);
But this workaround could cease to work in future versions, since the list is not actually modified with this operation and triggering a list change event is not required by the contract of ObservableList (but replacing the TableColumn with a new instance (and copying the properties) should always work).
Hard to say given the code snippets. Maybe you're not on the javaFX thread when doing the update? In that case use Platform.runLater(...), or share some minimal amout of code to reproduce the problem.

CheckBoxTreeItems always independent, not rendered properly. Why?

I am new to JavaFX and I am trying to create a simple TreeTableView, containing a
single Boolean column and is rendered with a CheckBoxTreeTableCell.
The problem I am having is that the two CheckBoxTreeItems seem independent (selecting
the root doesn't select the child and the other way around). I even try setting the
independancy manually (see commented code) but it makes no difference.
The documentation for CheckBoxTreeItems says that "By default, CheckBoxTreeItem instances are dependent", which doesn't seem to work for me.
Also, I am expecting toString() value of the Model class to be shown as checkboxes'
texts but no text is drawn, only empty checkboxes. Why is this?
And finally, it is possible to set a graphic node for a CheckBoxTreeItem, and this
node is then shown to the left of the CheckBoxTreeItem. Would it be possible to have
it drawn between the checkbox and the checkbox text instead? Something like:
[x][graphic_node]A simple checkbox text
I am using JDK 1.8.0_40
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.stage.Stage;
public final class CheckBoxTreeItemTest extends Application {
private Stage stage;
public static void main(String[] args) {
launch(args);
}
#Override
public final void start(final Stage stage) throws Exception {
this.stage = stage;
final CheckBoxTreeItem<Model> root = new CheckBoxTreeItem<>(new Model("Root"));
final CheckBoxTreeItem<Model> parent = new CheckBoxTreeItem<>(new Model("Parent"));
final CheckBoxTreeItem<Model> child = new CheckBoxTreeItem<>(new Model("Child"));
//Manually setting independence makes no difference
/*parent.setIndependent(false);
child.setIndependent(false);
root.setIndependent(false);*/
parent.getChildren().add(child);
root.getChildren().add(parent);
final TreeTableColumn<Model, Boolean> selectedColumn =
new TreeTableColumn<>("Selection");
selectedColumn.setEditable(true);
selectedColumn.setCellValueFactory(param -> param.getValue().getValue().selectedProperty());
selectedColumn.setCellFactory(CheckBoxTreeTableCell.<Model>forTreeTableColumn(selectedColumn));
final TreeTableView<Model> table = new TreeTableView<>(root);
table.setShowRoot(false);
table.setEditable(true);
table.getColumns().add(selectedColumn);
final Scene scene = new Scene(table, 500, 350);
stage.setScene(scene);
stage.show();
}
private class Model {
private final BooleanProperty selected;
private final StringProperty name;
public Model(final String name) {
this.selected = new SimpleBooleanProperty(false);
this.name = new SimpleStringProperty(name);
}
public final void setSelected(final boolean selected) {
this.selected.set(selected);
}
public final boolean isSelected() {
return selected.get();
}
public final BooleanProperty selectedProperty() {
return selected;
}
public final StringProperty nameProperty() {
return name;
}
#Override
public String toString() {
return "Model [selected=" + selected + ", name=" + name + "]";
}
}
}
A CheckBoxTreeItem provides a selected property. It is this property that respects the independent state of the CheckBoxTreeItem (i.e. if the parent CheckBoxTreeItem is selected, then this CheckBoxTreeItem is automatically selected, etc).
However, in your application, the CheckBoxTreeItem's selected property is not the property represented by the item, because you set the cell value factory to map to the selected property of the Model instance represented by the item. So checking the check box sets Model.selected to true, but of course there is no logic managing that property in terms of parent and/or child selected properties.
Typically when you have your own boolean property representing the state of the checkbox, you would not use a CheckBoxTreeItem. However, if you want the functionality of the non-independent properties, you would have to implement that yourself. Since that logic is actually quite complicated, if you want your own Model class, I would just bidirectionally bind the property of interest to the CheckBoxTreeItem's selectedProperty:
Model rootModel = new Model("Root");
final CheckBoxTreeItem<Model> root = new CheckBoxTreeItem<>(rootModel);
root.selectedProperty().bindBidirectional(rootModel.selectedProperty());
Model parentModel = new Model("Parent");
final CheckBoxTreeItem<Model> parent = new CheckBoxTreeItem<>( parentModel);
parent.selectedProperty().bindBidirectional(parentModel.selectedProperty());
Model childModel = new Model("Child");
final CheckBoxTreeItem<Model> child = new CheckBoxTreeItem<>(childModel);
child.selectedProperty().bindBidirectional(childModel.selectedProperty());

Are bindings automatically removed in a TreeCell

I have TreeView that has a cell factory set on it. The TreeCells I'm returning are displayed below:
import javafx.beans.binding.StringBinding;
import javafx.collections.ObservableMap;
import javafx.scene.control.TreeCell;
public class TreeCellTest extends TreeCell<String> {
private ObservableMap<String, StringBinding> lookup;
public TreeCellTest(ObservableMap<String, StringBinding> lookup) {
this.lookup = lookup;
}
#Override
protected void updateItem(String id, boolean empty) {
super.updateItem(id, empty);
if (empty) {
setText(null);
} else {
StringBinding stringBinding = lookup.get(id);
textProperty().bind(stringBinding);
}
}
}
Notice that I'm not setting the text but I'm binding the textProperty to a StringBinding. This works fine in normal situations but I'm wondering if it is OK to use it inside a TreeCell.
The TreeCell gets recycled as and when needed so I would like to know whether when this happens the binding gets automatically removed or whether I need to remove it manually?
I don't want the case where each TreeCell has 100's of bindings attached to it.
While it's not documented, it appears that calling bind(...) will remove any existing bindings before creating the new binding.
For example:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class RebindingTest {
public static void main(String[] args) {
StringProperty text = new SimpleStringProperty();
StringProperty value1 = new SimpleStringProperty();
StringProperty value2 = new SimpleStringProperty();
text.addListener((obs, oldValue, newValue) -> System.out.printf("text changed from %s to %s%n", oldValue, newValue));
text.bind(value1);
value1.set("Set value 1");
text.bind(value2);
value2.set("Set value 2");
value1.set("Reset value 1");
}
}
So I think all you need to do to make your code work correctly is add
textProperty().unbind();
to the if (empty) { ... } block.
Of course, calling that unconditionally in your updateItem(...) method would mean you're not relying on undocumented behavior, and any loss of efficiency is probably minimal.

Categories