我一直在练习提高使用JavaFX的技能,我遇到了一些使用JavaFX ListView的问题. 我做了一个定制布局的标签,圆圈的行为作为一个图像持有者和一个复选框,如下所示.

screen capture

然后我创建了一个ListCell,班级类型为"队形".类类型包含一个用于标签的字符串、一个用于圆的图像和一个用于计算复选框状态的布尔属性. 我try 了多种解决方案,但一直面临问题.

第一个解决方案是将CheckBox绑定到CheckBox的布尔属性.问题是当选中或取消选中单个复选框时,所有复选框都变为选中/取消选中.

我也try 了复选框的 Select 监听程序,但在ListView中以编程方式设置复选框的初始状态时,它会干扰监听程序(不断触发监听程序并进行未调用的更改).

我想要的是对特定复选框进行更改,使其不影响所有复选框,并使复选框 Select 侦听器仅响应用户生成的点击(通过单击选中复选框) 而不是程序性更改(例如,check box.setselect(True))

这是ListCell的代码:

package Formation.cells;

import Formation.Formation;
import com.jfoenix.controls.JFXCheckBox;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Circle;

import java.awt.image.BufferedImage;
import java.util.concurrent.atomic.AtomicBoolean;

public class Available_formation_cell extends ListCell<Formation> {
    private AnchorPane layout;
    private Label formation_name;
    private Circle formation_icon;

    private JFXCheckBox select_box;

    public Available_formation_cell(){
    try{
        FXMLLoader load_available_formations_layout=new FXMLLoader(getClass().getResource("/Formation/available_formation_layout.fxml"));
        layout=load_available_formations_layout.load();
        this.formation_name= (Label) load_available_formations_layout.getNamespace().get("formation_name");
        this.formation_icon =(Circle) load_available_formations_layout.getNamespace().get("formation_icon");
        this.select_box=(JFXCheckBox) load_available_formations_layout.getNamespace().get("select_box");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    protected void updateItem(Formation formation, boolean b) {
        super.updateItem(formation, b);
        select_box.selectedProperty().unbind();
        if(formation==null || b){
            setGraphic(null);
            setText(null);
        }
        else{
            formation_name.setText(formation.getFormation_name());
            Image formation_image=formation.getFormation_image();

       
                formation_icon.setFill(new ImagePattern(formation_image));
            
            if(formation.isSelected_available_group()){
                select_box.setSelected(true);
            }
            else{
                select_box.setSelected(false);
            }
                select_box.selectedProperty().bindBidirectional(formation.selected_available_groupProperty());

            setGraphic(layout);
            setText(null);
        }
    }
}

推荐答案

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()};
    }
}

Java相关问答推荐

当耗时的代码完成时,Circular ProgressIndicator显示得太晚

使用log 4j2格式的Hibernate 显示SQL日志(log)

日食IDE 2024-03在Ubuntu下崩溃,导致hr_err_pid.log

如果给定层次 struct 级别,如何从其预序穿越构造n元树

需要一个找不到的jakarta.sistence.EntityManager类型的Bean

解析Javadoc时链接的全限定类名

如何让JFileChooser(DIRECTORIES_ONLY)从FolderName中的空白开始?

Bean定义不是从Spring ApplationConext.xml文件加载的

将Spring Boot 3.2.0升级到3.2.1后查询执行错误

STREAMS减少部分结果的问题

Java 21中泛型的不兼容更改

在实例化中指定泛型类型与不指定泛型类型之间的区别

在ECLIPSE上的M1 Pro上运行JavaFX的问题

我该如何为我的类编写getter和setter方法?

不能在 map 上移除折线

为什么使用lo索引来解决二进制搜索问题不同于使用hi索引?

如何使用Rascal Evaluator从编译的JAR访问Rascal函数?

AspectJ编织外部依赖代码,重新打包jar并强制依赖用户使用它

ANTLR 接受特殊字符,例如 .标识符或表达式中的(点)和 ,(逗号)

语句打印在错误的行(Java Token 问题)