В моем приложении у меня есть TreeView
с левой стороны, и я обновляю панель с правой стороны в соответствии с выбором в TreeView
. Очень прямой сценарий. Когда выбор нулевой, я показываю сообщение, например «сделайте выбор» на панели, т. Е. Также обрабатывает нулевой выбор в TreeView
.JavaFX - странное поведение TreeView при удалении выбранного элемента
В течение срока службы приложения некоторые предметы могут быть добавлены/удалены из TreeView
. У меня возникли проблемы, когда выбранный элемент в TreeView
удален. В этом случае я ожидал, что выбор TreeView
станет нулевым, однако это не так!
Для отладки этого случая, я writed простого приложения 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
. Слушатель также присоединяется к valueProperty
выбранного TreeItem
в случае, если пользователь не нажимает на какой-либо элемент, но по какой-либо причине значение выбранного элемента изменяется. Пользователь может удалить определенный элемент, нажав кнопку удаления внутри него. В любой момент пользователь может нажать кнопку обновления, чтобы обновить содержимое ярлыков, отображающее текущий выбор, в случае, если какое-то событие упустило мои обработчики.
В этом простом тестовом приложении, когда я удаляю выбранный элемент, он иногда показывает элемент непосредственно перед удаленным как выбранный элемент, а иногда показывает элемент сразу после удаления в качестве выбранного элемента. Однако выбранный элемент не изменяет большую часть времени, даже если он не содержится в TreeView
!
Мой первый вопрос: это нормально или ошибка? Вы когда-нибудь видели что-то подобное?
В качестве обходного пути, я добавил следующий код сразу после того, как цикл:
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()
. Мой второй вопрос: авто-выбор нормальный?
Заключительный вопрос, есть ли лучшее обходное решение?
Извините, вопрос был очень длинный. Спасибо, если вы все еще читаете :)
Да, для меня выбор просто не обновляется, когда я удалить последний элемент. – Gman