X

Geertjan's Blog

  • April 5, 2011

JDesktopPane on the NetBeans Platform (Part 1)

Geertjan Wielenga
Product Manager
Aside from Jesse and Tim's attendance, the recent NetBeans Platform Certified Training in Boston was special in that the last 2 days (of 5 days) was spent with the students talking about their various Java applications and how those applications might be ported to the NetBeans Platform. One of the presentations was done by a group of developers from a big financial institution who had a JDesktopPane containing a number of JFrames. Each JFrame provides a different tool in the application suite. And, when a JFrame is chosen, the JDesktopPane's menubar (and other items) changes to provide exactly the content relevant for the current tool.

How would such a structure be ported to the NetBeans Platform? Obviously, each JFrame should become a TopComponent. However, what about the JDesktopPane behavior? Switching from one TopComponent to another should add/remove items to/from the menubar and toolbar. Fortunately, Jesse was in the room and he knocked together a solution on the spot.

Below, in the first screenshot, Win1 is selected:

Now, in the second screenshot, Win2 is selected and the menubar and toolbar both change:

You can also see, above, that each window can be moved outside the application frame.

So, how to create an application on the NetBeans Platform where the selected TopComponent determines the content of the menubar and toolbar? Jesse has provided an API module and a module that uses the API.

The API module has two classes:

import org.openide.filesystems.FileSystem;
import org.openide.util.Utilities;
/\*\*
\* Layer fragment active when in {@link Utilities#actionsGlobalContext}.
\*/
public interface LayerProvider {
FileSystem layer() throws Exception;
}

And here's the class implementing the above:

import java.net.URL;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.XMLFileSystem;
import org.xml.sax.SAXException;
/\*\*
\* Layer provider based on an XML fragment.
\*/
public final class FixedLayerProvider implements LayerProvider {
private final FileSystem fs;
public FixedLayerProvider(URL layerXML) throws SAXException {
fs = new XMLFileSystem(layerXML);
}
public @Override FileSystem layer() {
return fs;
}
}

And, in the second module, there are two windows that start as follows:

public final class Win1TopComponent extends TopComponent {
public Win1TopComponent() throws SAXException {
initComponents();
setName("Win1");
associateLookup(Lookups.singleton(new FixedLayerProvider(Win1TopComponent.class.getResource("win1.xml"))));
}
public final class Win2TopComponent extends TopComponent {
public Win2TopComponent() throws SAXException {
initComponents();
setName("Win2");
associateLookup(Lookups.singleton(new FixedLayerProvider(Win1TopComponent.class.getResource("win2.xml"))));
}

In the module are also two XML files, in the same package as the two windows above, defining the XML fragments. So, in Jesse's words: "You supply the URL to a layer fragment. A couple of menus and actions for them are defined in the fragment XML." And: "The API is just to provide some stuff for the SFS whenever that object is in the selection (most easily by placing in a TC lookup or even implementing on a TC class)."

There are some problems with the solution, especially when you add top-level menus, rather than items within menus, since with top-level menus the menu does appear, but you have to click on it a few times before it posts anything. There could be a bug in the NetBeans Platform sources that would need to be fixed for this.

But, aside from that, this is at least a starting point for someone wanting this behavior. I made sure to put the windows into a "view" mode (in this case "properties") to ensure they'd be modal when undocked. If using this solution, you'd maybe create some new "view" modes that are undocked by default. Then, you'd provide a list in the main frame's menu bar, containing all the tools that the application provides. When a tool is selected, the related TopComponent would open in one of the "view" modes, i.e., undocked, in a specific place, and when the TopComponent is active the menubar and toolbar would change to match the content relevant for the window.

Join the discussion

Comments ( 4 )
  • Jesse Glick Tuesday, April 5, 2011

    Unmentioned in the blog entry is the impl class in the API module which scans LayerProvider's in the selection and loads them. In its current version it is pretty short too:

    @ServiceProvider(service=FileSystem.class)

    public final class DelegatingFS extends MultiFileSystem implements LookupListener {

    private final Lookup.Result<LayerProvider> r = Utilities.actionsGlobalContext().lookupResult(LayerProvider.class);

    public DelegatingFS() {

    setPropagateMasks(true);

    r.addLookupListener(this);

    resultChanged(null);

    }

    public @Override void resultChanged(LookupEvent le) {

    List<FileSystem> fss = new ArrayList<FileSystem>();

    for (LayerProvider p : r.allInstances()) {

    try {

    fss.add(p.layer());

    } catch (Exception x) {...}

    }

    setDelegates(fss.toArray(new FileSystem[fss.size()]));

    }

    }

    In the future something like this could be part of the Platform, perhaps.


  • Grant Harris Wednesday, April 6, 2011

    Geertjan -

    Thanks again for the great training/workshop last week! Might you post the project source for this example? Looks very useful.


  • Jesse Glick Thursday, July 21, 2011

    By the way, I have developed this system further. Making it work well requires a patch in DataShadow which I put into 7.1. You can get a working demo (requires a 7.1 dev build) here: https://bitbucket.org/jglick/dynamicmenudemo


  • Thomas Monday, October 15, 2012

    This took me hours, so I wanted to share the information.

    First: Thanks Jesse for your example.

    But the first thing I noticed was, that the UnitTest SystemSubPathLayerProviderTest::testProvider fails with a StackOverflow when using Netbeans Platform 7.2.

    What took me hours is the fact, that - using your example - you cannot hide menu-items by adding the suffix "_hidden" to shadows.

    So "shadow_hidden" won't work! You have to use this spelling: "shadow.hidden"

    e.g.:

    <folder name="Help">

    <file name="org-netbeans-modules-autoupdate-ui-actions-CheckForUpdatesAction.shadow.hidden"/>

    </folder>


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha