-
Enhancement
-
Resolution: Unresolved
-
P4
-
None
-
6
-
sparc
-
solaris_10
FULL PRODUCT VERSION :
Java(TM) SE Runtime Environment (build 1.6.0-rc-b94)
A DESCRIPTION OF THE PROBLEM :
It is possible to keep track of the expansion state of a JTree in other components. For some forms of data, expansion is part of the data. In these situations using the data's tracking of expansion state is better than having the JTree track it. A problem arises when the JTrees data structure change, the UI appears not to recalculate its layout cache of expanded nodes. It assumes that the JTree's nodes will not be expanded and does nothing to recalculate its cache which means though the Node is expanded, visually it does not appear so. It has been mentioned that it would be better to not use treeStructureChanged as the notification mechanism but use one of the add/remove notification methods. This is not possible when there is not a way to hook into the data and monitor the individual changes, these are situations where all the user knows is that the data has changed in some significant way. Because of this the user must resort to using treeStructureChanged.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the following test, select a node and press the move right button. Watch as the tree collapses. Expand the branch that contains the moved node. See that it now shows the branch with its parent expanded. This should have been visible from the moment the structure changed.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
That the expansion state would visually stick.
ACTUAL -
Everything is collapsed, though the expanded data is in the expanded state.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.tree.*;
import java.util.*;
public class MovingNodeProblem implements Runnable, TreeModel{
EventListenerList listeners;
Node root;
public void run(){
listeners = new EventListenerList();
root = new Node("Root"){
//public boolean isExpanded(){ return true; }
};
for(int i = 0; i < 10; i ++){
Node n2 = new Node(String.valueOf(i));
n2.moveAsFirstChild(root);
for(int j = 0; j < 10; j++){
Node n3 = new Node(String.valueOf(j));
n3.moveAsFirstChild(n2);
}
}
final NodeJTree jtree = new NodeJTree(this);
JFrame jf = new JFrame();
jf.add(new JScrollPane(jtree));
JPanel movers = new JPanel();
JButton mr = new JButton("Move Right"){
@Override
public void fireActionPerformed(ActionEvent e){
TreePath spath = jtree.getSelectionPath();
if(spath == null) return;
Node n = (Node)spath.getLastPathComponent();
Node p = n.parent;
int index = p.children.indexOf(n);
if(index == 0) return;
int newparent = index -1;
Node np = p.children.get(newparent);
n.moveAsFirstChild(np);
np.expanded = true;
reload();
}
};
movers.add(mr);
jf.add(movers, BorderLayout.SOUTH);
jf.setSize(500,500);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
TreePath pathToRoot(Node n){
ArrayList al = new ArrayList();
Node current = n;
while(current != null){
al.add(0, current); current = current.parent;
}
return new TreePath(al.toArray());
}
void reload(){
TreePath rootpath = new TreePath(root);
TreeModelEvent e = new TreeModelEvent(this, rootpath);
for(TreeModelListener listener: listeners.getListeners(TreeModelListener.class)){
listener.treeStructureChanged(e);
}
}
public void addTreeModelListener(TreeModelListener listener){
listeners.add(TreeModelListener.class, listener);
}
public void removeTreeModelListener(TreeModelListener listener){
listeners.remove(TreeModelListener.class, listener);
}
public int getIndexOfChild(Object parent, Object child){
Node p = (Node)parent;
Node c = (Node)child;
return p.children.indexOf(c);
}
public boolean isLeaf(Object node){
Node n = (Node)node;
return n.children.size() == 0;
}
public Object getChild(Object node, int index){
Node n = (Node)node;
return n.children.get(index);
}
public int getChildCount(Object node){
Node n = (Node)node;
return n.children.size();
}
public Object getRoot(){
return root;
}
public void valueForPathChanged(TreePath path, Object newValue){
}
public static void main(String ... args){
SwingUtilities.invokeLater(new MovingNodeProblem());
}
class Node{
Node parent;
java.util.List<Node> children;
String value;
boolean expanded;
Node(String value){
children = new LinkedList<Node>();
this.value = value;
expanded = false;
}
void moveAfter(Node n){
Node parent = n.parent;
int index = parent.children.indexOf(n);
parent.children.add(index, this);
this.parent.children.remove(this);
this.parent = parent;
}
void moveAsFirstChild(Node p){
p.children.add(0, this);
if(this.parent != null)
this.parent.children.remove(this);
this.parent = p;
}
boolean isExpanded(){ return expanded; }
public Enumeration<TreePath> getExpandedDescendants(){
Vector expanded = new Vector();
getExpandedDescendants(expanded);
return expanded.elements();
}
public void getExpandedDescendants(Vector expanded){
Node target = this;
Vector path = new Vector();
while(target != null){
path.add(0, target);
target = target.parent;
}
TreePath tp = new TreePath(path.toArray());
expanded.add(tp);
for(Node child: children){
if(child.expanded)
child.getExpandedDescendants(expanded);
}
}
public String toString(){ return value; }
}
class NodeJTree extends JTree{
public NodeJTree(TreeModel model){
super(model);
}
@Override
public boolean isExpanded( TreePath tpath ){
Node expander = (Node)tpath.getLastPathComponent();
if( expander == root ) return true;
else return expander.isExpanded();
}
@Override
public boolean isExpanded( int row ){
TreePath tp = getPathForRow( row );
return isExpanded( tp );
}
@Override
public void setExpandedState( TreePath path, boolean state ){
Node expander = (Node)path.getLastPathComponent();
if( state ){
try{
fireTreeWillExpand( path );
}catch( ExpandVetoException eve ){ return; }
expander.expanded = true;
fireTreeExpanded( path );
}
else{
try{
fireTreeWillCollapse( path );
}catch( ExpandVetoException eve ){ return; }
expander.expanded = false;
fireTreeCollapsed( path );
}
}
@Override
public Enumeration<TreePath> getExpandedDescendants( TreePath parent ){
Node expander = (Node)parent.getLastPathComponent();
return expander.getExpandedDescendants();
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Oddly selecting the node under the root will cause the expansion to reappear.
Java(TM) SE Runtime Environment (build 1.6.0-rc-b94)
A DESCRIPTION OF THE PROBLEM :
It is possible to keep track of the expansion state of a JTree in other components. For some forms of data, expansion is part of the data. In these situations using the data's tracking of expansion state is better than having the JTree track it. A problem arises when the JTrees data structure change, the UI appears not to recalculate its layout cache of expanded nodes. It assumes that the JTree's nodes will not be expanded and does nothing to recalculate its cache which means though the Node is expanded, visually it does not appear so. It has been mentioned that it would be better to not use treeStructureChanged as the notification mechanism but use one of the add/remove notification methods. This is not possible when there is not a way to hook into the data and monitor the individual changes, these are situations where all the user knows is that the data has changed in some significant way. Because of this the user must resort to using treeStructureChanged.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the following test, select a node and press the move right button. Watch as the tree collapses. Expand the branch that contains the moved node. See that it now shows the branch with its parent expanded. This should have been visible from the moment the structure changed.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
That the expansion state would visually stick.
ACTUAL -
Everything is collapsed, though the expanded data is in the expanded state.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.tree.*;
import java.util.*;
public class MovingNodeProblem implements Runnable, TreeModel{
EventListenerList listeners;
Node root;
public void run(){
listeners = new EventListenerList();
root = new Node("Root"){
//public boolean isExpanded(){ return true; }
};
for(int i = 0; i < 10; i ++){
Node n2 = new Node(String.valueOf(i));
n2.moveAsFirstChild(root);
for(int j = 0; j < 10; j++){
Node n3 = new Node(String.valueOf(j));
n3.moveAsFirstChild(n2);
}
}
final NodeJTree jtree = new NodeJTree(this);
JFrame jf = new JFrame();
jf.add(new JScrollPane(jtree));
JPanel movers = new JPanel();
JButton mr = new JButton("Move Right"){
@Override
public void fireActionPerformed(ActionEvent e){
TreePath spath = jtree.getSelectionPath();
if(spath == null) return;
Node n = (Node)spath.getLastPathComponent();
Node p = n.parent;
int index = p.children.indexOf(n);
if(index == 0) return;
int newparent = index -1;
Node np = p.children.get(newparent);
n.moveAsFirstChild(np);
np.expanded = true;
reload();
}
};
movers.add(mr);
jf.add(movers, BorderLayout.SOUTH);
jf.setSize(500,500);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
TreePath pathToRoot(Node n){
ArrayList al = new ArrayList();
Node current = n;
while(current != null){
al.add(0, current); current = current.parent;
}
return new TreePath(al.toArray());
}
void reload(){
TreePath rootpath = new TreePath(root);
TreeModelEvent e = new TreeModelEvent(this, rootpath);
for(TreeModelListener listener: listeners.getListeners(TreeModelListener.class)){
listener.treeStructureChanged(e);
}
}
public void addTreeModelListener(TreeModelListener listener){
listeners.add(TreeModelListener.class, listener);
}
public void removeTreeModelListener(TreeModelListener listener){
listeners.remove(TreeModelListener.class, listener);
}
public int getIndexOfChild(Object parent, Object child){
Node p = (Node)parent;
Node c = (Node)child;
return p.children.indexOf(c);
}
public boolean isLeaf(Object node){
Node n = (Node)node;
return n.children.size() == 0;
}
public Object getChild(Object node, int index){
Node n = (Node)node;
return n.children.get(index);
}
public int getChildCount(Object node){
Node n = (Node)node;
return n.children.size();
}
public Object getRoot(){
return root;
}
public void valueForPathChanged(TreePath path, Object newValue){
}
public static void main(String ... args){
SwingUtilities.invokeLater(new MovingNodeProblem());
}
class Node{
Node parent;
java.util.List<Node> children;
String value;
boolean expanded;
Node(String value){
children = new LinkedList<Node>();
this.value = value;
expanded = false;
}
void moveAfter(Node n){
Node parent = n.parent;
int index = parent.children.indexOf(n);
parent.children.add(index, this);
this.parent.children.remove(this);
this.parent = parent;
}
void moveAsFirstChild(Node p){
p.children.add(0, this);
if(this.parent != null)
this.parent.children.remove(this);
this.parent = p;
}
boolean isExpanded(){ return expanded; }
public Enumeration<TreePath> getExpandedDescendants(){
Vector expanded = new Vector();
getExpandedDescendants(expanded);
return expanded.elements();
}
public void getExpandedDescendants(Vector expanded){
Node target = this;
Vector path = new Vector();
while(target != null){
path.add(0, target);
target = target.parent;
}
TreePath tp = new TreePath(path.toArray());
expanded.add(tp);
for(Node child: children){
if(child.expanded)
child.getExpandedDescendants(expanded);
}
}
public String toString(){ return value; }
}
class NodeJTree extends JTree{
public NodeJTree(TreeModel model){
super(model);
}
@Override
public boolean isExpanded( TreePath tpath ){
Node expander = (Node)tpath.getLastPathComponent();
if( expander == root ) return true;
else return expander.isExpanded();
}
@Override
public boolean isExpanded( int row ){
TreePath tp = getPathForRow( row );
return isExpanded( tp );
}
@Override
public void setExpandedState( TreePath path, boolean state ){
Node expander = (Node)path.getLastPathComponent();
if( state ){
try{
fireTreeWillExpand( path );
}catch( ExpandVetoException eve ){ return; }
expander.expanded = true;
fireTreeExpanded( path );
}
else{
try{
fireTreeWillCollapse( path );
}catch( ExpandVetoException eve ){ return; }
expander.expanded = false;
fireTreeCollapsed( path );
}
}
@Override
public Enumeration<TreePath> getExpandedDescendants( TreePath parent ){
Node expander = (Node)parent.getLastPathComponent();
return expander.getExpandedDescendants();
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Oddly selecting the node under the root will cause the expansion to reappear.