VMDNodeWidget & VMDPinWidget

It's more likely that the XML document you want to create tools for is slightly more complex than the one we've looked at so far. So let's imagine it looks like this:
<?xml version="1.0" encoding="UTF-8"?>
    <task name="grow flowers" x="213" y="129">
    <task name="pick flowers" x="214" y="27">
        <requirement>flower pot</requirement>
    <task name="admire flowers" x="24" y="154">
    <task name="plant flowers" x="22" y="27">

As you can see, we now have three levels of elements ("activity/task/requirement"), rather than two ("activity/task"). In our Visual Library representation of this file, we'd like to present the requirements together with the tasks, which can be done quite nicely with the Visual Library VMDNodeWidget and VMDPinWidget:

For the moment, we won't think about drawing lines between the widgets (i.e., "ConnectionWidget") yet, we'll do that next time so that we can create a visual workflow on top of our XML file. Right now, we'll focus on changing the existing sample (part 1, part 2, and part 3) so as to work with VMDNodeWidget and VMDPinWidget.

  1. Rewrite the Task object. We now have a list of requirements that are part of the XML file, so we need to remodel that file, as follows (note the parts that are highlighted in particular):
    public class Task {
        int index = 0;
        String name;
        int x;
        int y;
        List<String> requirements = new ArrayList<String>();
        private PropertyChangeSupport propertyChangeSupport;
        public Task() {
            propertyChangeSupport = new PropertyChangeSupport(this);
        public int getIndex() {
            return index;
        public PropertyChangeSupport getPropertyChangeSupport() {
            return propertyChangeSupport;
        public void setPropertyChangeSupport(PropertyChangeSupport propertyChangeSupport) {
            this.propertyChangeSupport = propertyChangeSupport;
        public int getX() {
            return x;
        public void setX(int newValue) {
            int oldValue = this.x;
            this.x = newValue;
            propertyChangeSupport.firePropertyChange("x", oldValue, newValue);
        public int getY() {
            return y;
        public void setY(int newValue) {
            int oldValue = this.y;
            this.y = newValue;
            propertyChangeSupport.firePropertyChange("y", oldValue, newValue);
        public String getName() {
            return name;
        public void setName(String newValue) {
            String oldValue = this.name;
            this.name = newValue;
            propertyChangeSupport.firePropertyChange("name", oldValue, newValue);
        public List<String> getRequirements() {
            return requirements;
        public void setRequirements(List<String> newValue) {
            List<String> oldValue = this.requirements;
            this.requirements = newValue;
            propertyChangeSupport.firePropertyChange("requirements", oldValue, newValue);
        public void addPropertyChangeListener(PropertyChangeListener listener) {
        public void removePropertyChangeListener(PropertyChangeListener listener) {


  2. Update the Deserialization of the XML File. Deserialization is done in the ChildFactory, where you need to add the bits highlighted below in the ChildFactory.createKeys method:
    protected boolean createKeys(List<Task> list) {
        try {
            InputSource source = new InputSource(stream);
            org.w3c.dom.Document doc = XMLUtil.parse(source, true, true, null, null);
            org.w3c.dom.NodeList taskElements = doc.getElementsByTagName("task");
            for (int i = 0; i < taskElements.getLength(); i++) {
                org.w3c.dom.Node taskNode = taskElements.item(i);
                NodeList childNodes = taskNode.getChildNodes();
                Task task = new Task();
                NamedNodeMap map = taskNode.getAttributes();
                for (int j = 0; j < map.getLength(); j++) {
                    org.w3c.dom.Node attrNode = map.item(j);
                    String attrName = attrNode.getNodeName();
                    if (attrName.equals("name")) {
                    if (attrName.equals("x")) {
                    if (attrName.equals("y")) {
                List tempList = new ArrayList(childNodes.getLength());
                int length = childNodes.getLength();
                for (int k = 0; k < length; k++) {
                    org.w3c.dom.Node childNode = childNodes.item(k);
                    if (childNode.getNodeName().equals("requirement")) {
        } catch (IOException ex) {
        } catch (SAXException ex) {
        return true;


  3. Create a GraphPinScene. While creating the GraphPinScene, make sure you can work with the underlying "Task" domain object, i.e., by using generics to get hold of the Task:
    public class TaskGraphPinScene extends GraphPinScene<Task, Task, String> {
        private LayerWidget mainLayer;
        private final AbcEditorSupport support;
        TaskGraphPinScene(Node[] childNodes, AbcEditorSupport support) {
            this.support = support;
            mainLayer = new LayerWidget(this);
            for (Node node : childNodes) {
                Task task = node.getLookup().lookup(Task.class);
                List<String> reqs = task.getRequirements();
                for (String req : reqs) {
                    this.addPin(task, req);
        protected Widget attachNodeWidget(Task task) {
            VMDNodeWidget widget = new VMDNodeWidget(this);
            widget.getActions().addAction(ActionFactory.createMoveAction(new TaskMoveProvider(task), new TaskMoveProvider(task)));
            widget.setPreferredLocation(new Point(task.getX(), task.getY()));
            widget.getActions().addAction(ActionFactory.createInplaceEditorAction(new LabelTextFieldEditor(task)));
            return widget;
        protected Widget attachPinWidget(Task task, String pin) {
            VMDPinWidget widget = new VMDPinWidget(this);
            ((VMDNodeWidget) findWidget(task)).addChild(widget);
            return widget;
        protected Widget attachEdgeWidget(Task e) {
            return null;
        protected void attachEdgeSourceAnchor(Task e, String p, String p1) {
        protected void attachEdgeTargetAnchor(Task e, String p, String p1) {
        private void serialize() {
            SceneSerializer.serialize(this, FileUtil.toFile(support.getDataObject().getPrimaryFile()));
        private class TaskMoveProvider implements MoveProvider, MoveStrategy {
            private Point original;
            private final Task task;
            private TaskMoveProvider(Task task) {
                this.task = task;
            public void movementStarted(Widget widget) {
            public void movementFinished(Widget widget) {
                original = null;
            public Point getOriginalLocation(Widget widget) {
                original = widget.getPreferredLocation();
                return original;
            public void setNewLocation(Widget widget, Point location) {
                //Set the X and Y of the Task object:
            public Point locationSuggested(Widget widget, Point originalPoint, Point suggested) {
                return suggested;
        private class LabelTextFieldEditor implements TextFieldInplaceEditor {
            private final Task task;
            private LabelTextFieldEditor(Task task) {
                this.task = task;
            public boolean isEnabled(Widget widget) {
                return true;
            public String getText(Widget widget) {
                return ((VMDNodeWidget) widget).getNodeName();
            public void setText(Widget widget, String text) {
                ((VMDNodeWidget) widget).setNodeName(text);
                //Set the Name of the Task object:


  4. Rewrite the SceneSerializer. Now you want to be able to serialize the requirements, as well as everything else you'd been serializing before. Also, we're no longer using an ObjecScene. Instead of that we're using our own extension of the GraphPinScene class:
    public class SceneSerializer {
        private static final String SCENE_ELEMENT = "activity"; // NOI18N
        private static final String TASK_ELEMENT = "task"; // NOI18N
        private static final String REQUIREMENT_ELEMENT = "requirement"; // NOI18N
        private static final String NODE_NAME_ATTR = "name"; // NOI18N
        private static final String NODE_X_ATTR = "x"; // NOI18N
        private static final String NODE_Y_ATTR = "y"; // NOI18N
        public static void serialize(TaskGraphPinScene scene, File file) {
            Document document = XMLUtil.createDocument(SCENE_ELEMENT, null, null, null);
            Node sceneElement = document.getFirstChild();
            for (Object o : scene.getObjects()) {
                Element taskElement = document.createElement(TASK_ELEMENT);
                Object storedObject = scene.findStoredObject(o);
                if (storedObject instanceof Task) {
                    Task task = (Task) storedObject;
                    setAttribute(document, taskElement, NODE_NAME_ATTR, task.getName());
                    setAttribute(document, taskElement, NODE_X_ATTR, Integer.toString(task.getX()));
                    setAttribute(document, taskElement, NODE_Y_ATTR, Integer.toString(task.getY()));
                    List reqs = task.getRequirements();
                    for (String req : reqs) {
                        Element requirementElement = document.createElement(REQUIREMENT_ELEMENT);
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                XMLUtil.write(document, fos, "UTF-8"); // NOI18N
            } catch (Exception e) {
            } finally {
                try {
                    if (fos != null) {
                } catch (Exception e) {
        private static void setAttribute(Document xml, Node node, String name, String value) {
            NamedNodeMap map = node.getAttributes();
            Attr attribute = xml.createAttribute(name);


  5. Rewrite the JScrollPane. Finally, most of the content of the JScrollPane can be removed. All that's needed right now is the following. Note the highlighted line which is where the TaskGraphPinScene is initialized:
    public class VisLibScrollPane extends JScrollPane {
        ExplorerManager em;
        private final AbcEditorSupport support;
        public VisLibScrollPane(AbcEditorSupport support) {
            this.support = support;
        public void addNotify() {
            ExplorerManager m = ExplorerManager.find(this);
            if (m == em) {
                // do nothing
            } else {
                em = m;
                Node root = em.getRootContext();
                Node[] childNodes = root.getChildren().getNodes();
                TaskGraphPinScene scene = new TaskGraphPinScene(childNodes, support);

That's all. Next time... how to create a workflow out of our widgets and serialize the underlying XML file with changes to the workflow.


Thanks Geertjan!
Your examples are always useful.
Not enough has been written about the motivation and features of the vmdnodewidget and vmdpinwidget.
More explanation of the advantages (when to use these rather than other widget implementations) would be useful.

Posted by Stu Rodgers on May 11, 2010 at 03:09 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.


« May 2016