The next step in the life of my small visual bean editor is to think about persistence. Right now, I don't have any file type associated with my scene. It's simply a scene on which you can drag and drop items from the palette. However, when I restart the application, I see this:

In other words, the widgets are persisted across restarts. But how is this possible if there's no file type associated with the scene? Well, I have set the new @ConvertAsJavaBean annotation on the bean that defines the widget. (In other words, that bean is my domain object.) That enables the generation of a ".settings" file for each widget dropped into the scene. And, because I have a property change listener on the bean, whenever a new property change is fired on my bean, the ".settings" file for the changed widget is automatically updated!
Here's the ".settings" files (in build/testuserdir/config/EditorComponents) for the scene that you see above:

And, even if my scene were to be associated with a particular file type (which I may still add), I would STILL use the above approach so that the user would be able to see the last state of the widgets.
Here's the sources of this project, feel free to join and also please feel free to contribute your own code too:
http://kenai.com/projects/org-simple-editor
So, how to end up with the above scenario? Here is the general approach:
  1. Mark your domain object (i.e., the bean describing the widget) with @ConvertAsJavaBean. Make sure to also add property change listener support to the bean, with each setter firing a property change.
  2. Whenever a widget is dropped, you need to ensure that a ".settings" file is created. So, in the scene's AcceptProvider, within the "accept" method, I have this try/catch block that does the trick:try { FileObject editorComponentsFolder = FileUtil.getConfigFile("EditorComponents"); if (editorComponentsFolder == null) { FileUtil.getConfigRoot().createFolder("EditorComponents"); } InstanceDataObject.create( DataFolder.findFolder( FileUtil.getConfigFile("EditorComponents")), component.getType() + "-" + System.currentTimeMillis(), component, null, true);} catch (IOException ex) { Exceptions.printStackTrace(ex);}
    What's "component" above? That's what I get from the transferable after the drag/drop from the palette is complete—an instance of the bean defining my widget.
    Note that I create a unique name for my ".settings" file, by adding a timestamp to the type of the widget. That, in turn, is determined by the palette item, which is built from the layer file.
  3. So, at this stage, the ".settings" file exists. How are these files loaded into the scene at restart? The TopComponent that holds my scene implements a LookupListener. This is what happens when the TopComponent opens:@Overridepublic void componentOpened() { res = Lookups.forPath("EditorComponents").lookupResult(EditorComponent.class); res.addLookupListener(this); resultChanged(new LookupEvent(res));}
    So, we look in the System FileSystem, into the "EditorComponents" folder, while indicating an interest in the "EditorComponent" class (which is the bean that defines the widget). And this is what the "resultChanged" method looks like:@Overridepublic void resultChanged(LookupEvent le) { scene = new EditorComponentGraphScene(res.allInstances()); myView = scene.createView(); widgetScrollPane.setViewportView(myView);}
    And then, in the scene, we process the "res.allInstances()" like this:for (EditorComponent component : coll) { Widget w = attachNodeWidget(new EditorComponentWrapper(component)); Point point = new Point(); point.setLocation(component.getX(), component.getY()); w.setPreferredLocation(widget.convertLocalToScene( point)); w.setPreferredSize(new Dimension(component.getWidth(), component.getHeight()));}
    Now we have a widget in the scene for each item we found in the "EditorComponents" folder.
  4. Next, whenever the user changes something (e.g., resizes or moves a widget), we need to let the NetBeans Platform recreate the ".settings" file. Here, for example, is the relevant part of the "movementFinished" method in my "MoveStrategyProvider" (which implements both "MoveStrategy" and "MoveProvider"):component.setX(widget.getPreferredLocation().x); component.setY(widget.getPreferredLocation().y);
    Hence, when the move is complete, we update the bean with the current X and current Y of the widget, which fires a property change in the bean, which in turn results in the ".settings" file being updated.
    The trickiest part was figuring out how to update the JComponent (which represents the JavaBean being edited) within the bean that defines the widget. For this, I created my own BeanNode, so that I could listen to the changes of the JComponent that is wrapped within the BeanNode:public class MyBeanNode extends BeanNode implements PropertyChangeListener { EditorComponent editorComponent; public MyBeanNode(EditorComponent editorComponent) throws Exception { super(editorComponent.getComponent(), Children.LEAF); this.editorComponent = editorComponent; editorComponent.getComponent().addPropertyChangeLi stener(this); } @Override public void propertyChange(final PropertyChangeEvent e) { editorComponent.fireChange(e.getPropertyName(), e.getOldValue(), e.getNewValue()); }}
    The firing of the change on the bean that defines the widget is what is necessary for the ".settings" file to be updated with the current data of the JComponent wrapped by the BeanNode.
And that's all. It's relatively simple and now I can restore my scene across restarts, without needing to think about associating a file type with my scene.


More...