2013-02-22 27 views
5

Tôi có một ứng dụng có chứa JTree được sao lưu bằng một DefaultTreeModel được sử dụng để hiển thị phân cấp các tệp phản ánh một phần của hệ thống tệp trên máy chủ của tôi (tôi sẽ tham khảo đây là ứng dụng khách của tôi). Tôi cũng có một ứng dụng máy chủ cung cấp dữ liệu mà ứng dụng khách của tôi cần hiển thị (tôi sẽ xem đây là ứng dụng máy chủ ) của tôi. Tôi đang sử dụng phương pháp "tải trẻ em lười biếng" để tôi chỉ phải tải các tệp vào cây nếu người dùng quan tâm đến chúng. Cách tiếp cận tải lười biếng:Khôi phục trạng thái mở rộng/lựa chọn trong JTree với tải Lazy

  1. tôi ghi đè treeWillExpand(TreeExpansionEvent evt)
  2. tôi đặt con đường lựa chọn là của các nút mở rộng.
  3. Sau đó, tôi gửi thư đến máy chủ yêu cầu con của nút đó.
  4. Khi máy chủ phản hồi tôi nhận được thành phần đường dẫn được chọn cuối cùng.
  5. Sau đó, tôi sử dụng DefaultTreeModel.insertNodeInto() cho mỗi tệp dữ liệu được trả về.
  6. Cuối cùng tôi gọi DefaultTreeModel.nodeStructureChanged().

Các công trình trên tốt và tôi không gặp bất kỳ vấn đề nào với việc tải trẻ em một cách lười biếng. Vấn đề của tôi xuất hiện khi dữ liệu mới được tải lên máy chủ và tôi muốn cập nhật cây để không chỉ bao gồm dữ liệu mới, mà còn để đặt trạng thái mở rộng và nút đã chọn thành cái mà trước khi cập nhật cây (để người dùng không bị giật trên cây chỉ vì có dữ liệu mới để xem). Dòng chảy như sau:

  1. dữ liệu mới được tải lên máy chủ
  2. tài liệu lưu trữ ứng dụng máy chủ dữ liệu này và populates một cơ sở dữ liệu thông tin về các tập tin tải lên.
  3. Ứng dụng máy chủ thông báo cho ứng dụng khách rằng dữ liệu mới đã được tải lên.
  4. ứng dụng khách hàng tiết kiệm trạng thái mở rộng của cây sử dụng JTree.getExpandedDescendants()
  5. ứng dụng khách hàng tiết kiệm con đường lựa chọn của cây sử dụng JTree.getSelectionPath()
  6. ứng dụng khách hàng loại bỏ tất cả các nút từ DefaultTreeModel.
  7. Ứng dụng khách yêu cầu dữ liệu từ máy chủ bắt đầu bằng nút gốc.
  8. Ứng dụng khách đi qua các liệt kê đường dẫn cây được trả lại từ JTree.getExpandedDescendants() gọi JTree.expandPath() trên mỗi TreePath trong điều tra.
  9. Ứng dụng khách sẽ đặt đường dẫn cây đã chọn.

Vấn đề của tôi là dù tôi thử GUI của cây nào không được cập nhật để phản ánh trạng thái mở rộng. Tôi biết rằng lệnh gọi hàm expandPath của tôi đang hoạt động vì tôi có thể xem yêu cầu dữ liệu được gửi từ máy khách và phản hồi với dữ liệu từ máy chủ cho mỗi cuộc gọi đến expandPath. Tôi cũng hiển thị thông tin về nút hiện đang được chọn trong cửa sổ khác và nó đang hiển thị nút được chọn chính xác. Nhưng, than ôi, với sự thất vọng của tôi, GUI chỉ hiển thị nút gốc (mở rộng) và nó là con (không được mở rộng) thay vì trạng thái mở rộng trước đó.

Vì vậy, câu hỏi của tôi là: Làm cách nào để khôi phục trạng thái mở rộng của JTree để GUI vẫn giữ nguyên trước và sau khi cập nhật mô hình dữ liệu?

Đây là một vài trong số những điều mà tôi đã cố gắng:

  • tôi thấy a thread với một thiết lập tương tự như mỏ và vấn đề của mình đã được giải quyết bằng cách ghi đè equals()hashCode() nhưng điều đó không làm việc cho tôi.
  • phương pháp khác nhau để gọi mở rộng như: setExpandsSelectedPaths(true), nodeStructureChanged(), JTree.invalidate()
  • Nhiều biến thể khác nhau về tiết kiệm trạng thái mở rộng, tuy nhiên, tôi không tin trạng thái mở rộng là không chính xác như tôi có thể nhìn thấy các dữ liệu chính xác được truyền lại và giữa ứng dụng khách và ứng dụng máy chủ.

Đây là SSCCE tôi:

package tree.sscce; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.SwingUtilities; 
import java.awt.BorderLayout; 
import javax.swing.JScrollPane; 
import javax.swing.JTree; 
import javax.swing.JButton; 
import java.util.Enumeration; 
import javax.swing.BoxLayout; 
import javax.swing.event.TreeExpansionEvent; 
import javax.swing.event.TreeWillExpandListener; 
import javax.swing.tree.DefaultMutableTreeNode; 
import javax.swing.tree.DefaultTreeModel; 
import javax.swing.tree.ExpandVetoException; 
import javax.swing.tree.MutableTreeNode; 
import javax.swing.tree.TreePath; 
import java.awt.event.ActionListener; 
import java.awt.event.ActionEvent; 
import javax.swing.JTextPane; 

public class TreeSSCCE extends JFrame implements TreeWillExpandListener { 

private static final long serialVersionUID = -1930472429779070045L; 

public static void main(String[] args) 
{ 
    SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      TreeSSCCE inst = new TreeSSCCE(); 
      inst.setLocationRelativeTo(null); 
      inst.setVisible(true); 
      inst.setDefaultCloseOperation(EXIT_ON_CLOSE); 
     }   
    }); 
} 

private DefaultMutableTreeNode rootNode; 
private JTree tree; 
private DefaultTreeModel treeModel; 
private TreePath selectionPathPriorToNewData; 
private Enumeration<TreePath> expandedPathsPriorToNewData; 
private int treeSize = 5; 

public TreeSSCCE() { 

    this.setBounds(0, 0, 500, 400); 
    JPanel mainPanel = new JPanel(); 
    getContentPane().add(mainPanel, BorderLayout.CENTER); 
    mainPanel.setBounds(0, 0, 500, 400); 
    mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); 

    JPanel descriptionPanel = new JPanel(); 
    descriptionPanel.setBounds(0, 0, 500, 200); 
    mainPanel.add(descriptionPanel); 

    JTextPane textPane = new JTextPane(); 
    String newLine = System.getProperty("line.separator"); 
    descriptionPanel.setLayout(new BorderLayout(0, 0)); 
    textPane.setText("Start by expanding some nodes then click 'Add New Data' and you will notice that the tree state is not retained."); 
    descriptionPanel.add(textPane); 

    // Initialize The Tree 
    tree = new JTree(); 
    rootNode = new DefaultMutableTreeNode("Root"); 
    treeModel = new DefaultTreeModel(rootNode); 
    tree.addTreeWillExpandListener(this); 
    tree.setModel(treeModel); 
    tree.setShowsRootHandles(true); 
    populateTree(false); 

    JScrollPane scrollPane = new JScrollPane(tree); 
    mainPanel.add(scrollPane); 

    JPanel buttonPanel = new JPanel(); 
    mainPanel.add(buttonPanel); 

    JButton btnAddNewData = new JButton("Add New Data"); 
    btnAddNewData.addActionListener(new ActionListener() { 
     public void actionPerformed(ActionEvent arg0) { 
      addNewDataToTree(); 
     } 
    }); 
    buttonPanel.add(btnAddNewData); 


} 

private void removeAllTreeNodes() 
{ 

    while(!treeModel.isLeaf(treeModel.getRoot())) 
    { 
     treeModel.removeNodeFromParent((MutableTreeNode)treeModel.getChild(treeModel.getRoot(),0)); 
    } 
    treeModel = null; 
    treeModel = new DefaultTreeModel(rootNode); 
    tree.setModel(treeModel); 
} 

public void restoreExpansionState(Enumeration enumeration) 
{ 
    if (enumeration != null) 
    { 
     while (enumeration.hasMoreElements()) 
     { 
      TreePath treePath = (TreePath) enumeration.nextElement(); 
      tree.expandPath(treePath); 
      tree.setSelectionPath(treePath); 
     } 
     tree.setSelectionPath(selectionPathPriorToNewData); 
    } 
} 

protected void addNewDataToTree() 
{ 
    // save the tree state 
    selectionPathPriorToNewData = tree.getSelectionPath(); 
    expandedPathsPriorToNewData = tree.getExpandedDescendants(new TreePath(tree.getModel().getRoot())); 
    removeAllTreeNodes(); 
    populateTree(true); 
    restoreExpansionState(expandedPathsPriorToNewData); 
} 

private void populateTree(boolean newData) 
{ 
    if(newData) 
     treeSize++; 
    MyParentNode[] parents = new MyParentNode[treeSize]; 
    for(int i = 0; i < treeSize; i++) 
    { 
     parents[i] = new MyParentNode("Parent [" + i + "]"); 
     treeModel.insertNodeInto(parents[i], rootNode, i); 
    } 
} 

@Override 
public void treeWillCollapse(TreeExpansionEvent evt) throws ExpandVetoException { 
    // Not used. 
} 

@Override 
public void treeWillExpand(TreeExpansionEvent evt) throws ExpandVetoException 
{ 
    System.out.println("Tree expanding: " + evt.getPath()); 
    tree.setExpandsSelectedPaths(true); 
    tree.setSelectionPath(evt.getPath()); 
    // we have already loaded the top-level items below root when we 
    // connected so lets just return... 
    if(evt.getPath().getLastPathComponent().equals(treeModel.getRoot())) 
     return; 

    // if this is not root lets figure out what we need to do. 
    DefaultMutableTreeNode expandingNode = (DefaultMutableTreeNode) evt.getPath().getLastPathComponent(); 

    // if this node already has children then we don't want to reload so lets return; 
    if(expandingNode.getChildCount() > 0) 
     return;  
    // if this node has no children then lets add some 
    MyParentNode mpn = new MyParentNode("Parent Under " + expandingNode.toString()); 
    treeModel.insertNodeInto(mpn, expandingNode, expandingNode.getChildCount()); 
    for(int i = 0; i < 3; i++) 
    { 
     treeModel.insertNodeInto(new DefaultMutableTreeNode("Node [" + i + "]"), mpn, i); 
    } 
} 

private class MyParentNode extends DefaultMutableTreeNode 
{ 
    private static final long serialVersionUID = 433317389888990065L; 
    private String name = ""; 

    public MyParentNode(String _name) 
    { 
     super(_name); 
     name = _name; 
    } 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + getOuterType().hashCode(); 
     result = prime * result + ((name == null) ? 0 : name.hashCode()); 
     return result; 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (getClass() != obj.getClass()) 
      return false; 
     MyParentNode other = (MyParentNode) obj; 
     if (!getOuterType().equals(other.getOuterType())) 
      return false; 
     if (name == null) { 
      if (other.name != null) 
       return false; 
     } else if (!name.equals(other.name)) 
      return false; 
     return true; 
    } 

    @Override 
    public boolean isLeaf() 
    { 
     return false; 
    } 

    private TreeSSCCE getOuterType() { 
     return TreeSSCCE.this; 
    }  
} 

} 

Cảm ơn trước sự giúp đỡ nào bạn có thể cung cấp.

P.S. Đây là câu hỏi đầu tiên của tôi vì vậy xin vui lòng cho tôi biết nếu tôi yêu cầu đúng (và mang nó dễ dàng trên tôi;)).

+0

Wow, +1 cho nhiều thông tin hữu ích trả trước! Tôi nghi ngờ rằng bạn sẽ phải xây dựng lại một đường dẫn cây bằng cách sử dụng các nút mới, dựa trên các nút cũ. Tôi giả sử bạn có một số cách để nhận dạng mỗi nút? Tạo một 'String' đơn giản (ví dụ), đại diện cho tất cả các đường dẫn mở rộng. Khi bạn đến để cập nhật cây, sử dụng các đường dẫn 'String' này, hãy tạo đường dẫn cây mới từ các nút có sẵn ... – MadProgrammer

+1

' FileTreeModel' trích dẫn [ở đây] (http://stackoverflow.com/a/14837208/230513) có thể đơn giản hóa mã của bạn, nếu không loại bỏ vấn đề. – trashgod

+0

@trashgod 'FileTreeModel' sẽ không hoạt động đối với tôi vì các nút của tôi là các đối tượng tùy chỉnh có thông tin về tệp cũng như thông tin ứng dụng cụ thể. Ngoài ra, ứng dụng khách của tôi không có quyền truy cập trực tiếp vào các tệp này như là tệp nằm trên máy chủ. – Jeremy

Trả lời

1

Tôi đang sử dụng mô hình cây tùy chỉnh (mở rộng DefaultTreeModel) và phản hồi trong sự kiện DBTreeEvent.STRUCTURE_CHANGED để xử lý điều này. Đây là những gì tôi làm để bảo tồn trạng thái cũ. Không chắc chắn nếu nó sẽ giúp bạn hay không ..

//NOTE: node is the tree node that caused the tree event 
TreePath nodesPath = new TreePath(node.getPath()); 
TreePath currentSel = myTree.getLeadSelectionPath(); 
List<TreePath> currOpen = getCurrExpandedPaths(nodesPath); 
super.nodeStructureChanged(node); 
reExpandPaths(currOpen); 
myTree.setSelectionPath(currentSel); 

private List<TreePath> getCurrExpandedPaths(TreePath currPath) 
{ 
    List<TreePath> paths = new ArrayList<TreePath>(); 
    Enumeration<TreePath> expandEnum = myTree.getExpandedDescendants(currPath); 
    if (expandEnum == null) 
     return null; 

    while (expandEnum.hasMoreElements()) 
     paths.add(expandEnum.nextElement()); 

    return paths; 
} 

private void reExpandPaths(List<TreePath> expPaths) 
{ 
    if(expPaths == null) 
     return; 
    for(TreePath tp : expPaths) 
     myTree.expandPath(tp); 
} 
Các vấn đề liên quan