The Problem
您正在101directionally绑定复选框的选定属性,但正在调用unbind()
以try 解除绑定.该方法只能用于解除绑定102directional绑定.这意味着您永远不会将复选框的选定属性与项的选定属性解除绑定.最重要的是,单元被重复使用and对于相同的属性可以同时存在多个101directional绑定.最终,每个复选框都绑定到多个项,这意味着对其中一个项的更新将传播到所有项.
Solutions
这里至少有两个解决方案.
正确解绑双向绑定
要解除绑定双向绑定,必须调用unbindBidirectional(Property)
.不幸的是,这将使您的代码变得稍微复杂一些,因为您必须维护对旧Property
的引用才能解除对它的绑定.幸运的是,根据单元格及其updateItem
方法的工作方式,您可以通过调用getItem()
before调用super.updateItem(...)
来获取旧项.
这里有一个例子.
Formation.java个个
import javafx.beans.property.*;
import javafx.scene.image.Image;
public class Formation {
private final StringProperty name = new SimpleStringProperty(this, "name");
public final void setName(String name) { this.name.set(name); }
public final String getName() { return name.get(); }
public final StringProperty nameProperty() { return name; }
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
public final void setImage(Image image) { this.image.set(image); }
public final Image getImage() { return image.get(); }
public final ObjectProperty<Image> imageProperty() { return image; }
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected");
public final void setSelected(boolean selected) { this.selected.set(selected); }
public final boolean isSelected() { return selected.get(); }
public final BooleanProperty selectedProperty() { return selected; }
}
FormationListCell.java个个
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Circle;
public class FormationListCell extends ListCell<Formation> {
private VBox container;
private Label label;
private Circle circle;
private CheckBox checkBox;
@Override
protected void updateItem(Formation item, boolean empty) {
Formation oldItem = getItem();
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
if (oldItem != null) {
unbindState(oldItem);
}
} else if (oldItem != item) {
if (container == null) {
createGraphic();
} else if (oldItem != null) {
unbindState(oldItem);
}
setGraphic(container);
label.textProperty().bind(item.nameProperty());
circle.fillProperty().bind(Bindings.createObjectBinding(() -> {
Image image = item.getImage();
return image == null ? null : new ImagePattern(image);
}, item.imageProperty()));
checkBox.selectedProperty().bindBidirectional(item.selectedProperty());
}
}
private void unbindState(Formation oldItem) {
label.textProperty().unbind();
label.setText(null); // don't keep a strong reference to the string
circle.fillProperty().unbind();
circle.setFill(null); // don't keep a strong reference to the image
// correctly unbind the check box's selected property
checkBox.selectedProperty().unbindBidirectional(oldItem.selectedProperty());
}
private void createGraphic() {
label = new Label();
circle = new Circle(25, null);
circle.setStroke(Color.BLACK);
checkBox = new CheckBox();
container = new VBox(10, label, circle, checkBox);
container.setAlignment(Pos.TOP_CENTER);
}
}
Main.java个个
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
ListView<Formation> listView = new ListView<>();
listView.setCellFactory(lv -> new FormationListCell());
for (int i = 1; i <= 1000; i++) {
Formation formation = new Formation();
formation.setName("Formation " + i);
formation.setSelected(Math.random() < 0.5);
listView.getItems().add(formation);
}
primaryStage.setScene(new Scene(listView, 600, 400));
primaryStage.show();
}
}
使用监听程序而不是绑定
您可以直接在updateItem
方法中设置UI状态,而不是使用绑定.然后在复选框的Selected属性上设置一个侦听器,该侦听器将根据需要更新该项.但是,当Formation
项的属性在其他地方更改时,为了使UI与Formation
项保持同步,您需要将列表视图的项设置为ObservableList
,即created with a so-called "extractor".提取程序允许列表观察其元素的属性,最终意味着当任何项的property无效时,将调用单元格的updateItem
方法.
就我个人而言,我更喜欢绑定解决方案,因为在设置列表视图的单元格工厂之后,它"就能正常工作",而这种方法要求您also记住将items
属性设置为定制列表.
这里有一个例子.
Formation.java个个
Unchanged from previous solution.个
FormationListCell.java个个
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Circle;
public class FormationListCell extends ListCell<Formation> {
private VBox container;
private Label label;
private Circle circle;
private CheckBox checkBox;
@Override
protected void updateItem(Formation item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
if (container != null) {
// no need to "clear" check box's selected property
label.setText(null);
circle.setFill(null);
}
} else {
if (container == null) {
createGraphic();
}
setGraphic(container);
label.setText(item.getName());
circle.setFill(item.getImage() == null ? null : new ImagePattern(item.getImage()));
checkBox.setSelected(item.isSelected());
}
}
private void createGraphic() {
label = new Label();
circle = new Circle(25, null);
circle.setStroke(Color.BLACK);
checkBox = new CheckBox();
// have a listener update the model property
checkBox.selectedProperty().addListener((obs, oldValue, newValue) -> {
Formation item = getItem();
if (item != null && newValue != item.isSelected()) {
item.setSelected(newValue);
}
});
container = new VBox(10, label, circle, checkBox);
container.setAlignment(Pos.TOP_CENTER);
}
}
Main.java个个
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
ListView<Formation> listView = new ListView<>();
listView.setItems(FXCollections.observableArrayList(formationPropertyExtractor()));
listView.setCellFactory(lv -> new FormationListCell());
for (int i = 1; i <= 1000; i++) {
Formation formation = new Formation();
formation.setName("Formation " + i);
formation.setSelected(Math.random() < 0.5);
listView.getItems().add(formation);
}
primaryStage.setScene(new Scene(listView, 600, 400));
primaryStage.show();
}
private Callback<Formation, Observable[]> formationPropertyExtractor() {
return f -> new Observable[] {f.nameProperty(), f.imageProperty(), f.selectedProperty()};
}
}