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.
Related
I have something like this.
Grid<Stock> mygrid = new Grid<Stock>(store, cm)
{
#Override
protected void onAfterFirstAttach()
{
super.onAfterFirstAttach();
//here do the binding for the cell elements that have myclass
}
};
And for the view of mygrid I have this
view.setViewConfig(new GridViewConfig<Stock>()
{
#Override
public String getRowStyle(Stock model, int rowIndex)
{
return "";
}
#Override
public String getColStyle(Stock model, ValueProvider<? super Stock, ?> valueProvider, int rowIndex, int colIndex)
{
String style = "";
if(SOME_CASE)
{
style = "myclass";
}
return style;
}
});
What I want to do is onAfterFirstAttach I want to add some listener/handler that during hover will do something, let's say for the sake of simplicity, give an alert.
Is what I'm trying to do possible? Can I accomplish what I want without using JSNI or a library like gwtquery? Is it sure that onAfterFirstAttach all the cells that should have the class myclass will be available and accessible? If yes how can I attach the listener/handler that I want?
The questions seem a lot, but I believe they are all interconnected.
I'm using GWT 2.6 with GXT 3.1 on top of it.
Update
I forgot to mention that a feasible solution would be to add a listener for the mouse move on the whole grid, and then get each cell and check if it has the required class, but this seems such an overkill. There must be a simpler way.
A browser fires native events (click, focus, blur, etc.) regardless of whether you need them. These events "bubble up" the DOM tree giving you a chance to "listen" to them at the right level.
In your example you can create 30 listeners at the cell level, waiting for something to happen in "their" cell. This is a heavy (30 objects) and slow (it takes time to build and bind all of them) solution. Alternatively, you can create a single listener at the grid level, which listens to all events that bubble up from the cells and decides if something needs to be done or an event should be ignored. A single listener is lighter and can be created faster. On the flip side, it needs to examine events, although it's usually a fast and efficient operation (e.g. checking of an event source has a particular class name). Note also that this operation takes time when a browser typically does not have much to do anyway, so examining events only to ignore them does not impact user experience.
In my experience a single listener is almost always a better solution, although modern browsers and computers are so fast that the difference is rarely noticeable.
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.
i think this not a specific problem to me; everybody might have encountered this issue before.
To properly illustrate it, here's a simple UI:
As you can see, those two spinners are controlling a single variable -- "A". The only difference is that they control it using different views.
Since these two spinners' displaying values are synchronized, cyclic event shows up.
If i change the top spinner, "A" will be changed and the bottom spinner's value will also be updated accordingly. However, updating the bottom spinner's call (such as setValue) will also trigger another event instructing the top spinner to update based on the bottom spinner's value. Thus creates a bad cycle which can eventually cause a StackOverFlow exception.
My previously solution is kinda cumbersome: i placed a guarding boolean to indicate whether the 2nd updating call should be performed.
Now i'd like to ask "how can i handle such situation elegantly? ( in general, not specific to spinners )"
thx
Update:
Since i've got 2 answers suggesting me to utilize the observer structure, i have to say something about it.
Like what i've said, it's great but far from being perfect. Not only because of its inherent complexity, but also Its inability to solve the problem.
Why? To see the reason, you must realize the tight coupling of the View and Model-Controller in Java Swing. Lets take my spinner UI for an example. Suppose the variable A is actually an Observer object. Then, after firing the first state change event from the top spinner, the Observer "A" will update its value and fire a PropertyChange event to notify the bottom spinner. Then comes the 2nd updating which updates the bottom spinner's View. However, changing bottom spinner's view inevitably triggers a redundant event that will try to set "A"'s value again. Afterwards, the deadly loop is fully constructed and the stack overflow will be thrown.
In theory, the Observer model tries to solve the direct cycle by introducing 2 independent feedback paths. The chained updating odds(in event-response codes) implicitly form a bridge connecting both paths, making a cycle again.
Going back to Model-View-Controller, think about what your Model is, and what your View is.
In your current implementation, you have two models (one for each Spinner control), and they're being synced through the View layer.
What you should be doing though is share the same backing model. For the spinner with a subtracted value, create a proxy to the original model. ie:
class ProxySpinnerModel implements SpinnerModel {
getValue() { return originalSpinner.getValue() - 10 }
setValue(v) { originalSpinner.setValue(v+10) }
}
spinnerA = new JSpinner()
spinnerB = new JSpinner( new ProxySpinnerModel( spinnerA.getModel() ) )
Now, you don't need to add listeners, since they're both working off the same model and the default implementation (the originalModel) already has change listeners which it fires to the view.
Problem Solved
I've got many different suggestions. Particularly,
i want to thank Marc W & Reverend Gonzo. I'm here to make a summary for these ideas; this can save your time navigating thru big chunk of texts.
This problem can be easily bypassed if you carefully decouple the View and Model-Controller.
The dead cycle is caused by dependent writes: write_1 -> write_2 -> write_1 ->.... Intuitively, breaking the dependency can solve the problem elegantly.
If we look into the problem in depth, we can find updating the corresponding views doesn't necessarily involves an external write call. Actually, a view only depends on the data it's representing. Known this, we can then re-write the logic as follow: write_1 -> read_2 & write_2 -> read_1.
To illustrate this idea, lets compare the 3 methods mentioned by different posters:
alt text http://www.freeimagehosting.net/uploads/2707f1b483.png
As you can see, only the proxied view can solve all the dependency thus it's the generic solution for this knid of problem.
In practice, it can be implemented as something like this (in your event-response codes):
setValue(newValue);
anotherSyncUI.parse(); // not anotherSyncUI.setValue() any more
anotherSyncUI.repaint();
No more loops. Solved.
It's a bit complicated, but you could make A actually be an object that's observable. Both spinners (or whatever needs to update itself based on A's value) would then observe A. Whenever A changes, the spinners (or again, whatever object) update themselves to reflect the new value of A. This decouples the spinners' logic from one another. In your example here, the spinners should not be coupled to one another because they really have nothing to do with each other. Instead, they should both simply be bound to A and take care of their own view updating individually.
Whenever the value in the first spinner is changed, you would simply update A's value to match it. Whenever the value in the second spinner is changed, you would of course add 10 to its value before assigning it to A.
Update
In response to the update to your original question, my answer is that the spinners do not listen to one another's change events. Have a separate event handling method for each spinner. A user clicking the up or down arrows in the spinner generates a different event than calling setValue on the spinner programmatically, correct? If the spinners are completely independent of one another, there will be no infinite loop.
E.g. for the second spinner, calculate A-10 and then compare it to the current value of the spinner. If it's the same, do nothing, ending the infinite loop. Similarly for the first spinner.
I think there are also ways to update the spinner's model in a way that doesn't fire an event, but I don't know them off the top of my head.
Use a single SpinnerModel for both JSpinners. See the following code:
Note that the call to setValue() is only made once each time a new value is defined by one of the JSpinners.
import java.awt.BorderLayout;
import javax.swing.*;
public class Test {
public static void main(String[] args) {
JFrame jf = new JFrame();
SpinnerModel spinModel = new MySpinnerModel();
JSpinner jspin1 = new JSpinner(spinModel);
JSpinner jspin2 = new JSpinner(spinModel);
jf.setLayout(new BorderLayout());
jf.add(jspin1, BorderLayout.NORTH);
jf.add(jspin2, BorderLayout.SOUTH);
jf.pack();
jf.setVisible(true);
jf.setDefaultCloseOperation(3);
}
}
class MySpinnerModel extends AbstractSpinnerModel {
private int _value = 0;
private int _min = 0;
private int _max = 10;
#Override
public Object getNextValue() {
if (_value == _max) {
return null;
}
return _value + 1;
}
#Override
public Object getPreviousValue() {
if (_value == _min) {
return null;
}
return _value - 1;
}
#Override
public Object getValue() {
return _value;
}
#Override
public void setValue(Object value) {
System.out.println("setValue(" + value + ")");
if (value instanceof Integer) {
_value = (Integer) value;
fireStateChanged();
}
}
}
It seems you're really observing the wrong thing. From the example given I presume what you want to detect is the user's actions on the controls, not the changes in the values themselves. As you've outlined, changes in your model are reflected in the values of the spinners, and it is this which forms the infinite loop of events.
However, diving further into the UI implementation may not be the answer you want. In that case I'd say the best you can do is either your current guard solution, or to better extract the logic into your model (similar to what Marc and William have said). How that can be done will depend on the 'real world' model behind a particular implementation of the provided puzzle.
As a rule, your model should not be defined by your GUI. Ie, the SpinnerModel that backs each JSpinner should not be your value A. (That would be a horribly inelegant tightly coupled dependency on a particular view.)
Instead, your value A should either be a POJO or a property of an object. In which case, you can add PropertyChangeSupport to it. (And presumably have already done so in any case, as you want your spinners to update automatically if A is changed by other parts of your program).
I realise this is similar to Marc W's answer, and you were concerned that it's "complicated", but PropertyChangeSupport does almost all of it for you.
In fact, for trivially simple cases, you can just use a single class that wires a "setProperty" method through to a "firePropertyChange" call (as well as storing the value in a HashMap for any "getProperty" calls).
I don't really want to solve your problem but I find it interesting. I have already been confront to it and solved it each time a different way. But when I think about the 'why ?' and not about the 'how ?' am staying perplexed.
This problem only exists because I am using an automatism (MVC) which had to help me, and exactly in that way. The art how the components are used make this automatism a barrier to a beautiful code.
Why do set #setEvent() has to produce the same event as a GUI action?
Though, my opinion is also pretty close to Observer pattern but it is a bit lighter than that!!!
Have A as a variable with a setter
private Integer A;
setA(int A)
{
this.A = A;
refreshSpinners();
}
refreshSpinners()
{
setSpinnerA();
setSpinnerAMinus10();
}
setSpinnerA()
{
// show value of A
}
setSpinnerAMinus10()
{
// show value of A-10
}
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!?!
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.