NetBeans Platform Swing Porting Tutorial
This tutorial needs a review. You can edit it in GitHub following these contribution guidelines. |
This tutorial demonstrates how to port the Swing components and business logic of a simple Java application to the NetBeans Platform. Though the scenario below is simple, the basic concepts of "porting" an application to the NetBeans Platform will become clear. In the end, some general principles will be identified, based on the steps taken in the tutorial. Hopefully, they will be useful to you when porting your own Swing applications to the NetBeans Platform.
For troubleshooting purposes, you are welcome to download the completed tutorial source code.
Introduction to Porting
Before beginning this procedure, it makes sense to ask why one would want to port an application to the NetBeans Platform in the first place. A typical Swing application consists of a domain-specific layer on top of a general framework. The general framework normally provides features dealing with an application’s infrastructure, such as an application’s menu bar, windowing system (also known as "docking framework"), and lifecycle management. Typically this framework is very generic and is (or could be) reused by many applications within the same organization.
The NetBeans Platform exists specifically to cater to these infrastructural concerns. You do not need to create these on your own for your own Swing applications. You can simply move the useful domain-specific parts of your application to the NetBeans Platform and then, from that point onwards, the NetBeans Platform will be the new underlying 'plumbing' layer of your application. You can then focus on the more interesting parts of your application, specifically, the domain-specific parts. This will speed up your development process and give you a consistent basis for all your applications.
In this tutorial, we will begin with the Anagram Game, which is a standard Swing application sample that is distributed with NetBeans IDE. We will, step by step, move it to the NetBeans Platform and gradually see the advantages of doing so.
Getting the Anagram Game
We begin by getting the Anagram Game, which is one of the IDE’s standard Java samples, from the New Project wizard. Then we run it and analyze its parts.
-
Choose File > New Project (Ctrl-Shift-N). Under Categories, select Samples > Java. Under Projects, select Anagram Game. Click Next and Finish. You should now see the Anagram Game application in the Projects window, as shown here:
The application contains the following classes:
-
*
WordLibrary.java
*. Provides an abstract class, with abstract methods likegetWord(int idx)
,getScrambledWord(int idx)
, andisCorrect(int idx, String userGuess)
. -
*
StaticWordLibrary.java
*. ExtendsWordLibrary.java
, providing a list of scrambled words, as well as their unscrambled equivalents, together with the getters and setters for accessing them and for evaluating them. -
*
Anagrams.java
*. Provides the main user interface of the application, principally consisting of aJFrame
with aJPanel
containing labels and text fields. Also included is a menu bar containing a File menu, with the menu items 'About' and 'Exit'. -
*
About.java
*. Provides the About box, accessed from the File menu.
1. Run the application and you should see the following:
-
When you enter the correctly unscrambled word, you will see this:
Before porting this application to the NetBeans Platform, we need to think about the stages in which we want to port our application. In other words, you do not need to port everything at once. And there are different levels to which you can integrate your application, from a mostly superfical level to a level that aligns your application completely with the paradigms and purposes of the NetBeans Platform. The next section will show the levels of compliance your application can have with the NetBeans Platform.
Levels of Compliance
Converting an application to be fit for a framework such as the NetBeans Platform can be done on various levels. The integration can be shallow and use just a few integration points or it can be deeper, tightly following the paradigms of the NetBeans Platform.
The stages can be described as follows:
Level 0: Launchable
One or more of the following can be done to make your application launchable with as few changes as possible:
-
Enhance your manifest with NetBeans key/value pairs so that your JAR is recognized as an OSGi bundle or as a NetBeans module.
-
Set dependencies between modules. In the manifest, with instances of plain Class-Path you can set dependencies between modules.
-
Register a menu item in the declarative layer file (
layer.xml
) of your module, to invoke your original application. This file can be automatically created and populated when the module is compiled, via annotations on Action classes, as you will learn to do later in this tutorial.
In this tutorial, we will do all of the above. We will enhance the manifest, which the module project wizard will do for us. We will create a menu item that will invoke our application. To do so, we will move our application’s classes into a module source structure. Then we will create a new Java ActionListener
for opening the JFrame
of the application. We will annotate the ActionListener
to register it in the application’s registry as a new menu item. From that action, we will invoke our application.
Level 1: Integrated
Here are some pointers for integrating the application more tightly with the NetBeans Platform:
-
Integrate visually to get the benefits of the NetBeans Window System, which is its docking framework.
-
Use NetBeans Window System API and the Dialog APIs, primarily the
TopComponent
class and theDialogDisplayer
class. -
Change initialization code of your application, use the
ModuleInstall
class or declarative registrations, through the layer file or the META-INF/services folder.
In this tutorial, we will move the relevant parts of the JPanel
from the JFrame
to a new TopComponent
. The TopComponent
class creates a window on the NetBeans Platform, which in our case will show our JPanel
.
Level 2: Use Case Support
This level of compliance with the NetBeans Platform is concerned with one or more of the following activities:
-
Bind your application to other modules by inspecting existing functionality and trying to use it.
-
Simplify the workflow to fit into the NetBeans Platform paradigms.
-
Listen to the global selection to discover what other modules are doing and update your state accordingly.
In this tutorial, we will listen for the existence of EditorCookies
. A cookie is a capability. With a Java interface, your object’s capabilities are fixed at compile time, while NetBeans Platform cookies allow your object to behave dynamically because your object can expose capabilities, or not, based on its state. An EditorCookie
defines an editor, with interfaces for common activities such as opening a document, closing the editor, background loading of files, document saving, and modification notifications.
We will listen for the existence of such a cookie and then we will pass the content of the editor to the TopComponent
, in the form of words. By doing this, we are doing what the first item above outlines, i.e., inspecting existing functionality and reusing it within the context of our ported application. This is a modest level of integration. However, it pays off because it shows how you can reuse functionality provided by the NetBeans Platform or by any other application created on top of the NetBeans Platform, such as NetBeans IDE.
Level 3: Aligned
In this final stage of your porting activity, you are concerned with the following thoughts, first and foremost:
-
Become a good citizen of the NetBeans Platform, by exposing your own state to other modules so that they know what you are doing.
-
Eliminate duplicated functionality, by reusing the Navigator, Favorites window, Task List, Progress API, etc., instead of creating or maintaining your own.
-
Cooperate with other modules and adapt your application to the NetBeans Platform way of doing things.
Towards the end of this tutorial, we will adopt this level of compliance by letting our TopComponent
expose a Savable
when changes are made to the "Guessed Word" text field. By doing this, we will enable the NetBeans Platform Save actions, which can be invoked from the File menu, toolbar, and keyboard shortcuts. This kind of integration brings the full benefits of the NetBeans Platform, however it also requires some effort to attain.
Creating the NetBeans Platform Application
First, let’s create the basis of our application. We use a wizard to do so. This is the typical first practical step of creating a new application on top of the NetBeans Platform application.
-
Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Modules. Under Projects, select NetBeans Platform Application, as shown below:
Click Next.
-
Name the application
AnagramApplication
, as shown below:
Click Finish. You now have a NetBeans Platform application. You can right-click it and then run it and you will see an empty main window, with a menu bar and a tool bar:
Look under some of the menus, click a few toolbar buttons, and explore the basis of your new application. For example, open the Properties window and the Output window, from the Window menu, and you have the starting point of a complex application:
Next, we create a first custom module. We will name it AnagramCore
because, in the end, it will contain the essential parts of the application. Using subsequent tutorials on the NetBeans Platform Learning Trail, we will be able to add more features to the application, none of which will be manadatory parts, since the user will be able to plug them into the application. The core module, however, that is, AnagramCore
, will be a required module in every distribution of the application.
-
Right-click the application’s "Modules" node and choose "Add New…", as shown below:
-
Type
AnagramGameCore
in Project Name and accept the default project location, which is the root folder of the application, as shown below:
Click Next.
-
Type a unique name in the Code Name Base field, which provides the unique identifier for your module. It could be anything, but here it is
com.toy.anagrams.core
because it is convenient to reproduce the package structure of the original application, which is "com.toy.anagrams.*". Do not click "Generate OSGi Bundle", because in this tutorial you will use the default NetBeans module system.
Click Finish. Below the original Anagram Game sample, you should now see the source structure of your new module, as shown here:
Above, we can see that we now have the original application, together with the module to which it will be ported. In the next sections, we will begin porting the application to the module, using the porting levels described earlier.
Porting Level 0: Launchable
At this stage, we simply want to be able to launch our application. To do that we will create a menu item that invokes the application. We begin by copying the application’s sources into the module source structure.
-
Copy the two packages from the Anagram Game into the module. Below, the new packages and classes in the module are highlighted:
-
In the
com.toy.anagrams.core
package, create a new Java class namedOpenAnagramGameAction
, implementing the standard JDKActionListener
class as follows:
import com.toy.anagrams.ui.Anagrams;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class OpenAnagramGameAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
new Anagrams().setVisible(true);
}
}
As you can see in the code above, when the user invokes the OpenAnagramGameAction
, the JFrame
from the Anagram Game will open.
-
Next, we need to register the new
OpenAnagramGameAction
in the NetBeans central registry, which is also known as the "System FileSystem". We will do this via annotations that will generate entries in the central registry. To use these annotations, the AnagramGameCore module needs to have a library dependency on the module that provides the annotations. Right-click on the module’s "Libraries" node and choose "Add Module Dependency", as shown below:
Start typing "ActionRegistration" and you will see that the filter narrows to show the library dependency that provides the ActionRegistration
class:
Click OK. Next, add another dependency, this time on the Utilities API, which provides the @Messages
annotation that you will use below.
-
Now you can annotate your
Action
class as follows:
package com.toy.anagrams.core;
import com.toy.anagrams.ui.Anagrams;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.NbBundle.Messages;
@ActionID(id="com.toy.anagrams.core.OpenAnagramGameAction",category="Window")
@ActionRegistration(displayName = "#CTL_OpenAnagramGameAction")
@ActionReferences({
@ActionReference(path = "Menu/Window", position = 10)
})
@Messages("CTL_OpenAnagramGameAction=Open Anagram Game")
public class OpenAnagramGameAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
new Anagrams().setVisible(true);
}
}
-
In the Projects window, right-click the AnagramApplication project node and choose Run. The application starts up, installing all the modules provided by the application, which includes our custom module.
-
Under the Window menu, you should find the menu item "Open Anagram Game", as shown below:
Click "Open Anagram Game" and your application appears, as before.
The application is displayed, but note that it is not well integrated with the NetBeans Platform. For example, it is not modal and it is impossible to close the JFrame
, unless you close the application. The latter is because the application now manages the lifecycle of the JFrame
. In the next section, we will integrate the Anagram Game more tightly with the NetBeans Platform.
Porting Level 1: Integration
In this section, we integrate the application more tightly by creating a new window, so that we have a user interface, that is, a window, to which we can move those contents of the JFrame
that are useful to our new application.
-
Right-click the
com.toy.anagrams.core
package in the Projects window and then choose New > Other. Under Categories, select Module Development. Under File Types, select Window:
Click Next.
-
Choose the position where you would like the window to appear. For purposes of this tutorial choose "editor", which will place the Anagram Game in the main part of the application. Also check the first checkbox, to specify that the window should open automatically when the application starts up:
Click Next.
-
Type
Anagram
in Class Name Prefix and selectcom.toy.anagrams.core
in Package, as shown here:
Above, notice that the IDE shows the files it will create and modify.
-
Click Finish. Now you have a new Java class named "AnagramGameTopComponent.java". Double-click it and the Matisse GUI Builder opens. You can use the GUI Builder to design your windows:
-
Open the
Anagrams
class in thecom.toy.anagrams.ui
package. Click within the Anagrams in the GUI Builder until you see an orange line around theJPanel
, as shown below:
-
When you see the orange line around the
JPanel
, as shown above, right-click it and choose "Copy". Then paste theJPanel
into theAnagramTopComponent
and you should see the old user interface in your newAnagramTopComponent
class:
-
You have now ported the user interface of the Anagram Game. A few variables need still to be moved from the
Anagrams
class to the newAnagramTopComponent
class. Declare these two, which are in theAnagrams
class, at the top of your newAnagramTopComponent
class.
private int wordIdx = 0;
private WordLibrary wordLibrary;
Next, look in the constructor of the Anagrams
class. The first line in the constructor is as follows:
wordLibrary = WordLibrary.getDefault();
Copy that statement. Paste it into the TopComponent
class, making it the new first statement in the constructor of the TopComponent
class.
Make sure to add the import statement for the WordLibrary
class to the import section at the top of the class:
import com.toy.anagrams.lib.WordLibrary;
-
Run the application again. When the application starts up, you should now see the Anagram Game window, which you defined in this section. You will also find a new menu item that opens the window, under the Window menu. Also notice that the game works as before. You need to click the "New Word" button once, to have the module call up a new word, and then you can use it as before:
As a final step in this section, you can simply delete the com.toy.anagrams.ui
package. That package contains the two UI classes from the original Anagram Game. You do not need either of these two classes anymore. Simply delete the package that contains them, since you have ported everything of interest to the NetBeans Platform. Then also delete the OpenAnagramGameAction
class, since this class is not needed because the AnagramTopComponent
provides its own Action
for opening the window.
Porting Level 2: Use Case Support
In this section, we are concerned with listening to the global selection and making use of data we find there. The global selection is the registry for global singletons and instances of objects which have been registered in the system by modules. Here we query the lookup for EditorCookie
s and make use of the EditorCookie
's document to fill the string array that defines the scrambled words displayed in the TopComponent
.
A cookie is a capability. With a Java interface, your object’s capabilities are fixed at compile time, while NetBeans Platform cookies allow your object to behave dynamically because your object can expose capabilities, or not, based on its state. An EditorCookie
defines an editor, with interfaces for common activities such as opening a document, closing the editor, background loading of files, document saving, and modification notifications. We will listen for the existence of such a cookie and then we will pass the content of the editor to the TopComponent, in the form of words. By doing this, we are inspecting existing functionality and reusing it within the context of our ported application. This is a modest level of integration. However, it pays off because you are reusing functionality provided by the NetBeans Platform.
-
We begin by tweaking the
StaticWordLibrary
class. We do this so that we can set its list of words externally. The sample provides a hardcoded list, but we want to be able to set that list ourselves, via an external action. Therefore, add this method toStaticWordLibrary
:
public static void setScrambledWordList(String[] inScrambledWordList) {
SCRAMBLED_WORD_LIST = inScrambledWordList;
}
Importantly, change the class signature of StaticWordLibrary to public class and remove the final from the signature of SCRAMBLED_WORD_LIST
|
Next, we will create an action that will obtain the content of a Manifest file, break the content down into words, and fill the SCRAMBLED_WORD_LIST
string array with these words.
-
As you learned to do in the previous section, set library dependencies on the Text API and the Nodes API.
-
Create a Java class named
SetScrambledAnagramsAction
, in thecom.toy.anagrams.core
package, and define it as follows:
package com.toy.anagrams.core;
import com.toy.anagrams.lib.StaticWordLibrary;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.StyledDocument;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.cookies.EditorCookie;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
@ActionID(id="com.toy.anagrams.core.SetScrambledAnagramsAction",category="Window")
@ActionRegistration(displayName = "#CTL_SetScrambledAnagramsAction")
@ActionReferences({
@ActionReference(path = "Editors/text/x-manifest/Popup", position = 10)
})
@Messages("CTL_SetScrambledAnagramsAction=Set Scrambled Words")
public final class SetScrambledAnagramsAction implements ActionListener {
private final EditorCookie context;
public SetScrambledAnagramsAction(EditorCookie context) {
this.context = context;
}
@Override
public void actionPerformed(ActionEvent ev) {
try {
//Get the EditorCookie's document:
StyledDocument doc = context.getDocument();
//Get the complete textual content:
String all = doc.getText(0, doc.getLength());
//Make words from the content:
String[] tokens = all.split(" ");
//Pass the words to the WordLibrary class:
StaticWordLibrary.setScrambledWordList(tokens);
//Open the TopComponent:
TopComponent win = WindowManager.getDefault().findTopComponent("AnagramTopComponent");
win.open();
win.requestActive();
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
}
-
As discussed above, when we run the application, we want to be able to right-click within a Manifest file, choose a menu item, and invoke our Action. Right now, however, the NetBeans Platform is unable to distinguish Manifest files from any other file. Therefore, we need to enable Manifest support in our application. For demonstration purposes, we will enable ALL the modules in the NetBeans Platform, as well as those provided by NetBeans IDE that relate to Java development. As a result, when we run the application, a new instance of NetBeans IDE for Java development will start up, together with our custom module. To achieve the above, expand the Important Files node in the AnagramApplication, then open the NetBeans Platform Config file, which on disk is named
platform.properties
. Notice that many clusters (groups of modules) and individual modules have been excluded or disabled. You can enable them via the Project Properties dialog of the NetBeans Platform application. Since we are simply going to enable ALL of those that relate to Java development them, we need only change the content of theplatform.properties
file to the following:
branding.token=anagramapplication
cluster.path=\
${nbplatform.active.dir}/extide:\
${nbplatform.active.dir}/harness:\
${nbplatform.active.dir}/ide:\
${nbplatform.active.dir}/java:\
${nbplatform.active.dir}/platform:\
${nbplatform.active.dir}/websvccommon
disabled.modules=\
javaewah.dummy,\
org.apache.commons.httpclient,\
org.apache.commons.io,\
org.apache.commons.lang,\
org.apache.ws.commons.util,\
org.apache.xmlrpc,\
org.eclipse.core.contenttype,\
org.eclipse.core.jobs,\
org.eclipse.core.net,\
org.eclipse.core.runtime,\
org.eclipse.core.runtime.compatibility.auth,\
org.eclipse.equinox.app,\
org.eclipse.equinox.common,\
org.eclipse.equinox.preferences,\
org.eclipse.equinox.registry,\
org.eclipse.equinox.security,\
org.eclipse.jgit,\
org.eclipse.mylyn.bugzilla.core,\
org.eclipse.mylyn.commons.core,\
org.eclipse.mylyn.commons.net,\
org.eclipse.mylyn.commons.repositories.core,\
org.eclipse.mylyn.commons.xmlrpc,\
org.eclipse.mylyn.tasks.core,\
org.eclipse.mylyn.wikitext.confluence.core,\
org.eclipse.mylyn.wikitext.core,\
org.eclipse.mylyn.wikitext.textile.core,\
org.netbeans.core.browser,\
org.netbeans.core.browser.webview,\
org.netbeans.libs.git,\
org.netbeans.libs.ini4j,\
org.netbeans.libs.javafx,\
org.netbeans.libs.jsch.agentproxy,\
org.netbeans.libs.nbi.ant,\
org.netbeans.libs.nbi.engine,\
org.netbeans.libs.smack,\
org.netbeans.libs.svnClientAdapter,\
org.netbeans.libs.svnClientAdapter.javahl,\
org.netbeans.libs.svnClientAdapter.svnkit,\
org.netbeans.modules.apisupport.harness,\
org.netbeans.modules.bugtracking,\
org.netbeans.modules.bugtracking.bridge,\
org.netbeans.modules.bugtracking.commons,\
org.netbeans.modules.bugzilla,\
org.netbeans.modules.css.prep,\
org.netbeans.modules.editor.global.format,\
org.netbeans.modules.extexecution.impl,\
org.netbeans.modules.git,\
org.netbeans.modules.html.angular,\
org.netbeans.modules.html.custom,\
org.netbeans.modules.html.knockout,\
org.netbeans.modules.hudson.git,\
org.netbeans.modules.hudson.mercurial,\
org.netbeans.modules.hudson.subversion,\
org.netbeans.modules.hudson.tasklist,\
org.netbeans.modules.languages,\
org.netbeans.modules.lexer.nbbridge,\
org.netbeans.modules.localhistory,\
org.netbeans.modules.localtasks,\
org.netbeans.modules.mercurial,\
org.netbeans.modules.mylyn.util,\
org.netbeans.modules.notifications,\
org.netbeans.modules.parsing.ui,\
org.netbeans.modules.properties.syntax,\
org.netbeans.modules.server,\
org.netbeans.modules.spellchecker,\
org.netbeans.modules.spellchecker.bindings.htmlxml,\
org.netbeans.modules.spellchecker.bindings.properties,\
org.netbeans.modules.spellchecker.dictionary_en,\
org.netbeans.modules.spellchecker.kit,\
org.netbeans.modules.subversion,\
org.netbeans.modules.target.iterator,\
org.netbeans.modules.team.commons,\
org.netbeans.modules.team.ide,\
org.netbeans.modules.utilities.project,\
org.netbeans.modules.versioning,\
org.netbeans.modules.versioning.core,\
org.netbeans.modules.versioning.indexingbridge,\
org.netbeans.modules.versioning.masterfs,\
org.netbeans.modules.versioning.system.cvss.installer,\
org.netbeans.modules.versioning.ui,\
org.netbeans.modules.versioning.util,\
org.netbeans.modules.web.webkit.debugging,\
org.netbeans.modules.websvc.saas.kit,\
org.netbeans.modules.websvc.saas.services.amazon,\
org.netbeans.modules.websvc.saas.services.delicious,\
org.netbeans.modules.websvc.saas.services.flickr,\
org.netbeans.modules.websvc.saas.services.google,\
org.netbeans.modules.websvc.saas.services.strikeiron,\
org.netbeans.modules.websvc.saas.services.weatherbug,\
org.netbeans.modules.websvc.saas.services.zillow,\
org.netbeans.modules.websvc.saas.services.zvents,\
org.netbeans.modules.websvc.saas.ui,\
org.openidex.util
nbplatform.active=default
In the next step, when we run the application, all the groups of modules (called "clusters") will be enabled, nothing will be excluded, and you will see NetBeans IDE started up.
-
Build the application by right-clicking it and choosing "Clean and Build". After you have done so, run the application. Go to the Window menu and choose Favorites. In the Favorites window, browse to a Manifest file. Open the file. Inside the file, i.e., in the Manifest Editor, right-click, and invoke the Set Scrambled Words action via the menu item.
The AnagramTopComponent
is displayed and, when you click the Next Word button, you will see that the scrambled words all come from the selected Manifest file.
The result of this exercise is that you now see the content of the Manifest file in the Scrambled Word text field. Of course, these words are not really scrambled and you cannot really unscramble them. However, your module is making use of the content of a file that is supported by a different set of modules altogether, that is, the Manifest support modules, as well as related editor modules.
Optionally, before continuing, you can now remove all the groups of modules (known as "clusters") provided by NetBeans IDE, which may not be relevant for your own application. To do so, right-click the AnagramApplication
node in the Projects window, choose Properties, go to the Libraries tab, and uncheck all the checkboxes, except for platform
. Run the application again and you will see that all the project-related and editor-related features of the application have now been removed.
Porting Level 3: Aligned
In this section, we are concerned with becoming a "good citizen" of the NetBeans Platform. We are going to expose the state of the TopComponent to the other modules, so that we can cooperate with them.
As an example of this, we will modify the TopComponent to offer a Savable
, which gives the user a way to store the text typed in the text field. By offering the Savable
when changes are made in the text field, the Save button and the Save menu item under the File menu and the shortcuts for invoking the Save action will become enabled. That is because the NetBeans Platform provides a context-sensitive Action called SaveAction
. The SaveAction
becomes enabled whenever the capability of being saved, in other words, the Savable
, is available. In this case, we will make the Savable
available whenever the user types something in the guessedWord
text field. Then the SaveAction
will automatically become enabled.
When the user selects the enabled button or menu item, a dialog will be displayed and the button and menu item will become disabled, until the next time that a change is made to the text field.
-
Begin by setting a library dependency on the Dialogs API, which you learned to do in the previous sections.
-
Next, we define an extension of the ` AbstractSavable
, somewhere within the `AnagramTopComponent
class:
class MySavable extends AbstractSavable {
private final Object obj;
public MySavable(Object obj) {
this.obj = obj;
register();
}
@Override
protected String findDisplayName() {
return "My name is " + obj.toString(); // get display name somehow
}
@Override
protected void handleSave() throws IOException {
// save 'obj' somehow
Confirmation msg = new NotifyDescriptor.Confirmation("Do you want to save \""
+ guessedWord.getText() + "\"?", NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(msg);
//When user clicks "Yes", indicating they really want to save,
//we need to disable the Save button and Save menu item,
//so that it will only be usable when the next change is made
//to the text field:
if (NotifyDescriptor.YES_OPTION.equals(result)) {
fire(false);
//Implement your save functionality here.
}
}
@Override
public boolean equals(Object other) {
if (other instanceof MySavable) {
return ((MySavable)other).obj.equals(obj);
}
return false;
}
@Override
public int hashCode() {
return obj.hashCode();
}
}
We have not defined the fire
method yet, so the related statement above will be underlined in red until we do so.
-
In the constructor, call the as-yet-undefined
fire
method, passing in true this time, whenever a change is detected in theguessedWord
text field:
guessedWord.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent arg0) {
fire(true);
}
@Override
public void removeUpdate(DocumentEvent arg0) {
fire(true);
}
@Override
public void changedUpdate(DocumentEvent arg0) {
fire(true);
}
});
-
Now we declare an ` InstanceContent` at the top of the class. The
InstanceContent
class is a very powerful class in the NetBeans Platform, enabling you to update the Lookup on the fly, at runtime. We also declare the implementation of ourSavable
:
InstanceContent ic;
MySavable impl;
-
Next, at the end of the constructor, we instantiate the
Savable
and theInstanceContent
, while adding theInstanceContent
to theLookup
of theAnagramTopComponent
:
impl = new MySavable(guessedWord.getText());
ic = new InstanceContent();
associateLookup(new AbstractLookup(ic));
-
Now we can add the
fire
method, which dynamically adds and removes theSavable
from theInstanceContent
:
public void fire(boolean modified) {
if (modified) {
//If the text is modified,
//we add the Savable implementation
//to the InstanceContent, which
//is in the Lookup of the TopComponent:
ic.add(impl);
} else {
//Otherwise, we remove the Savable
//from the InstanceContent:
ic.remove(impl);
}
}
-
Run the application again. Make a change in the "Guessed Word" text field and notice that the Save menu item is enabled:
Click the menu item, click the "OK" button in the dialog…
…and notice that the Save menu item is disabled afterwards.
Congratulations! Now that your application is making use of existing NetBeans Platform functionality, you have taken one further step in successfully aligning it with the NetBeans Platform. Other modules can be now be plugged into the NetBeans Platform to take advantage of, or even extend, features added by your application. Hence, not only can your application benefit from what the NetBeans Platform provides, but you can create features that other modules can use as well.
Porting Tips & Tricks
There are several next steps one can take at this point, aside from further aligning the application with the NetBeans Platform, as outlined above:
-
Attain a thorough understanding of what the NetBeans Platform provides. As you port your application, you will learn more and more about the various features that the NetBeans Platform makes available. A central problem is that the NetBeans Platform is quite large and attaining a thorough overview of all that it offers can be a lengthy process. A quick shortcut is to download and print out the NetBeans Platform 7.0 Refcard, which is a free DZone document that highlights all the NetBeans Platform benefits, features, APIs, and many tips and tricks in an easy to digest format.
-
Become aware of the differences between standard Swing applications and the NetBeans Platform. For the most part, the standard Swing approach to creating a user interface will continue to work for your NetBeans Platform application. However, the NetBeans Platform approach is better, easier, or both in some cases. One example is that of the NetBeans Dialogs API. The standard Swing approach, via, for example, the
JOptionsPane
, works OK, but using the NetBeans Dialogs API is easier, because it automatically centers your dialog in the application and allows you to dismiss it with the ESC key. Using the Dialogs API also lets you plug in a different DialogDisplayer, which can make it easier to customize or test your application.
Below is a list of the principle differences between the typical Swing approach and that of the NetBeans Platform:
-
Loading of images
-
Loading of resource bundles and localized string
-
Assigning of mnemonics to labels and buttons
-
Showing dialogs
For details on all of the above items, read this FAQ: Common calls that should be done slightly differently in NetBeans than standard Swing apps (loading images, localized strings, showing dialogs).
In addition, note that, since the NetBeans Platform now handles the lifecycle of your module, since it is now part of the whole application, you can no longer use System.exit
. Instead, you need to use LifecycleManager
. To run code on start up, which should only be done when absolutely necessary, you need to use the NetBeans ModuleInstall
class and, specifically, its restored
method. A useful reference in this context is Porting a Java Swing Application to the NetBeans Platform, by Tom Wheeler, in Building A Complete NetBeans Platform Application.
-
Create a module project for each distinct part of your application. The NetBeans Platform provides a modular architecture out of the box. Break your application into one or more modules. Doing so requires some analysis of your original application and an assessment of which parts could best fit within a new module and how to communicate between them. Since the example in this tutorial was simple, we only needed one module. A next step might be to put the
WordLibrary
class in a separate module and expose it as a public API. TheStaticWordLibrary
would be put into another module, providing an implementation of theWordLibrary
API. Doing so would let other modules provide user interfaces on top of the API provided by the first module, without depending in any way on the implementations.
As shown above, you need to put the modules in a module suite. Then set a dependency in the plugin module on the API module, using the Libraries panel in the plugin module’s Project Properties dialog box. The size of each module, i.e., when one should create a new module or continue developing within an existing one, is a question of debate. Smaller is better, in general.
-
Always keep reevaluating what you really need to port. Look at the NetBeans Platform and decide where there is overlap with your own application. Where there is overlap, such as the menu bar and About box, decide what you want to do. Typically, you want to leverage as much as possible from the NetBeans Platform. Therefore, you would port as little as possible from your own application, while keeping as much of it as is useful to you.
-
Move distinct parts of your user interface to one or more TopComponents. On the NetBeans Platform, the
TopComponent
class provides the top level Swing container. In effect, it is a window. Move the user interface from your original application to one or more of these windows and discard your originalJFrame
s. -
Copy the Java classes that do not provide user interface elements. We simply copied the original
WordLibrary.java
class. You can do the same with the model of your own Swing applications. You might need to tweak some code to smoothen the transition between the old Swing application and the new NetBeans Platform application, but (as in the case shown in this tutorial) this might not even be necessary. -
Learn from others. Aside from joining the dev@platform.netbeans.org mailing list, also read the following two crucial articles:
-
Watch the Top 10 NetBeans APIs Screencast. The screencast series gives a good overview of the NetBeans Platform, with many useful code snippets and coding patterns. Send Us Your Feedback
Next Steps
For more information about creating and developing NetBeans modules, see the following resources: