I'm using a JTree that is populated from a database.
The tree is created by setting the root node and its childs with custom objects this way:
private DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Categorias");
...
ResultSet primaryCategories = dbm.fetchAllCategories();
while (primaryCategories.next()){
Category category = new Category(primaryCategories.getLong("_id"),
primaryCategories.getString("category"));
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(category);
rootNode.add(childNode);
ResultSet currentSubcategory = dbm.fetchChildSubcategories(category.getCatId());
while (currentSubcategory.next()){
Category subcategory = new Category(currentSubcategory.getLong("_id"),
currentSubcategory.getString("category"));
childNode.add(new DefaultMutableTreeNode(subcategory, false));
}
}
...
After this, the tree is perfectly created. Populated with "Category" Objects, every object has its own ID number and its name to use in toString() method.
The problem comes when it's set editable. Once the node is renamed, the Category node is also converted into a String Object, so I cant update the new Category name value to the database.
I've tried to capture the renaming event with treeNodesChanged(TreeModelEvent e) but, the userObject is already changed to a String Object, and can't get a referece of what object was edited.
What way can I solve this? Should I have a copy of the tree that's shown and another of the downloaded from the database and uptade both everytime a change occurs?
*PD: *
I also tried to capture the changed node from the model overriding the method:
public void nodeChanged(TreeNode newNode) {
DefaultMutableTreeNode parent = ((DefaultMutableTreeNode)newNode.getParent());
int index = getIndexOfChild(parent, newNode);
DefaultMutableTreeNode oldNode = (DefaultMutableTreeNode) getChild(parent, index);
System.out.println(parent.getUserObject().getClass().toString());
System.out.println(oldNode.getUserObject().getClass().toString());
}
this prints:
class com.giorgi.commandserver.entity.Category
class java.lang.String
So the old node here has already been changed to a String and I've lost completely the reference to the older Category and its ID so I cannot update it in the database.
Any help is wellcome.
Okay, that took a bit of digging.
Basically, when the editing is "stopped", the JTree will request the editor's value via editor's getCellEditorValue. This is then passed to the model via the valuesForPathChanged method, which finally calls the node's setUserObject method.
Presumably, you are using either the default editor or one based on text field. This will return a String value.
What you need to do is trap the change to the setUserObject method of your tree node, access the value coming (ie, check if it's a String or not) and update as required.
Final solution was as MadProgrammer said to get it in:
public void valueForPathChanged(TreePath path, Object newValue) {
DefaultMutableTreeNode aNode = (DefaultMutableTreeNode)path.getLastPathComponent();
Category catNode = (Category) aNode.getUserObject();
catNode.setCategory((String) newValue);
catNode.updateFromDatabase();
nodeChanged(aNode);
}
Related
I want to check whether a node with a certain ID is already in my graph or whether I have to create a new object. At the moment I am doing it with the following code:
// at this point I have the attributes for the node I need
String id = getIdOfNeededNode(); // The id is used to search for the node in the graph
// now I have to search for the node in the graph
Node node = new Node("dummy_id"); // This is the line I don't like;
// I would prefer not to have a dummy node
// but the compiler will then complain that the node might not be initialized
boolean alreadyCreated = false;
for(Node r : graph.getVertices()){ // search for the node with this id in the graph
if (r.getId().equals(portId)){
node = r;
alreadyCreated = true;
break;
}
}
if (!alreadyCreated) { // create a new object if the node was not found
node = new Resource(portId);
createdPortResources.add(port);
}
// In the remainder of the program, I am working with the node object which then is in the graph
The fact that I am creating a dummy node that is just a placeholder is really ugly. Please let me know how I can solve this problem in a more elegant way.
Well, you can just do this Node node = null;
But in general, just keep a map from the portIds to the nodes.
When you want to make that check, just consult that map.
It will be way easier and faster.
I want to copy a tree structure(source) to another one(target), but I got java.util.ConcurrentModificationException when I execute the method below:
private void mergeNode(TreeLayoutNode source, TreeLayoutNode target){
List<TreeLayoutNode> children = source.getChildren();
if(CollectionUtils.isEmpty(children)){
return;
}
Iterator<TreeLayoutNode> it = children.iterator();
while(it.hasNext()){
**TreeLayoutNode child = it.next();**
TreeLayoutNode copy = new TreeLayoutNode();
copy.setName(child.getName());
copy.setCapacity(child.getCapacity());
copy.setParent(child.getParent());
copy.setChildren(child.getChildren());
copy.setProportions(child.getProportions());
target.addChildNode(copy);
mergeNode(child, copy);
}
}
The code started with "**" is where exception occurs.Could any one give any hints?
Remove the call to 'copy.setChildren(child.getChildren())'. This results in references to children from the source tree being added to the destination tree, which is not what you want. The recursive method call will handle filling in the children.
Also you need to set the parent of the new node to the correct node in the new tree rather than calling 'copy.setParent(child.getParent())', which sets the parent reference to a node in the old tree. The call should probably be 'copy.setParent(target)'.
I am working on a small project that requires loading an XML file. I found a nice code sample that extends DefaultHandler and uses a custom TreeRender to format the XML into a treeview here. The code compiles and runs (always a plus) and gives me the starting point I'm looking for but there is one little thing I don't understand in the code.
Here's the code snippet that I don't get:
public class XmlTreeView extends DefaultHandler {
private DefaultMutableTreeNode _base;
<snip>
#Override
public void startElement(String uri, String localName, String tagName, Attributes attr) throws SAXException {
System.out.println("startElement: uri=" + uri + " localname=" + localName + " tagName=" + tagName );
DefaultMutableTreeNode current = new DefaultMutableTreeNode(tagName);
_base.add(current);
_base = current;
for (int i = 0; i < attr.getLength(); i++) {
// <snip> attribute processing
}
}
The class declares a DefaultMutableTreeNode named _base. The startElement() method instantiates a new DefaultMutableTreeNode named current then does
_base.add(current);
_base = current;
All my programming knowledge tells me that the second statement assigns the new object (current) to the _base "variable", making the first statement useless. However, if I take out the first statement the code no longer works properly. In fact, if I take out either statement it no longer works properly. Both statements are required for the element to get added to the tree.
Can you explain to me what's happening here? I just don't get it.
Thanks in advance,
Steve
_base is a reference to an object. When you say _base.add(current), you are calling a method that makes some change to that object. When you then say _base = current;, _base becomes a reference to a different object. But the first object is still there. And whatever change you made to it can still impact the rest of the program, if there's a reference to it somewhere else.
These variables are badly named which is causing most of the confusion.
The field _base should really be called something like currentNode and the new Node created in startElement should be something like childNode.
Here is the same code but rewritten using these new variable names:
currentNode.add(childNode);
currentNode= childNode;
So you see when we enter a start element in the XML file, we create a new node and add it to our Object representation of the tree structure of the XML. The new child element that we just started is added to the current node as a child. Then we change our reference of the current node to point to this new child node. This makes the new child node our current node.
I assume in code that you haven't shown, there is an endElement where we do the reverse operation and move up the tree to the currentNode's parent.
You have a strange piece of code here in my mind but this is what is happening:
private DefaultMutableTreeNode _base;
Is acting as a global currentNode for the class. When you call startElement you are doing this:
DefaultMutableTreeNode current = new DefaultMutableTreeNode(tagName);
//Create a new TreeNode item based off the tagName
So you now have:
_base (no children)
Now you are going to add to the children of _base, the freshly created node
_base.add(current);
Now that this is done you have:
_base (no children)
current (child)
Finally
_base = current;
Now you have
(parent of _base) (the old base)
_base / current
The reference to _base now points to the freshly created child. When you call endElement, you will exit out of the _base and return to your old _base
_base is storing the xmlElement that you are currently working on. When you call startElement all calls to setAttribute or startElement will be based off this.
SUMMARY:
Here is what this looks like played out in code:
XmlWriter xWriter;
xWriter.startElement("NPC"); //_base becomes new node "hello"
xWriter.startAttribute("Greeting", "Hi"); //attribute is now set to _base (or greeting)
xWriter.startElement("Data"); //_base becomes new node "data"
xWriter.startAttribute("Height", "200"); //attribute is now set to _base (or data)
xWriter.endElement(); //on end element you move to parent of data, so greeting
xWriter.endElement(); //again you move to the parent
Creating:
<NPC Greeting='Hi'>
<Data Height='200'/>
</NPC>
When a JTree node is edited, the node's user object is lost (set to String). How do I get the original user object? Because I would to like to get the user object's id so as to update the edited name in the database.
I am using TreeModelListener's treeNodesChanged method as shown below.
public void treeNodesChanged(TreeModelEvent e) {
DefaultMutableTreeNode node;
node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());
try {
int index = e.getChildIndices()[0];
node = (DefaultMutableTreeNode) (node.getChildAt(index));
} catch (NullPointerException ex) {}
System.out.println(node.getUserObject().getClass()); // my user object gone
}
So is there any way where I can get the original user object before edit? Any DefaultTreeCellRenderer or DefaultTreeCellEditor methods to override?
Implement TreeModel.valueForPathChanged(). Or override DefaultTreeModel.valueForPathChanged() if you're using DefaultTreeModel. This is where the new user object is set and it gives you the opportunity to get to the original user object.
I have the following problem (this is related to my post blink a tree node):
I have a custom cell renderer.
In some part of my code I create a new DefaultMutableTreeNode and store it in a list
public static List<DefaultMutableTreeNode> nodes = new ArrayList<DefaultMutableTreeNode>()
//in some time
DefaultMutableTreeNode aNode = new DefaultMutableTreeNode("SomeValue");
nodes.add(node);
In my cell renderer I do:
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
DefaultMutableTreeNode n = (DefaultMutableTreeNode)value;
if(nodes.contains(n)){
//set background to red
}
}
At this point nodes has a node but the code never goes in the if branch.
Why? I can not understand since I already stored it in the arraylist. Do I get a different reference?
Also I created a swing timer:
Timer t = new Timer(400, new ActionListener(){
public void actionPerformed(ActionEvent evt) {
if(nodes.size == 0)
return;
TreePath p = new TreePath(nodes.get(0));
Rectangle r = tree.getPathBounds(p);
tree.repaint(r);
}
});
But I get a NPE in tree.getPathBounds.
I can not understand why. Can't I manipulate DefaultMutableNodes I stored in my list this way? What am I doing wrong in my thinking?
Note: If I simply call repaint(); in the timer and in the cell renderer I loop over the nodes to see if it displays the sametext with the node I have stored, what I want I get the blinking, works
Thanks
Actually TreePath is a list of objects... path from tree root to the node. If you create a path from the single node the path exists in the tree only if the node is root of the tree.
I woul recommend to use TreeSelectionEvent public TreePath[] getPaths() method. The method provides actual paths.
I don't think DefaultMutableTreeNode defines an equals method, so it might not find the match in your List of nodes. Try storing and searching for the user object or extends DefaultMutableTreeNode and define equals.