JDK-4296946 : TreeModel insertNodeInto/removeNodeFromParent shouldn't chg tree's expand state
  • Type: Enhancement
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.2.2,1.3.0
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic,windows_nt
  • CPU: generic,x86
  • Submitted: 1999-12-05
  • Updated: 2017-05-23
Related Reports
Relates :  
Description
Name: krT82822			Date: 12/04/99


java version "1.3beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3beta-O)
Java(TM) HotSpot Client VM (build 1.3beta-O, mixed mode)

/*
Problem: insertNodeInto and removeNodeFromParent of JTreeModel
         should not modify collapse/expand state.

Setup:   java.version 1.3 beta and 1.2.2, NT 4.0 SP4

java version "1.3beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3beta-O)
Java(TM) HotSpot Client VM (build 1.3beta-O, mixed mode)

Background:

Your article called Understanding the TreeModel says the following:
http://java.sun.com/products/jfc/tsc/articles/jtree/index.html

"Since DefaultMutableTreeNode objects implement swing.tree.MutableTreeNode,
you can avoid the two-step process of making changes and notifying the
model of them by using the following methods, which make changes directly
to the DefaultTreeModel:"

public void insertNodeInto(MutableTreeNode newChild,
                           MutableTreeNode parent, int index){
public void removeNodeFromParent(MutableTreeNode node) {

Problem

I need to change the sort order of folders. I initially have a tree with
folders Apollo and Skylab. Skylab comes before Apollo i.e. descending.
I need to have Apollo come before Skylab i.e. ascending.

However, when I use removeNodeFromParent and insertNodeInto to achieve
this as recommended by your article, it loses the infomation about the
expansion of Apollo's and Skylab's children. Specifically, initially
the children are expanded and after using removeNodeFromParent and
insertNodeInto, the children are collapsed. This is bad since I only
want to change the order of the folders and preserve the same
expand/collapse state for all nodes.

Ideally, when I remove apolloNode and skylabNode using
removeNodeFromParent, their child node expand/collapse states
should not change. When I then insert apolloNode and
skylabNode using insertNodeInto, insertNodeInto should look at
the expand/collapse state of apolloNode and skylabNode and
insert them using their expand/collapse state. I've tried to
trace through the source code but there are so many places that
it examines and changes the expand/collapse state that I can't
readily find which line of code causes the problem.

Since I need to resort folders frequently in my application,
I need a reliable way to move folders while preserving the
expand/collapse state of all the nodes. Any feedback is appreciated.

Thanks

###@###.###

*/

//Author:       John Petrula
//Company:      Z-FAST
//Description:  ###@###.###

package com.petrula.bug.JTreeInsertRemoveCollapse;

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

public class TreeExample extends JTree {

  static DefaultMutableTreeNode rootNode   = null;
  static DefaultMutableTreeNode skylabNode = null;
  static DefaultMutableTreeNode apolloNode = null;

	public TreeExample() {

		rootNode = new DefaultMutableTreeNode();

		skylabNode = new DefaultMutableTreeNode("Skylab");
		rootNode.add(skylabNode);

		apolloNode = new DefaultMutableTreeNode("Apollo");
		rootNode.add(apolloNode);

		DefaultMutableTreeNode n =
			new DefaultMutableTreeNode("11");
		apolloNode.add(n);
		n.add(new DefaultMutableTreeNode("Neil Armstrong"));
		n.add(new DefaultMutableTreeNode("Buzz Aldrin"));
		n.add(new DefaultMutableTreeNode("Michael Collins"));

		n = new DefaultMutableTreeNode("12");
		apolloNode.add(n);
		n.add(new DefaultMutableTreeNode("Pete Conrad"));
		n.add(new DefaultMutableTreeNode("Alan Bean"));
		n.add(new DefaultMutableTreeNode("Richard Gordon"));

		n = new DefaultMutableTreeNode("2");
		skylabNode.add(n);
		n.add(new DefaultMutableTreeNode("Pete Conrad"));
		n.add(new DefaultMutableTreeNode("Joseph Kerwin"));
		n.add(new DefaultMutableTreeNode("Paul Weitz"));

		n = new DefaultMutableTreeNode("3");
		skylabNode.add(n);
		n.add(new DefaultMutableTreeNode("Alan Bean"));
		n.add(new DefaultMutableTreeNode("Owen Garriott"));
		n.add(new DefaultMutableTreeNode("Jack Lousma"));

		this.setModel(new DefaultTreeModel(rootNode));

    expandAll((TreeNode)getModel().getRoot());
	}


  public void expandAll(TreeNode tNode) {

    TreePath tp = new TreePath(((DefaultMutableTreeNode)tNode).getPath());
    expandPath(tp);

    int i=0;
    for(; i<tNode.getChildCount(); i++)
      expandAll(tNode.getChildAt(i));
  }

  public static void main(String[] args) {
    try {
      // Windows L&F
      UIManager.setLookAndFeel
("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");

      // Create JFrame
      JFrame myFrame = new JFrame("Tree Bug");

      myFrame.addWindowListener(new java.awt.event.WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });

		  TreeExample tree = new TreeExample();

		  myFrame.getContentPane().add(new JScrollPane(tree));

      myFrame.pack();

      myFrame.setVisible(true);

      Thread.sleep(5000);

      DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();

      treeModel.removeNodeFromParent(apolloNode);
      treeModel.removeNodeFromParent(skylabNode);

      treeModel.insertNodeInto(apolloNode, rootNode, 0);
      treeModel.insertNodeInto(skylabNode, rootNode, 1);
    }
    catch( Exception e) {
      e.printStackTrace();
    }
  }
}
(Review ID: 98619) 
======================================================================

Name: krC82822			Date: 12/14/2000


JavaTM 2 Platform, Standard Edition,?v1.2.2
java version "1.2.2"
Classic VM (build JDK-1.2.2-001, native threads, symcjit)


REF: See existing Bug ID: 4296946

The problem is described well in the above bug report and I report it again
because of the following, anonymous comment from the JTree.java code,
treeStructureChanged(TreeModelEvent e) method, which suggests that the problem
is known about and can, perhaps be fixed:

	    // NOTE: If I change this to NOT remove the descendants
	    // and update BasicTreeUIs treeStructureChanged method
	    // to update descendants in response to a treeStructureChanged
	    // event, all the children of the event won't collapse!

In my case, I have the following tree structure (for example) in the display:

body
    -arm
        -hand
             -finger
                    -nail

If I use our Java interface to add another arm to the body, the following is
what I get:

body
    +arm
    +arm

Where '-' represents expanded and '+' represents collapsed tree structure.

Despite the evaluation comments for the above bug report, I maintain that this
is a real problem that should be fixed in the JTree code and not by application
work around.
(Review ID: 113781)
======================================================================

Comments
WORK AROUND Name: krC82822 Date: 12/14/2000 There are suggestions in the original bug report to workaround. Basically, the application remembers the expanded state of the Tree Node by some means and then redraws it in the correct state after it is collapsed. (Review ID: 113781) ======================================================================
24-09-2004

EVALUATION The reason tree was not initially designed like this is largely due to weak references not existing in the JRE at the time. As you can imagine, tree needs to keep around extra information about the state of each node, such as if it is expanded, what children are expanded, children sizes... As such, tree uses the remove notification to determine when it needs to clean up. If tree did not do this, there would be a huge leak. As you removed things from your model, tree would keep a reference to them and they would never go away. But with the introduction of weak references, it is possible for tree to store everything as a WeakReference. Unfortunately it isn't that simple. Currently tree and its related class use a TreePath as a unique identifier. The problem with this is that they are typically short live objects and won't really work as a key for weak references. If tree were to assume the object from the model is unique then this could be done. Unfortunately this isn't the case. I am going to leave this open, as it may be possible to implement this in the future with a property that indicates the uniqueness of the objects from your model. I am moving also moving this over to an RFE, as this is not a bug. scott.violet@eng 2000-03-17
17-03-2000