当前位置: 首页 > 知识库问答 >
问题:

JavaFX-删除选定项目时TreeView的奇怪行为

缑修齐
2023-03-14

在我的应用程序中,左侧有一个TreeView,我根据TreeView中的选择更新右侧的窗格。一个非常直接的场景。当选择为空时,我会在窗格中显示类似“请进行选择”的消息,即我也会在树视图中处理空选择。

在应用程序的生命周期内,可以在TreeView中添加/删除一些项。当删除TreeView中选定的项目时,我遇到了一些问题。在这种情况下,我期望TreeView的选择变为空,但是它不是!

为了调试这个案例,我编写了一个简单的FXML应用程序,如下所示:

FXMLDocument.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="350.0" prefWidth="250.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="treeviewbug.FXMLDocumentController">
  <children>
    <TreeView fx:id="treeView" prefHeight="350.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="50.0" />
    <Button mnemonicParsing="false" onAction="#update" text="update" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="15.0" />
    <Label fx:id="selectionLabel" text="" AnchorPane.leftAnchor="100.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="20.0" />
  </children>
</AnchorPane>

FXMLDocumentController.java:

package treeviewbug;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.util.Callback;

public class FXMLDocumentController implements Initializable {

    @FXML
    Label selectionLabel;
    @FXML
    private TreeView<String> treeView;
    private TreeItem<String> selectedItem = null;
    private ChangeListener<String> changeListener = new ChangeListener<String>() {

        @Override
        public void changed(ObservableValue<? extends String> ov, String oldValue, String newValue) {
            selectionLabel.setText(newValue);
        }
    };

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        final TreeItem<String> root = new TreeItem<>();
        for (int i = 1; i <= 20; i++) {
            root.getChildren().add(new TreeItem<String>("Item " + i));
        }
        treeView.setShowRoot(false);
        treeView.setRoot(root);
        treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<String>>() {

            @Override
            public synchronized void changed(ObservableValue<? extends TreeItem<String>> ov, TreeItem<String> oldSelection, TreeItem<String> newSelection) {
                if (selectedItem != null) {
                    selectedItem.valueProperty().removeListener(changeListener);
                }
                if (newSelection == null) {
                    selectionLabel.setText("selection is null");
                } else {
                    selectionLabel.setText(newSelection.getValue());
                }
                selectedItem = newSelection;
                if (selectedItem != null) {
                    selectedItem.valueProperty().addListener(changeListener);
                }
            }
        });
        treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {

            @Override
            public TreeCell<String> call(TreeView<String> p) {
                System.out.println("Creating new cell.");
                return new TreeCell<String>() {

                    Label label = new Label();
                    Button button = new Button("remove");
                    HBox box = new HBox(20);
                    {
                        button.setOnAction(new EventHandler<ActionEvent>() {

                                @Override
                                public void handle(ActionEvent t) {
                                    TreeItem<String> itemToRemove = null;
                                    for (TreeItem<String> item : root.getChildren()) {
                                        if (item.getValue().equals(getItem())) {
                                            itemToRemove = item;
                                            break;
                                        }
                                    }
                                    if (itemToRemove != null) {
                                        root.getChildren().remove(itemToRemove);
                                    }
                                    t.consume();
                                }
                            });
                        box.getChildren().addAll(label, button);
                    }

                    @Override
                    protected void updateItem(String value, boolean bln) {
                        super.updateItem(value, bln);
                        if (value != null) {
                            label.setText(value);
                            setGraphic(box);
                        } else {
                            setGraphic(null);
                        }
                    }
                };
            }
        });
    }

    @FXML
    private void update() {
        TreeItem<String> i = treeView.getSelectionModel().getSelectedItem();
        if (i == null) {
            selectionLabel.setText("selection is null");
        } else {
            selectionLabel.setText(i.getValue());
        }
    }

}

最初,TreeView填充了20个项。侦听器附加到selectedItemProperty,以防用户单击某些TreeItem。侦听器还附加到所选的TreeItemvalue eProperty,以防用户没有单击某些项目,但所选项目的值因某种原因而更改。用户可以通过单击其中的删除按钮来删除特定项目。在任何时候,用户可以点击更新按钮来更新显示当前选择的标签内容,以防我的处理程序错过了一些事件。

在这个简单的测试应用程序中,当我删除所选项目时,它有时会将删除项目之前的项目显示为所选项目,有时会将删除项目之后的项目显示为所选项目。但是,所选项目大部分时间不会更改,即使它不再包含在TreeView中!

我的第一个问题是,这是正常的,还是一个错误?你见过这样的东西吗?

作为一种解决方法,我在for循环之后添加了以下代码

root.getChildren().addListener(new ListChangeListener<TreeItem<String>>() {

            @Override
            public void onChanged(ListChangeListener.Change<? extends TreeItem<String>> change) {
                while (change.next()) {
                    if (change.wasRemoved() && selectedItem != null) {
                        if (change.getRemoved().contains(selectedItem)) {
                            selectedItem.valueProperty().removeListener(changeListener);
                            selectedItem = null;
                            treeView.getSelectionModel().clearSelection();
                        }
                    }
                }
            }
        });

现在,没有像选择一个不存在的项那样的奇怪情况了。但有时选择不是空的,尽管我调用了clearSelection()。我的第二个问题是,这种自动选择正常吗?

最后一个问题,有更好的解决办法吗?

对不起,这是一个很长的问题。如果你还在阅读,谢谢你:)

共有3个答案

曾航
2023-03-14

我有同样的问题,你应该传递一个可观察的集合到你的根节点。

在代码中,而不是:

    final TreeItem<String> root = new TreeItem<>();
    for (int i = 1; i <= 20; i++) {
        root.getChildren().add(new TreeItem<String>("Item " + i));
    }
    treeView.setShowRoot(false);
    treeView.setRoot(root);

添加以下属性:

private ObservableList<TreeItem<String>> obsTreeItems;
private List<TreeItem<String>> treeItems;

并使用以下命令更改您的第一行:

    treeItems = new ArrayList<>();
    final TreeItem<String> root = new TreeItem<>();
    for (int i = 1; i <= 20; i++) {
        /*root.getChildren().add(new TreeItem<String>("Item " + i));*/
        treeItems.add(new TreeItem<String>("Item "+i));
    }
    //observable collection
    obsTreeItems = FXCollections.observableArrayList(treeItems);

    //add items as observable list to root
    root.getChildren().addAll(obsTreeItems);

    treeView.setShowRoot(false);
    treeView.setRoot(root);

你会注意到这次更新会做得很好。您还应该删除所有的变通代码。更新按钮将不再需要。

终安和
2023-03-14

的确,SelectEditeProperty()没有更新,但是getSelectedIndices()已正确更新。因此,您可以将侦听器添加到getSelectedIndices()。

我使用下面的代码来保持selectedItem的可观察列表。(

  cnt_treeView.getSelectionModel().getSelectedIndices().addListener((InvalidationListener) observable -> {
            ArrayList<Folder> tmmp = new ArrayList<>();
            for (TreeItem<Folder> ti : cnt_treeView.getSelectionModel().getSelectedItems()) {
                if (ti != null)
                    tmmp.add(ti.getValue());
                selectedFolders.setAll(tmmp);
            }

        });

在上找到此解决方案https://community.oracle.com/tech/developers/discussion/2393837/change-listener-not-notified-when-tree-item-is-deselected

佴英奕
2023-03-14

在Java 1.8.092中,行为似乎是:

  • 如果选择后面的项目被删除,则selectedItem和selectedIndex保持不变,
  • 如果删除选择之前的项目,selectedItem保持不变,但selectedIndex发生变化(因为selectedItem改变了其位置),
  • 如果所选项目已删除,则selectedIndex将递减,selectedItem也会发生类似变化;仅当第一项被删除时,选择的索引保持不变

如果删除最后一项,则会出现唯一奇怪的行为。在这种情况下,selectedIndex和selectedItem不会改变,正如Gman所说的那样。这会导致选择一个不再存在的项目(似乎是一个bug)。

作为一种解决方法,您可以检查删除代码中的根子项。

删除按钮的onAction可能如下所示:

button.setOnAction(t -> {
    TreeItem<String> item = getTreeItem();
    item.getParent().getChildren().remove(item);        // remove item
    if(treeView.getRoot().getChildren().size() == 0) {  // check for empty tree
        treeView.getSelectionModel().clearSelection();
    }
    t.consume();
});
 类似资料:
  • 我正在使用ListView。向ListView添加新项很容易,但如何删除? 我通过以下方式构建ListView: Listcell Y的一个实例在setGraphics中设置了一个标签。要从我的观察列表中删除与之关联的字符串,我添加了一个MouseEvent处理程序: 虽然这段代码通过从ObservableList中删除每个单击的项目来工作,但listView的行为很奇怪。每次单击标签时,它都会成

  • 注意:我实际上不确定为什么这种情况首先需要监听器,因为我甚至不想经常监听,而是在调用方法时得到结果。

  • 问题内容: 请考虑以下示例Java类(下面的pom.xml): 我写一个FileOutputStream,然后尝试删除该文件, 而不先关闭Stream 。这是我最初的问题,当然是错误的,但它导致了一些奇怪的发现。 在Windows 7上运行主方法时,它将产生以下输出: 为什么第一次调用Files.delete()不会引发异常? 为什么以下对Files.exist()的调用返回false? 为什么无

  • 问题内容: 我有这段代码: 但我得到的结果是: 当然,我期望低于20的数字不会出现在结果中,我假设我对删除操作做错了什么。 问题答案: 在遍历列表时,你正在修改它。这意味着第一次遍历循环时,i==1,因此1从列表中删除。然后for循环转到列表中的第二项,不是2,而是3!然后从列表中删除,然后for循环转到列表中的第三项,现在是5。等等。也许这样更容易想象,用表示的值: 最初是列表的状态;然后删除1

  • 问题内容: @Entity public class Person { 给定以下类结构,当我尝试将新位置添加到“人的位置”列表中时,它总是导致以下SQL查询: 和 Hibernate(3.5.x / JPA 2)删除给定Person的所有关联记录,然后重新插入所有以前的记录以及新的记录。 我有一个想法,即Location上的equals / hashcode方法可以解决此问题,但是它没有任何改变。

  • 我有一个这样的列表视图 这很好,因为它会遍历我的 table1 项。该列表包含名称和价格。我的问题是如何通过鼠标点击一个项目来删除项目,然后点击一个说删除的按钮?我看到的问题是,我不知道有多少项目可用,这取决于用户添加了多少个项目。