How to refresh model source/target connection “children” of GEF NodeEditParts

I am currently developing a GEF-based editor for an EMF-based model with the help of the GEF Book, and vainolo’s incredibly helpful blog posts.

I had implemented connection creation, following vainolo’s post about connections, but in my editor, the added connection figures never showed up, although the respective model objects had been instantiated (I’ve checked!). It was only after I had saved the graph, and re-opened the editor that the connection figures became visible. The respective code bits looked like this:

DISCLAIMER: For reasons of space and readability, the source code is incomplete. Getter and setter methods, field declarations, imports, and methods non-substantial to the problem area are omitted. If you’re looking for more general information on how to add connections to your GEF editor, please go to vainolo.com, he has everything you need to know!

a) The NodeEditPart

/**
 * @author New Code on the Block
 *
 */
public class MyNodeEditPart extends AbstractGraphicalEditPart implements NodeEditPart {

	@Override protected void createEditPolicies() {
		...
		installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new MyNodeGraphicalNodeEditPolicy());
	}

	@Override protected List getModelSourceConnections() {
		List list = new ArrayList();
		MyNode model = (MyNode) getModel();
		for (Relation e : model.getMyGraph().getOutRelations(model.getSId())) {
			if (e instanceof MyRelation) list.add((MyRelation) e);
		}
		return list;
	}

	@Override protected List getModelTargetConnections() {
		List list = new ArrayList();
		MyNode model = (MyNode) getModel();
		for (Relation e : model.getMyGraph().getInRelations(model.getSId())) {
			if (e instanceof MyRelation) list.add((MyRelation) e);
		}
		return list;
	}

	/**
	 * Inner class MyNodeAdapter
	 */
	public class MyNodeAdapter implements Adapter {

		@Override public void notifyChanged(Notification notification) {
			refreshVisuals();
			refreshSourceConnections();
			refreshTargetConnections();
		}

}

b) The GraphicalNodeEditPolicy

/**
 * @author New Code on the Block
 *
 */
public class MyNodeGraphicalNodeEditPolicy extends GraphicalNodeEditPolicy {

	@Override
	protected Command getConnectionCompleteCommand(CreateConnectionRequest request) {
		CreateMyRelationCommand result = (CreateMyRelationCommand) request.getStartCommand();
		result.setTarget((MyNode) getHost().getModel());
		return result;
	}

	@Override
	protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
		CreateMyRelationCommand result = new CreateMyRelationCommand();
		result.setSource((MyNode) getHost().getModel());
		result.setRelation((MyRelation) request.getNewObject());
		result.setGraph((MyGraph) ((MyNode) getHost().getModel()).getGraph());
		request.setStartCommand(result);
		return result;
	}
}

c) The ConnectionCreationCommand

/**
 * @author New Code on the Block
 *
 */
public class CreateMyRelationCommand extends Command {

	@Override
	public boolean canExecute() {
		return source != null && target != null && relation != null;
	}

	@Override
	public void execute() {
		relation.setSource(source);
		relation.setTarget(target);
		relation.setGraph(graph);
	}

}

After googling and tearing my hair for a bit, I’ve realised where the problem seemed to be: While in the models from both the GEF Book and the vainolo editor, connections are structural features of nodes, just like in GEF where target and source connections are structural features of a NodeEditPart. My model differs from that approach, as connections are stored in the graph, and nodes “know” nothing of possibly existing connections, for which they may be source or target.

So basically, the connection figures never appeared in the editor after they’d been added, because the respective source and target node adapter never got notified of the addition, as it wasn’t any of their business: nothing about the nodes had changed.

How does this work? In the MyNodeEditPart‘s adapter (implements org.eclipse.emf.common.notify.Adapter) you find the refreshSourceConnections() and refreshTargetConnections() methods, which themselves call their respective getter methods getModelSourceConnections() and getModelTargetConnections(), when the EditPart‘s adapter is notified of any change to its model. These getter methods in turn are responsible for checking whether EditParts exist for all model objects, and organizing the creation for those which don’t (much like the getModelChildren() method on a given MyGraphEditPart will work, by returning a list of model children / connections).

Ergo: Because no change is registered in the node’s EditPart (or rather its Adapter) – due to the connection not being stored in the node but in the graph – it doesn’t bother refreshing itself. And therefore doesn’t trigger any refresh of the connection model “children” list. However, the way GEF works is that connections are a structural feature of the NodeEditPart.

The solution to this problem of course is to make the NodeEditPart refresh itself. To achieve this, simply pass the respective source and target nodes’ EditParts to the command in which the connection is created and linked to the nodes. This can be done in the GraphicalNodeEditPolicy responsible for creating the connection creation command, which itself is installed in the nodes’ EditParts.

All that’s needed is the addition of 4 lines of code plus a few getters and setters (highlighted below).

a) The GraphicalNodeEditPolicy

/**
 * @author New Code on the Block
 *
 */
public class MyNodeGraphicalNodeEditPolicy extends GraphicalNodeEditPolicy {

	@Override
	protected Command getConnectionCompleteCommand(CreateConnectionRequest request) {
		CreateMyRelationCommand result = (CreateMyRelationCommand) request.getStartCommand();
		result.setTarget((MyNode) getHost().getModel());
		result.setTargetEditPart((MyNodeEditPart) getHost());
		return result;
	}

	@Override
	protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
		CreateMyRelationCommand result = new CreateMyRelationCommand();
		result.setSource((MyNode) getHost().getModel());
		result.setSourceEditPart((MyNodeEditPart) getHost());
		result.setRelation((MyRelation) request.getNewObject());
		result.setGraph((MyGraph) ((MyNode) getHost().getModel()).getGraph());
		request.setStartCommand(result);
		return result;
	}
}

b) The ConnectionCreationCommand

/**
 * @author New Code on the Block
 *
 */
public class CreateMyRelationCommand extends Command {

	@Override
	public boolean canExecute() {
		return source != null && target != null && relation != null;
	}

	@Override
	public void execute() {
		relation.setSource(source);
		relation.setTarget(target);
		relation.setGraph(graph);
		sourceeditpart.refresh();
		targeteditpart.refresh();
	}
}

It’s easy to see what’s happening here: After the source and the target for the new connection have been set, and the new connection itself has been added to the graph, the respective EditParts for the source and target nodes are refreshed programatically, which triggers the creation of an EditPart for the new connection (via the getModelSourceConnections() and getModelTargetConnections() methods), which in turn creates a figure for the connection and, voilà, the connection figure appears on the screen.

I hope this can be of help to someone, it easily cost me a few days!

TwitterFacebookGoogle+EmailLinkedInPinterestDiggRedditStumbleUpon
If you found this post helpful, you may want to consider donating to help cover server costs.
Tagged with: , , , , , , , , , , , , , , , ,
Posted in Eclipse RCP, GEF: Eclipse Graphical Editing Framework
2 comments on “How to refresh model source/target connection “children” of GEF NodeEditParts
  1. vainolo says:

    While your solution surely works, it breaks the MVC pattern, creating a dependency from the commands to the controller, which is not encouraged.
    The solution I would implement is making MyGraph and Adapter and calling refresh() when the graph changes. Since the links are stored in MyGraph, it surely changes when a new link is added. I have also found that you should call refresh() instead of refreshSourceConnections() and others. It refreshes everything, so you never forget anything.

    • Hi vainolo,
      Funny that your comment came in just today, and thanks a lot. I’m in the process of re-implementing the editor (the original one was a hacky proof-of-concept affair), and have just stumbled over the same issue again, thinking that the above solution is not clean at all.
      I had made MyGraph an Adapter already, but that wouldn’t solve the issue, until I’ve found that I’ve never added the model objects (it wasn’t links I was working on, but annotations in today’s case) to the children List in getModelChildren()… D’uh! Well better find a bug late than never. All part of the GEF learning experience.
      Now I can just call getParent().refresh() in the Adapter’s notifyChanged() method and everything works just fine. I’ll update the post with the better implementation.
      Thanks also for your hint about not using refreshSourceConnections(), I’ll keep that in mind.
      Thanks!

Leave a Comment

%d bloggers like this: