1

Amongst other places, I've looked here and here, but I've not found an answer to my question.

A little background:

  • I'm implementing a view that represents a subset of the resources in an eclipse project.
  • The project nodes in the tree will check whether the project is open to see if they should contain children. Effectively, the Project node's hasChildren() method returns IProject.isOpen().
  • The eclipse view subscribes to resourceChangeEvents, and in the case that any change occurs, it calls refresh() on the TreeViewer.

Based on information from this question:

If you have added or removed objects in the tree use

TreeViewer.refresh();

refresh() (with no arguments) should then go through and update the entire tree with fresh information from the model.


By setting breakpoints in the code, I've discovered that during the refresh process, the framework calls hasChildren() several times on a node, but never calls getChildren().

Let us say that Project is currently open, and the node representing it in the tree is expanded with some children. I close the project and resource change event is fired. This causes a refresh(). When asked, the project now returns false from hasChildren(), and yet the children are still present in the tree.

Conversely, if the project is closed and the node has no children (there is no expansion arrow), opening the project will fire a resource change event, which refresh()es the tree, the project node returns true from hasChildren() and yet getChildren() is never called to find out what these children are. The node still has no children and no expansion arrow.

Currently the model fires events when new nodes are added and old ones removed, and the tree listens for these and calls add(Object, Object) and remove(Object) on the TreeViewer as appropriate, but the model only checks to see whether its children need to be changed when it is asked for them via the content provider's getChildren() method.

The problem can be partially alleviated by calling getChildren() from hasChildren(), but this seems messy, and from my understanding of how refresh() works, shouldn't be necessary.


Why does the TreeViewer's refresh() method not re-query a node to see what its children are? Given that is doesn't do this, how is it meant to handle adding and removing objects?

M_M
  • 1,955
  • 2
  • 17
  • 23
  • It only calls getChildren if the node is expanded. It should show the expand/collapse indicator just from the hasChildren value. – greg-449 Jul 26 '17 at 14:46
  • @greg: That's what I'd expect to happen. However, despite the `refresh`, the expand/collapse indicator doesn't reflect the value returned from `hasChildren`. – M_M Jul 26 '17 at 15:11
  • Maybe you should show us your content provider's implementation, and indicate exactly what is being set as the viewer's input. – nitind Jul 26 '17 at 18:39

1 Answers1

1

It turns out the problem was that the Implementation of the IElementComparer passed to TreeViewer.setComparer looked like this:

@Override
public boolean equals( final Object one, final Object two ) {
    if (one.getClass().equals( two.getClass() ) ) { <-- This is not always true...
        if ( one instanceof MyCustomNodeBaseClass) {
            return ((MyCustomNodeBaseClass) one).isEquivalentTo( (MyCustomNodeBaseClass) two);
        }
    }
    return false; <-- ...therefore this is the problem, as it wasn't expected to be reached
}

The IElementComparer.equals(Object, Object) method can be called with arguments of class org.eclipse.ui.internal.ViewSite, and should handle those too.

It was a questionable move to quietly return false in cases where the class of the parameters was unrecognised, and the problematic line could be replaced with:

return ( one.equals( two ) ); <-- this will quietly handle unexpected classes better.

I eventually found this by following the debugger into the updatePlus() method of the AbstractTreeViewer

M_M
  • 1,955
  • 2
  • 17
  • 23