2014-02-20 61 views
3

在我的應用程序中,左側有一個TreeView,根據TreeView中的選擇更新右側的窗格。一個非常簡單的場景。當選擇爲空,我表明,像在窗格中「請選擇」,即我也在TreeView手柄零點選擇的消息。JavaFX - 刪除所選項目時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件物品。如果用戶點擊某個TreeItem,則會在selectedItemProperty附加聽衆。如果用戶沒有點擊某個項目,但所選項目的值由於某種原因而改變,則所選的TreeItemvalueProperty也附加了聽衆。用戶可以通過點擊其中的刪除按鈕來刪除特定的項目。在任何時候,用戶都可以點擊更新按鈕來更新顯示當前選擇的標籤內容,以防我的處理程序錯過某些事件。

在這個簡單的測試程序,當我除去所選擇的項目,它有時顯示的項目只是刪除了一個作爲被選項目之前,有時僅示出拆下的一個作爲選擇的項目之後的項目。 但是,所選項目大部分時間都不會更改,即使它不再包含在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()。我的第二個問題是,自動選擇是否正常?

最後一個問題,有沒有更好的解決方法嗎?

對不起,這是一個非常長的問題。謝謝你,如果你仍然在閱讀:)

+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通過給定的集合(!),並在目標上執行一個「add」。因此,將什麼樣的集合放入'addAll'中,或者如果在每個元素的目標上調用'add',都沒有區別。 –

1

在Java 1.8.0_92的行爲似乎是:

  • 如果選擇後面的項目被刪除則selectedItem和的selectedIndex保持不變,
  • 如果選擇前一個項目被刪除則selectedItem停留在相同但selectedIndex更改(因爲selectedItem更改其位置),
  • 如果所選項目已刪除selectedIndex減少並且selectedItem更改類似;只有當第一項被刪除時,selectedIndex纔會保持不變。

如果最後一項被刪除,唯一的奇怪行爲發生。正如Gman也指出的,在這種情況下,selectedIndex和selectedItem不會改變。這導致不再存在的項目被選中(似乎是一個錯誤)。

作爲一種解決方法,您可以檢查刪除代碼中的根子項。

的刪除按鈕可能看起來像這樣的的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(); 
});