2012-02-13 47 views
2

要創建我的動態JTree的,我正在讀上this website in chapter "4.2 OutlineNode.java"動態的JTree和SwingUtilities.invokeLater()什麼也不做

有關動態JTrees教程現在我已經實現了它並認識到,在GUI線程加載數據需要較長也很醜陋。因此我添加了一個展開子元素的線程,然後將TreeNode元素添加到樹中。

private void getChildNodes() { 
    areChildrenDefined = true; 
    Thread t = new Thread(new Runnable() 
    { 
     @Override 
     public void run() { 
     System.out.println("Expand"); 
      final List<DECTTreeNode> listNodes = new ArrayList<DECTTreeNode>(); 
      if (castNode().canExpand()) 
      { 
       for(DECTNode crt : castNode().getChildren()) 
       { 
        DECTTreeNode treeNode = new DECTTreeNode(crt); 
        listNodes.add(treeNode); 
       } 

       try { 
        SwingUtilities.invokeAndWait(new Runnable() 
        { 
         @Override 
         public void run() { 
          System.out.println(listNodes.size()); 
          for (DECTTreeNode crt : listNodes) 
          { 
           add(crt); // <==== Adds the node to the JTree 
          } 
         } 

        }); 
        //}).run(); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 

      } 
     } 

    }); 
    t.start(); 
} 

沒有線程,它的工作沒有問題。如果我添加線程並將add調用放入SwingUtilities.invokeAndWait(...),孩子似乎會擴展,但它們在樹中不可見。

我已經在樹上嘗試過revalidate()repaint() - 沒有任何影響。

任何想法如何使這些元素可見?

預先感謝您。

+0

我認爲這個代碼什麼也沒做,你可以請印出從TreeModel的節點,如果包含東西 – mKorbel 2012-02-13 11:49:19

+0

如上所述:它似乎沒有線程工作。如果我展開它們並在同一個線程中顯示所有節點,它將起作用。 – Atmocreations 2012-02-13 12:24:47

回答

2
@Override 
public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException { 
    CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent(); 
    t.addChildLoadedListener(new ChildLoadedListener() { 
     @Override 
     public void childLoaded(TreeNode parent) { 
      ((CustomTreeNode) parent).setExpanded(true); 
      expandPath(new TreePath(((CustomTreeNode) parent).getPath())); 
     } 
    }); 
    if (!t.isExpanded()) { 
     factory.loadChildren(t); 
     throw new ExpandVetoException(null); 
    } 
} 

public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException { 
    CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent(); 
    t.setExpanded(false); 
} 

Jugde我還是不行。這對我有用。 CustomTreeNode是從defaultMutableTreeNode擴展而來的,並且已經添加了一個自寫的ChildLoadedListener,它在工廠加載子項時調用。 isExpanded布爾值是爲了避免無限循環。 工廠創建一個SwingWorker來加載子項並執行它。之後調用ChilLoadedListener並再次展開樹。

希望這將幫助,或者至少幫你想想你的問題;-)

編輯:

@Override 
public void loadChildren(CustomTreeNode tn) { 
    ctn = tn; 
    LoadChildrenWorker worker = new LoadChildrenWorker(); 
    worker.execute(); 
} 


private class LoadChildrenWorker extends SwingWorker<String, Object> { 

      @Override 
    protected String doInBackground() throws Exception { 
        //load source here and return a string when finished. 
        //In my case its a string repesentation of a directory 
    } 

    @Override 
    protected void done() { 
      //with get(), you get the string from doBackground() 
      for (String str : parseFromOutput(get())) { 
        if (str.endsWith("/")) { 
         ctn.add(new CustomTreeNode("Directory"); 
        } else { 
         ctn.add(new CustomTreeNode("Leaf"); 
        } 
      } 
      //call listeners 
      ctn.fireChildrenLoaded(); 
    } 
+0

感謝您的迴應。你能告訴我你的SwingWorker的'finished()'方法嗎?在迭代先前註冊的偵聽器時,我以某種方式得到'ConcurrentModificationException'。非常感謝 – Atmocreations 2012-02-13 16:26:52

+0

所以我添加了我的SwingWorker代碼。 – NotANormalNerd 2012-02-14 08:25:04

+0

非常感謝。我已經忘記了一些東西......現在它起作用了! – Atmocreations 2012-02-15 10:36:09

0

嘗試用invokeLater替換invokeAndWait

+0

已經嘗試過。也沒有工作。 – Atmocreations 2012-02-13 12:19:59

3

檢查你的add()方法觸發正確的TreeModelEvent

+0

對不起,造成了混亂。在我的問題中的片段是'擴展DefaultMutableTreeNode'類的一部分。因此,'add()'做父母'DefaultMutableTreeNode'的功能。 – Atmocreations 2012-02-13 12:23:51

+0

那就是這個問題。當調用add()時,DefaultMutableTreeNode不會觸發事件。您需要通過模型添加節點,請參閱DefaultTreeModel#insertNodeInto(或手動激發正確的事件)。 – 2012-02-13 16:39:52

1

我已經在我現在正在從事的項目同樣的問題。我使用TreeWillExpandListener來確定何時加載我的樹。 (LazyLoading) 當樹展開時,我捕獲了一個加載的節點,它是線程內的子節點,因爲我必須從服務器輸出中解析節點。

您面臨的問題是樹在您的孩子被加載之前展開。所以你必須扔ExpandVetoException或類似的東西,等到你的孩子被加載。然後展開你的樹。在這種情況下,一切都會顯示正確。

希望能夠希望你的問題。

Exapand->停止expand-> loadchildren-> addChildren - >現在展開樹 - >看你的節點

編輯:

如果您有迴轉工作,你最好使用SwingWorker的。對我更好。

+0

即使我沒有嘗試這種方式,我猜它不會工作:摺疊節點時,我不刪除內容。因此,在再次摺疊和重新展開節點時應該可見。但事實並非如此。或者,這是另一個不同的問題? – Atmocreations 2012-02-13 12:22:50

+0

我有同樣的問題。他們每次打開樹時都會載入孩子。並且在你加載孩子之前每次打開樹。 'System.out.println()'是我們的朋友;-)(正如我以前的老師總是說的) – NotANormalNerd 2012-02-13 13:06:14

+0

沒有冒犯,但即使您的解決方案似乎適用於您,我認爲這看起來像一個醜陋的黑客?那麼......如上所述:我有一面旗幟,確保兒童只能展開一次。他們將被保存在一個列表中,並返回... – Atmocreations 2012-02-13 13:18:20

3

由Walter +1擴大的建議,從JButton的addButton調用Runnable#Thread

import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import javax.swing.event.*; 
import javax.swing.tree.*; 

public class DynamicTreeDemo extends JPanel implements ActionListener { 

    private static final long serialVersionUID = 1L; 
    private int newNodeSuffix = 1; 
    private static String ADD_COMMAND = "add"; 
    private static String REMOVE_COMMAND = "remove"; 
    private static String CLEAR_COMMAND = "clear"; 
    private DynamicTree treePanel; 

    public DynamicTreeDemo() { 
     super(new BorderLayout()); // Create the components. 
     treePanel = new DynamicTree(); 
     populateTree(treePanel); 
     JButton addButton = new JButton("Add"); 
     addButton.setActionCommand(ADD_COMMAND); 
     addButton.addActionListener(this); 
     JButton removeButton = new JButton("Remove"); 
     removeButton.setActionCommand(REMOVE_COMMAND); 
     removeButton.addActionListener(this); 
     JButton clearButton = new JButton("Clear"); 
     clearButton.setActionCommand(CLEAR_COMMAND); 
     clearButton.addActionListener(this); // Lay everything out. 
     treePanel.setPreferredSize(new Dimension(300, 150)); 
     add(treePanel, BorderLayout.CENTER); 
     JPanel panel = new JPanel(new GridLayout(0, 3)); 
     panel.add(addButton); 
     panel.add(removeButton); 
     panel.add(clearButton); 
     add(panel, BorderLayout.SOUTH); 
    } 

    public void populateTree(DynamicTree treePanel) { 
     String p1Name = "Parent 1"; 
     String p2Name = "Parent 2"; 
     String c1Name = "Child 1"; 
     String c2Name = "Child 2"; 
     DefaultMutableTreeNode p1, p2; 
     p1 = treePanel.addObject(null, p1Name); 
     p2 = treePanel.addObject(null, p2Name); 
     treePanel.addObject(p1, c1Name); 
     treePanel.addObject(p1, c2Name); 
     treePanel.addObject(p2, c1Name); 
     treePanel.addObject(p2, c2Name); 
    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     String command = e.getActionCommand(); 
     if (ADD_COMMAND.equals(command)) { // Add button clicked 
      treePanel.addObject("New Node " + newNodeSuffix++); 
     } else if (REMOVE_COMMAND.equals(command)) { // Remove button clicked 
      treePanel.removeCurrentNode(); 
     } else if (CLEAR_COMMAND.equals(command)) { // Clear button clicked. 
      treePanel.clear(); 
     } 
    } 


    private static void createAndShowGUI() { // Create and set up the window. 
     JFrame frame = new JFrame("DynamicTreeDemo"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Create and set up the content pane. 
     DynamicTreeDemo newContentPane = new DynamicTreeDemo(); 
     newContentPane.setOpaque(true); // content panes must be opaque 
     frame.setContentPane(newContentPane); // Display the window. 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 
     javax.swing.SwingUtilities.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       createAndShowGUI(); 
      } 
     }); 
    } 
} 


class DynamicTree extends JPanel { 
    private static final long serialVersionUID = 1L; 

    private DefaultMutableTreeNode rootNode; 
    private DefaultTreeModel treeModel; 
    private JTree tree; 
    private Toolkit toolkit = Toolkit.getDefaultToolkit(); 

    public DynamicTree() { 
     super(new GridLayout(1, 0)); 
     rootNode = new DefaultMutableTreeNode("Root Node"); 
     treeModel = new DefaultTreeModel(rootNode); 
     treeModel.addTreeModelListener(new MyTreeModelListener()); 
     tree = new JTree(treeModel); 
     tree.setEditable(true); 
     tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 
     tree.setShowsRootHandles(true); 
     JScrollPane scrollPane = new JScrollPane(tree); 
     add(scrollPane); 
    } 

    public void clear() { 
     rootNode.removeAllChildren(); 
     treeModel.reload(); 
    } 

    public void removeCurrentNode() { 
     TreePath currentSelection = tree.getSelectionPath(); 
     if (currentSelection != null) { 
      DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent()); 
      MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent()); 
      if (parent != null) { 
       treeModel.removeNodeFromParent(currentNode); 
       return; 
      } 
     } // Either there was no selection, or the root was selected. 
     toolkit.beep(); 
    } 


    public DefaultMutableTreeNode addObject(Object child) { 
     DefaultMutableTreeNode parentNode = null; 
     TreePath parentPath = tree.getSelectionPath(); 
     if (parentPath == null) { 
      parentNode = rootNode; 
     } else { 
      parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent()); 
     } 
     return addObject(parentNode, child, true); 
    } 

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child) { 
     return addObject(parent, child, false); 
    } 

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) { 
     DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child); 
     if (parent == null) { 
      parent = rootNode; 
     } 
     // It is key to invoke this on the TreeModel, and NOT DefaultMutableTreeNode 
     treeModel.insertNodeInto(childNode, parent, parent.getChildCount()); 
     // Make sure the user can see the lovely new node. 
     if (shouldBeVisible) { 
      tree.scrollPathToVisible(new TreePath(childNode.getPath())); 
     } 
     return childNode; 
    } 



    class MyTreeModelListener implements TreeModelListener { 

     @Override 
     public void treeNodesChanged(TreeModelEvent e) { 
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());   /* 
      * If the event lists children, then the changed node is the child of the 
      * node we've already gotten. Otherwise, the changed node and the 
      * specified node are the same. 
      */ int index = e.getChildIndices()[0]; 
      node = (DefaultMutableTreeNode) (node.getChildAt(index)); 
      System.out.println("The user has finished editing the node."); 
      System.out.println("New value NodesChanged: " + node.getUserObject()); 
     } 

     @Override 
     public void treeNodesInserted(TreeModelEvent e) { 
      DefaultMutableTreeNode node= (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());   /* 
      * If the event lists children, then the changed node is the child of the 
      * node we've already gotten. Otherwise, the changed node and the 
      * specified node are the same. 
      */ int index = e.getChildIndices()[0]; 
      node = (DefaultMutableTreeNode) (node.getChildAt(index)); 
      System.out.println("New value NodesInserted : " + node.getUserObject()); 
     } 

     @Override 
     public void treeNodesRemoved(TreeModelEvent e) { 
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());   /* 
      * If the event lists children, then the changed node is the child of the 
      * node we've already gotten. Otherwise, the changed node and the 
      * specified node are the same. 
      */ int index = e.getChildIndices()[0]; 
      node = (DefaultMutableTreeNode) (node.getChildAt(index)); 
      System.out.println("New value NodesRemoved : " + node.getUserObject()); 
     } 

     @Override 
     public void treeStructureChanged(TreeModelEvent e) { 
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());   /* 
      * If the event lists children, then the changed node is the child of the 
      * node we've already gotten. Otherwise, the changed node and the 
      * specified node are the same. 
      */ int index = e.getChildIndices()[0]; 
      node = (DefaultMutableTreeNode) (node.getChildAt(index)); 
      System.out.println("New value StructureChanged : " + node.getUserObject()); 
     } 
    } 
}