带有 RadioButtons 的 JavaFX TreeView 在滚动时显示不正确的状态

JavaFX TreeView with RadioButtons displaying incorrect status when scrolling

我需要一个 TreeView,其中一些元素有 RadioButton。 所以我环顾四周,使用了 here 中的一些代码,并将我自己的一些想法放在那里。

所以结果是 class (RadioTreeView) 扩展 TreeView 自定义 TreeItems 和 TreeCells.

一切正常,除了一件重要的事情:

滚动 TreeView 并选中一个 RadioButton 时,其他按钮将显示为已选中。 这个 gif 演示了这个问题。(我在滚动时没有点击任何地方)

通过一些调试,我发现 ToggleGroupselectedItem-属性(由所有 RadioButtons 在 TreeView) 中,实际上永远不会改变。 当我为每个 RadioButtons' selected-属性 添加一个监听器时,它们也从未触发。

所以这让我感到困惑和疑惑,这是一个 JavaFX 错误还是我遗漏了一些非常明显的东西。

这是我的代码:

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.util.Callback;

public class RadioTreeView<T> extends TreeView<T> {
    private ToggleGroup toggleGroup; // shared by all radioButtons

    public RadioTreeView() {
        super();
        toggleGroup = new ToggleGroup();

        setCellFactory(new Callback<TreeView<T>, TreeCell<T>>() {
            @Override
            public TreeCell<T> call(TreeView<T> param) {
                return new RadioTreeCell<T>(toggleGroup);
            }
        });

    }

    public static class RadioTreeItem<T> extends TreeItem<T> {
        private final boolean hasRadio; // defines whether a RadioButton should be shown

        public RadioTreeItem(boolean hasRadio, T item) {
            super(item);
            this.hasRadio = hasRadio;
        }

        public boolean getHasRadio() {
            return hasRadio;
        }
    }

    public static class RadioTreeCell<T> extends TreeCell<T> {
        private final RadioTreeButton<T> radio = new RadioTreeButton<T>();
        private final ToggleGroup toggleGroup;

        public RadioTreeCell(ToggleGroup toggleGroup) {
            super();
            this.toggleGroup = toggleGroup;
        }

        {
            setContentDisplay(ContentDisplay.LEFT);
        }

        @Override
        public void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);

            if (!empty && item != null) {
                setText(item.toString());
                if (((RadioTreeItem<T>) getTreeItem()).getHasRadio()) { // display radioButton as graphic
                    setGraphic(radio);
                    radio.setToggleGroup(toggleGroup);
                } else {
                    setGraphic(null);
                }
            } else {
                setGraphic(null);
                setText(null);
            }
        }
    }

    private static class RadioTreeButton<T> extends RadioButton {
        // non-important methods removed

        public RadioTreeButton() {
        }
    }

非常感谢任何帮助。

PS:我知道我的代码并不完美

TreeCell 被重用。这意味着可以在包含不同 TreeItem 的单元格中使用单个 RadioButton。您永远不会根据项目更新单元格中 RadioButton 的选择状态。这导致相同的 RadioButton 保持选中状态。滚动时,不同的 TreeItem 会被放入不同的单元格中。 TableCell 的文本已更新,但其 RadioButton 的选择状态未更新。

您需要以某种方式存储所选 TreeItem,例如使用 userData 属性 的 ToggleGroup:

public RadioTreeCell(ToggleGroup toggleGroup) {
    this.toggleGroup = toggleGroup;
    setContentDisplay(ContentDisplay.LEFT); // initializer code moved to constructor

    // update userData for toggleGroup, when new radio becomes selected
    radio.selectedProperty().addListener((o, oldValue, newValue) -> {
        if (newValue) {
            toggleGroup.setUserData(getTreeItem());
        }
    });
}

@Override
public void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);

    if (!empty && item != null) {
        setText(item.toString());
        RadioTreeItem<T> treeItem = (RadioTreeItem<T>) getTreeItem();
        if (treeItem.getHasRadio()) { // display radioButton as graphic
            setGraphic(radio);
            radio.setToggleGroup(toggleGroup);

            // update selection based on toggleGroup userData
            radio.setSelected(toggleGroup.getUserData() == treeItem);
        } else {
            setGraphic(null);
        }
    } else {
        setGraphic(null);
        setText(null);
    }
}