2014-02-20 3 views
3

В моем приложении у меня есть 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(). Мой второй вопрос: авто-выбор нормальный?

Заключительный вопрос, есть ли лучшее обходное решение?

Извините, вопрос был очень длинный. Спасибо, если вы все еще читаете :)

+0

Да, для меня выбор просто не обновляется, когда я удалить последний элемент. – Gman

ответ

-1

У меня была такая же проблема, вы должны передать наблюдаемую коллекцию своему корневому узлу.

В своем коде, вместо:

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

Вы заметите, что обновления на этот раз все будет хорошо. Вы должны удалить все ключи обхода. Кнопка обновления больше не понадобится.

+1

Использование ObservableList в качестве входа в 'addAll' не изменит поведение корневых-детей. Реализация 'addAll' проходит через данную коллекцию (!) С помощью foreach и выполняет« добавление »на цель. Поэтому не имеет значения, какую коллекцию вы вкладываете в 'addAll', или если вы вызываете' add' на цель для каждого элемента. –

1

В Java 1.8.0_92 поведение кажется:

  • если элемент позади выбора удаляется SelectedItem и SelectedIndex остаются теми же,
  • если элемент перед выбором удален SelectedItem остается то же, но selectedIndex изменяется (потому что selectedItem изменил свою позицию),
  • , если выбранный элемент удален selectedIndex уменьшен и выбранItem изменяется аналогично; только если первый элемент удален. SelectedIndex остается неизменным.

Единственное странное поведение возникает, если последний элемент удален. В этом случае selectedIndex и selectedItem не изменяются, как заметил Gman. Это приводит к отсутствию уже существующего элемента (кажется, это ошибка).

В качестве обходного пути вы можете проверить корень-детей в вашем коде удаления.

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(); 
}); 
Смежные вопросы