Java Swing JTree Expansion - java

This method works as expected - it creates a JTree with a root node and two child container nodes (each with a respective leaf node):
private JComponent createSideBar()
{
final DefaultMutableTreeNode top = new DefaultMutableTreeNode("Projects");
final JTree tree = new JTree(top);
DefaultMutableTreeNode project = new DefaultMutableTreeNode("project 1");
DefaultMutableTreeNode version = new DefaultMutableTreeNode("version 1");
project.add(version);
top.add(project);
TreePath treePath = new TreePath(project.getPath());
// tree.expandPath(treePath);
project = new DefaultMutableTreeNode("project 2");
version = new DefaultMutableTreeNode("version 2");
project.add(version);
top.add(project);
return tree;
}
In this case, the tree starts out closed. I'd like the application to start with all nodes fully expanded so I started by adding the following:
tree.expandPath(treePath);
but when I un-comment it from the code above, the second set of child nodes don't show up, ie: Project 2 and Version 2 do not show up. In fact, all subsequently added nodes never show up.
For what its worth, I'm using JDK 1.5. From the docs, I can't seem to see any restrictions or why this method would have such ill-effects ... I'm going to try to look at the source but was hoping someone might have a good idea what and why this is expected behavior. I'm wondering if each subsequent node 'add' is somehow disallowed somehow - but I can't imagine would work for most run-time use cases.
Thanks,
-Luther

Unfortunately, Swing is often "helpful". In this case, it is creating a model for you from the data supplied, much the same as a JList would create a model if you supplied a Vector.
JTree and accomplices (primarily the Pluggable Look & Feel) will add listeners to the model to keep informed of updates. If you just change the data behind the (implicit) model's back, nothing will get updated other than by chance.
So, what you should do is explicitly create a model. When the model data changes (always on the EDT, of course), cause the relevant event to be fired.

If nodes are added to a node which has already been expanded, you need to reload the model.
((DefaultTreeModel)tree.getModel()).reload();
or
((DefaultTreeModel)tree.getModel()).reload(top);
This second version is more useful if you want to reload only a small part of a large tree.

Ah ... the model.
Your answers are elaborated on a bit here and here .... and even here.
I ended up doing something like:
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
model.insertNodeInto(newNode, parent, index);
which keeps the model directly informed. In my case, that scales just fine.
Now, how to mark one of these as the answer!?!

Related

Drag and Drop in JTree - can importData modify the tree?

I am experiencing some difficulty with a drag and drop in the JTree component. My drag and drop is to allow user rearrange nodes in the tree. My implementation works mostly fine, but in some situations there is a null pointer exception when moving a node in between of its children, like on the picture below I am dragging C under B (between B and A):
The direct cause of the exception seems to be that while performing cleanup of DnD framework, path to the item being dragged was invalidated by the changes I have performed on the tree to implement the required move.
More specifically, I get a callstack like this:
javax.swing.plaf.basic.BasicTreeUI#getDropLineRect
java.beans.PropertyChangeSupport#firePropertyChange
...
javax.swing.TransferHandler.DropHandler#cleanup
...
javax.swing.TransferHandler.SwingDropTarget#drop
The getDropLineRect is clearly attempting to evaluate a rectangle for a path in the tree which no longer exists, as the node was already deleted when the move was performed in my importData function.
My question is: is this expected? Is it considered unsafe to perform data changes while DnD has not completed yet, and should they be queued and performed later? I did not see any such requirement in any documentation, and I did not see such async implementation in any of the examples or tutorials.
IMHO it would not only be saver - as you stated yourself - to do any data modifications AFTER the drop occured, but also easier to implement.
If you already remove the node on the drag event, you need to have a rollback at hand to handle the situation of removing the node from you window or making an illegal drop.
On the other hand, if you use the drag event to just gain the needed information for a successful drop and don't change anything in your data yet, the previously stated problems dissapear. On drop, you would use the remembered information from the drag and complete the whole operation, or discard the drop, which in return rollbacks the visual actions (though I'm not 100% sure here), thus remaining a consistent state all the time.
Edit: "My question is: is this expected?": I think the Exception occurs from your logic of changing the tree model in between the drag and drop, making it inconsitent at a certain time for the event dispatching thread trying to redraw it from the model. Apologies for not being able to state "this is how it's done", maybe someone else finds a quote from the swing tree dnd doc.
I still can not give a general statement, but I think it's always a good idea to look at the standard implementations, in this case, at the TreeModel. You did not state what kind of TreeModel you use. If you a custom one, you have to fire the "tree changed event" yourself, to tell the tree something changed. I found this excerpt from an example for dnd, using the DefaultTreeModel. Its ready to run and I tried to build the error you described but it seemed to work fine. The line "model.insertNodeInto" fires the appropriate event in the DefaultTreeModel. http://www.coderanch.com/t/346509/GUI/java/JTree-drag-drop-tree-Java
Notice that the event is fired during the importData and before the clean up calling, where your exception occurs.
I remember that these events only differ in the value of the arguements passed to the same constructor. I also had problems getting to fire the right event for the tree to understand me. At some point I ended up copying the calling lines from the DefaultTreeModel source and used it in my code so it just worked. The docu on this is not very specific.
public boolean importData(TransferHandler.TransferSupport support) {
if(!canImport(support)) {
return false;
}
// Extract transfer data.
DefaultMutableTreeNode[] nodes = null;
try {
Transferable t = support.getTransferable();
nodes = (DefaultMutableTreeNode[])t.getTransferData(nodesFlavor);
} catch(UnsupportedFlavorException ufe) {
System.out.println("UnsupportedFlavor: " + ufe.getMessage());
} catch(java.io.IOException ioe) {
System.out.println("I/O error: " + ioe.getMessage());
}
// Get drop location info.
JTree.DropLocation dl =
(JTree.DropLocation)support.getDropLocation();
int childIndex = dl.getChildIndex();
TreePath dest = dl.getPath();
DefaultMutableTreeNode parent =
(DefaultMutableTreeNode)dest.getLastPathComponent();
JTree tree = (JTree)support.getComponent();
DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if(childIndex == -1) { // DropMode.ON
index = parent.getChildCount();
}
// Add data to model.
for(int i = 0; i < nodes.length; i++) {
model.insertNodeInto(nodes[i], parent, index++);
}
return true;
}
Default tree model source: http://developer.classpath.org/doc/javax/swing/tree/DefaultTreeModel-source.html
Edit: I think what also worked for me was firing a general "whole tree structure changed" event, which causes the tree to be built up from scratch from the model state. I think it was the Event constructor with all the specifics having a null value. Of course this might take some more time that just firing the specific event for the actual changes.

Display updated tree table value from another tree table

I am trying to make a scene editor to go with my rendering engine. I am using swing to make the GUI and also swingx for its JXTreeTable component. Everything is working fine, except that the Scene tree table is not updating the names of the nodes automatically as I would like. For example, in the next image, I change the name of one of the nodes, and nothing seems to happen. However if I then move my mouse over the node in the Scene box (the one at the top) the name gets updated.
I have two JXTreeTable, and two models which extend AbstractTreeTableModel.
Here is the relevant code for the Properties model.
public class PropertiesModel extends AbstractTreeTableModel{
private EE_Property root;
private SceneModel mSceneModel;
private EE_SceneObject sceneSelection;
...
#Override
public void setValueAt(Object value, Object node, int column){
((EE_Property)node).setValue((String)value);
// Updates the values of the current scene selection
sceneSelection.setProperties(root);
TreePath path = new TreePath(sceneSelection.getParent());
int index = mSceneModel.getIndexOfChild(sceneSelection.getParent(), sceneSelection);
// This is where I thought the updating of the scene model would happen and thus redraw it correctly
mSceneModel.getTreeModelSupport().fireChildChanged(path, index, sceneSelection);
}
}
I thought that using fireChildChanged() would update the scene tree table as I wanted.
If I call fireChildChanged() with index=0, I can get the Root node to update when I rename it, but any other index I have to wait till I move the mouse over it to update.
Edit: problem solved
I tried the redraw method suggested by #Shakedown which partially worked but sometimes would leave "..." after the text if the new text was longer than the original.
I did however realize that the problem was coming from the TreePath not being generated properly. When using TreePath path = new TreePath(sceneSelection.getParent());, the path's parent was null, thus not allowing the tree to update. I now use this code which works :
// mTT is the scene tree table
TreePath nodePath = mSceneModel.mTT.getTreeSelectionModel().getSelectionPath();
int index = mSceneModel.getIndexOfChild(sceneSelection.getParent(), sceneSelection);
mSceneModel.getTreeModelSupport().fireChildChanged(nodePath.getParentPath(), index, sceneSelection);
You're notifying the listeners of the SceneModel which is not the tree-table that you want to update. Look for some similar fireXXX methods on the AbstractTreeTableModel class and call those, which will notify the JXTreeTable and it will redraw itself.
It looks like the one you want is fireTreeNodesChanged(...), so play around with that and figure out what parameters you need to pass in.

expand Jtree at last modified area?

I am using dom4j to create a DocumentTreeModel from a dom4j document.
I display this DocumentTreeModel inside JScrollPane.
I have a button that adds a new node to the dom4j document, and recreates the DocumentTreeModel
I am using getPathForRow but this seems pretty limited. I need to be able to work with multiple tree depth. Basically looking for something like tree.getPathOfLastModifiedChildrensParent()
onAddNewNodeButtonClickEventFired {
dom4jdocument.addElement( "1" );
tree.setModel(new DocumentTreeModel(dom4jdocument));
tree.expandPath(tree.getPathForRow(1));
}
Basically I am trying to get the Jtree to redraw the document everytime document is edited.
Seeing you setting a new model whenever you edit the document looks like you still don't have the notification running, right? If so, you don't need any special method on the JTree - what you need is a well-behaved implementation of TreeModel ;-)
Just for fun, I looked up the DocumentTreeModel: that's an extremely small cover on top of DefaultTreeModel with no support whatever to glue changes in the Document to changes in the DocumentTreeModel. The fact that the Leaf-/BranchTreeNode implement TreeNode only (as opposed to going a step further and implement MutableTreeNode) even disables the models helper methods to insert/remove node. Short story: all the hard work is left to you.
Basically, you have to make the treeModel aware of any change in the underlying Document. Something like (pseudo-code):
DocNode newElement = document.addElement(...)
DocNode parentElement = newElement.getParent();
// walk the tree until you find the TreeNode which represents the DocNode
BranchTreeNode root = treeModel.getRoot();
BranchTreeNode parentNode = null;
forEach (root.child)
if child.getXMLNode().equals(parentElement)
parentNode = child;
// now find the childNode which corresponds to the new element
forEach (parentNode.child)
if (parentNode.child.getXMLNode().equals(newElement)
childNode = child;
// now notify the treeModel that an insertion has happened
treeModel.nodesWhereInserted(parentNode, childNode ...)
Hmm ... in your shoes I would look for a more comfortable implementation, can't believe that there isn another implementation around somewhere?
CU
Jeanette
Try - tree.revalidate();
It should refresh the component tree.

Creating Dynamic JTrees (Controlling Root Node Visibility)

I have a question about how to dynamically generate JTrees. Is there a way to set the Root Node invisible without making its children invisible too? I have tried to do the following but it shows all nodes as invisible. Keep in mind that I want to add and remove children of the Root Node at any point in time. I've added comments so you can follow what I intend to do. Let me know if they are doing something I dont need, as I am new to JTrees and don't know the conventions. I would also like to be able to select multiple children for the listener.
DefaultMutableTreeNode rootNode;
rootNode = new DefaultMutableTreeNode(); //I want this invisible.
DefaultTreeModel treeModel = new DefaultTreeModel(rootNode);
JTree tree = new JTree(treeModel);
treeModel.addTreeModelListener(this);
tree.setRootVisible(false); // Sets everything invisible
tree.setEditable(true); //makes tree dynamic
tree.setShowsRootHandles(true); //supposedly allows you to see the children of the nodes.
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
//I would like the line above to be multi-select; however, this doesn't seem to be an option.
DefaultMutableTreeNode table = new DefaultMutableTreeNode( "table1");
rootNode.add(book);
DefaultMutableTreeNode value = new DefaultMutableTreeNode( "value");
table.add(value);
In the above example. Nothing is shown and when I remove the "tree.setRootVisible(false)" everything is visible including the node.
A very late answer, but I have just had the same problem.
Ensure to expand your root node, so that its children become visible :
yourTree.expandPath(new TreePath(root.getPath()))
I'd say the difference between the code in the question and in the TreeDemo is that the tree demo creates and adds all its nodes before creating the actual tree. If the nodes are to be added dynamically (after the tree is created) it should be done through the TreeModel. Otherwise no events saying the tree has changed will be generated. At least that is what the tutorial seems to say about editing the node's "content", might be the same issue:
Note that although DefaultMutableTreeNode has methods for changing a
node's content, changes should go through the DefaultTreeModel cover
methods. Otherwise, the tree model events would not be generated, and
listeners such as the tree would not know about the updates.
Someone's solution
Works fine for me. I based my tests on the TreeDemo from the Swing tutorial on How to Use Trees. Compare your code with the tutorial code to see what the difference is.
A very late answer also, I'm a java beginner and had the same problem so it may help.
setRootVisible(false) also hides all nodes for me so I added setShowsRootHandles(true) to show all nodes :
tree.setRootVisible(false);
tree.setShowsRootHandles(true)
I hope it helps !
https://docs.oracle.com/javase/tutorial/uiswing/components/tree.html#display

Prefuse Toolkit: dynamically adding nodes and edges

Does anyone have experience with the prefuse graph toolkit? Is it possible to change an already displayed graph, ie. add/remove nodes and/or edges, and have the display correctly adapt?
For instance, prefuse comes with an example that visualizes a network of friends:
http://prefuse.org/doc/manual/introduction/example/Example.java
What I would like to do is something along the lines of this:
// -- 7. add new nodes on the fly -------------------------------------
new Timer(2000, new ActionListener() {
private Node oldNode = graph.nodes().next(); // init with random node
public void actionPerformed(ActionEvent e) {
// insert new node //
Node newNode = graph.addNode();
// insert new edge //
graph.addEdge(oldNode, newNode);
// remember node for next call //
oldNode = newNode;
}
}).start();
But it doesn't seem to work. Any hints?
You should be aware the several layers of prefuse:
Data
Visualization
Display
To be short, the three layers can be linked this way:
Graph graph = new Graph(eg. yourXML_file);
Visualization viz = new Visualization();
viz.add(GRAPH, graph);
Display disp = new Display();
disp.setVisualization(viz);
Display is a graphic component that you add to a panel as usual.
Here you only modify the data layer.
Node newNode = graph.addNode();
graph.addEdge(oldNode, newNode);
You need now to update the visual layer:
viz.run("repaint");
The repaint action has to be defined.
ActionList repaint = new ActionList();
repaint.add(new RepaintAction());
viz.putAction("repaint", repaint);
I really advise you to read the prefuse doc.
And you can find a lot a resources on the official forum
At least, I can say you that prefuse is for the moment not really efficient for live graph update.
But it should not be enough, as you modified the graph structure, you have to regenerate it in the visualization (ie. recalculate the node placements etc...). There are two actions already defined in your sample code. Run them at the end of your actionPerformed.
viz.run("color");
viz.run("layout");
This method is not very efficient, because it adds a lot of computation each time you add a node, but there are not any others for the moment with prefuse.
As pointed out in my other post, the reason new nodes and edges are not visible in the original example is that the colors etc. for the nodes are not set correctly. One way to fix this is to explicitly call vis.run("color"); whenever a node or edge was added.
Alternatively, we can ensure that the color action is always running, by initializing the ActionList to which we add it (called "color" in the original example) slightly differently:
instead of
ActionList color = new ActionList();
we could write
ActionList color = new ActionList(Activity.INFINITY);
This keeps the action list running indefinitely, so that new nodes/edges will automatically be initialized for their visual appearance.
However, it is unclear to me whether this would actually be the preferred method - for things like a dynamic layout action (e.g. ForceDirectedLayout), such a declaration makes perfect sense, but for colors it seems to me that a constantly running coloring action is mostly overhead.
So, perhaps the previously posted solution of just running the "color" action explicitly (but only once) whenever the graph gets extended, might be the better choice...
Okay, after digging a bit through the prefuse sources, I now have a better understanding of how things work under the hood. I found out that actually the new nodes I create with the code above are not only added correctly to the graph, the visualization also takes note of it!
So, unlike Jerome suggests, it is not necessary to call vis.run("layout") explicitly.
The reason I thought the nodes were not added correctly was the fact that they are drawn with white background-, border- and text color - on white background that is. Not astonishing that they are a bit difficult to spot.
To fix that one has to call the color action after a new node is inserted, like this:
// insert new edge //
graph.addEdge(oldNode, newNode);
vis.run("color"); // <- this is new
(Note that this action is defined further up in the code of Example.jar under //-- 4.)
One last thing I am unsure about now is whether calling this action will make prefuse go over all graph nodes again and set their color - for very large graphs that would be undesired, of course.
You need to tell the control container ('d', in example.java) do get redrawn. Calling invalidate() should be enough (not sure, though).
Anyway, this might help you.

Categories

Resources