http://blog.csdn.net/cutesource/archive/2010/08/04/5788534.aspx
31.8.10
数独
发信人: zms (小美), 信区: Java
标 题: Re: 数独
发信站: 水木社区 (Tue Aug 31 17:17:47 2010), 站内
搞定了,太没挑战了。。。
回溯 (最优路径判断)搞定了
hard++都是 瞬间出结果
不如聊聊 AtomicInteger 和 synchronized
发信人: zms (小美), 信区: Java
标 题: Re: 我发现
发信站: 水木社区 (Tue Aug 31 19:44:25 2010), 站内
写了段程序benchmark一下:
开100个线程,每个线程5e7次 对一个counter做 ++
1。 不做同步 (最后结果是错误的),用时 将近4秒
2。 synchronized 同步 ,用时将近 80 秒
3。 AtomicInteger ,用时 31秒
30.8.10
3.8.10
NetBeans Platform Porting Tutorial
This tutorial demonstrates how to port a simple Swing 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.
Contents
To follow this tutorial, you need the software and resources listed in the following table.
Software or Resource
Version Required
NetBeans IDE
version 6.9 or above
Java Developer Kit (JDK)
version 6
Introduction to Porting
Before beginning this procedure, it makes sense to ask why one would want to do so 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 outlined in the Projects window, as shown here:
The application contains the following classes:
- WordLibrary.java. Provides an abstract class, with abstract methods like
getWord(int idx)
,getScrambledWord(int idx)
, andisCorrect(int idx, String userGuess)
. - StaticWordLibrary.java. Extends
WordLibrary.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 a
JFrame
with a JPanelcontaining 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.
- WordLibrary.java. Provides an abstract class, with abstract methods like
-
Run the application and you should see the following:
-
When you specify 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 created automatically when you create a new module project in NetBeans IDE, as you will see later.
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 use the New Action wizard to create a new menu item, which will automatically be registered for us, and a dependency will be set on the UI Utilities API, which we will need to implement the Action. 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 the DialogDisplayer 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 TopComponentclass 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 theTopComponent, 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 you are reusing functionality provided by the NetBeans Platform.
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 SaveCookiewhen changes are made to the "Guessed Word" text field. By doing this, we will enable the Save menu item under the Tools menu. 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 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.
Next, we create our first custom module. We will name it
AnagramUI
because, in the end, it will only contain the user interface (UI) of the application. In a subsequent tutorial, we will add additional modules, which will provide an API and an implementation of the business logic. But, for the moment, we will port everything into the module that will ultimately provide only the UI of the application. -
Choose File > New Project (Ctrl-Shift-N) again. Under Categories, select NetBeans Modules. Under Projects, select Module, as shown below:
Click Next.
-
Type AnagramGameUI in Project Name and choose somewhere to store the module, 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 org.anagram.ui.
Note: Optionally, check "Generate XML Layer". If you do not select it, that's fine, because later in this tutorial you will be shown how to create it manually and you will also learn what it is used for.
-
Click Finish.
Below the 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 from a module. 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
org.anagram.ui
package, create a new Java class namedOpenAnagramGameAction
, implementing the standard JDKActionListener
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);
}
}When the user invokes the
OpenAnagramGameAction
, theJFrame
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".If you do not have a file named
layer.xml
yet, create one in theorg.anagram.ui
package, with this content:<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
<filesystem>
<folder name="Actions">
<folder name="Window">
<file name="org-anagram-ui-OpenAnagramGameAction.instance">
<attr name="delegate" newvalue="org.anagram.ui.OpenAnagramGameAction"/>
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
<attr name="displayName" bundlevalue="org.anagram.ui.Bundle#CTL_OpenAnagramGameAction"/>
</file>
</folder>
</folder>
<folder name="Menu">
<folder name="Window">
<file name="OpenLibraryViewerWindowAction.shadow">
<attr name="originalFile" stringvalue="Actions/Window/org-anagram-ui-OpenAnagramGameAction.instance"/>
</file>
</folder>
</folder>
</filesystem>Each module in your application can have, at most, one
layer.xml
file. The file is used to register contributions to the NetBeans Platform application. For example, new windows and new Actions are registered here, in folders specifically dedicated to the contribution in question. Above, you can see we are dealing with the "Actions" folder and the "Menu" folder. Some notes on the elements and attributes above:Above, in the "delegate" attribute, you have registered the
OpenAnagramGameAction
class. In the "instanceCreate" attribute, you have specified that the NetBeans Platform methodorg.openide.awt.Actions.alwaysEnabled
will be called when theOpenAnagramGameAction
is invoked. This is a NetBeans Platform method that creates an Action that is always enabled, regardless of the context of the application. Since you want the Anagram Game to be able to be started at any time, it makes sense for the Action to always be enabled.The "displayName" attribute above points to a key in the
Bundle.properties
file, which is the central file for storing all the localizable strings in the module.While the "Actions" folder registers the Action, the "Menu" folder specifies where the Action will be displayed. Above, you can see that the Action will be displayed in the Window menu.
In the
Bundle.properties
file, add the following key/value pair, based on the "displayName" attribute above:CTL_OpenAnagramGameAction=Open Anagram Game
In the Module Manifest, which you can find in the Important Files node in the module, and which is
manifest.mf
on disk, register thelayer.xml
file as follows:OpenIDE-Module-Layer: org/anagram/ui/layer.xml
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". Choose it and your application appears.
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 entire application. The latter is because the entire application now manages the lifecycle of the JFrame
. In the next section, we will integrate the Anagram Game more tightly with the NetBeans Platform.
Note: In the section above, you manually created and registered a layer.xml
file and you manually created anActionListener
and you manually registered the ActionListener
in the layer.xml
file. Now that you know how and why and where these activities occur, you can automate the process. Whenever you create a new module, select the "Generate XML Layer" checkbox, which will let the IDE automatically create the layer.xml
file and the IDE will also register it in themanifest.mf
file for you. In addition, you can automate the creation and registration of Actions by using the "New Action" wizard, which is available in the New File dialog (Ctrl-N). Also, whenever you use a wizard that registers something in thelayer.xml
file, such as the New Action wizard does, and the layer.xml
file does not exist, the layer.xml
file will automatically be created for you.
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
org.anagram.ui
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:
Optionally, specify whether the window should open automatically when the application starts up.
Click Next.
Type Anagram in Class Name Prefix and select org.anagram.ui in Package, as shown here:
Above, notice that the IDE shows the files it will create and modify.
Click Finish.
Now you have a set of new Java and XML source files, as shown here:
Open the Anagrams class in the
com.toy.anagrams.ui
package. Also open the AnagramTopComponent, which was created in the previous step. When you click the mouse in the Anagram class, notice that the labels and text fields are in a Swing container, in this case a JPanel, as shown here:Tip: If the Swing components were not within a container, you could select them all with the mouse, then right-click and choose "Enclose In", to let the IDE create a container within which all the selected components would be enclosed.
Right-click the JPanel and copy it. Paste it in the TopComponent and you should see the old user interface in your new
TopComponent
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 theTopComponent
class.Next, open the
Bundle.properties
file. The content should be something similar to this:CTL_AnagramAction=Anagram
CTL_AnagramTopComponent=Anagram Window
HINT_AnagramTopComponent=This is a Anagram window
OpenIDE-Module-Name=AnagramGameUI
CTL_OpenAnagramGameAction=Open Anagram Game
AnagramTopComponent.feedbackLabel.text=\
AnagramTopComponent.guessLabel.text=Your Guess:
AnagramTopComponent.scrambledLabel.text=Scrambled Word:
AnagramTopComponent.nextTrial.toolTipText=Fetch a new word.
AnagramTopComponent.nextTrial.text=New Word
AnagramTopComponent.guessButton.toolTipText=Guess the scrambled word.
AnagramTopComponent.guessButton.text=GuessNotice line 6 above. The IDE erroneously created a backslash instead of a space in the key/value pair in line 6 above. If you have a line where the value is set to be a backslash, simply delete the backslash.
If you do not delete the backslash, when you run the application, in the next step, you will see error messages such as these:
--java.util.MissingResourceException: Can't find resource for bundle
org.openide.util.NbBundle$PBundle, key AnagramTopComponent.guessLabel.text
--java.lang.AssertionError: Component cannot be created for {component=null,
displayName=Anagram, instanceCreate=AlwaysEnabledAction[Anagram]}If you see the above error messages, open the
Bundle.properties
file and remove the erroneously generated backslash. This is a known bug.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, as well as the registration entries you manually added to thelayer.xml
file earlier in this tutorial. Be careful when you delete these entries, since others have been added to thelayer.xml
file in the meantime and you do not want to delete entries that do not relate to this specific Action that you are deleting!
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 EditorCookies 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. AnEditorCookie
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 to StaticWordLibrary:
public static void setScrambledWordList(String[] inScrambledWordList) {
SCRAMBLED_WORD_LIST = inScrambledWordList;
}Importantly, change the class signature of StaticWordLibrary to
public class
and remove thefinal
from the signature ofSCRAMBLED_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 theSCRAMBLED_WORD_LIST string array with these words.
- Right-click the module, choose Properties, and then open the Libraries tab in the Project Properties dialog. Click "Add Dependency" and then set dependencies on the Text API and the Nodes API.
- In the Source Editor, create a Java class named
SetScrambledAnagramsAction
and define it as follows: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 = AnagramTopComponent.findInstance();
win.open();
win.requestActive();
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
} Register the Action above in the layer file. Unlike the previous Action, the Action above should not always be enabled. It should be available for Manifest files only, since that is the file type we happen to be interested in. Furthermore, the Action should only be enabled if an
EditorCookie
is present. The result of these requirements is a registration with this content:<folder name="Actions">
<folder name="Window">
<file name="org-anagram-ui-SetScrambledAnagramsAction.instance">
<attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
<attr name="displayName" bundlevalue="org.anagram.ui.Bundle#CTL_SetScrambledAnagramsAction"/>
<attr name="injectable" stringvalue="org.anagram.ui.SetScrambledAnagramsAction"/>
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
<attr name="noIconInMenu" boolvalue="false"/>
<attr name="selectionType" stringvalue="EXACTLY_ONE"/>
<attr name="type" stringvalue="org.openide.cookies.EditorCookie"/>
</file>
</folder>
</folder>
<folder name="Editors">
<folder name="text">
<folder name="x-manifest">
<folder name="Popup">
<file name="org-anagram-ui-SetScrambledAnagramsAction.shadow">
<attr name="originalFile" stringvalue="Actions/Window/org-anagram-ui-SetScrambledAnagramsAction.instance"/>
<attr name="position" intvalue="900"/>
</file>
</folder>
</folder>
</folder>
</folder>Many of the attributes above you have used previously. For example, the "displayName" attribute points to a key in the
Bundle.properties
file. This key does not exist yet. Create it now, in theBundle.properties
file, as follows:CTL_SetScrambledAnagramsAction=Set Scrambled Words
The
instanceCreate
attribute specifies that the org.openide.awt.Actions.context method handles the Action.Note: Above, you manually created an Action class and registered it in the
layer.xml
file. Now that you know how to do this, feel free to use the New Action wizard (in the New File dialog, Ctrl-N) instead to create context-sensitive Actions from now onwards.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. As a result, when we run the application, a new instance of NetBeans IDE will start up, together with our custom module.
To achieve the above, expand the Important Files node, then open the NetBeans Platform Config file, which on disk is named
platform.properties
. Notice that many modules have been disabled. You can enable them via the Project Properties dialog of the NetBeans Platform application. Since we are simply going to enable ALL of them, we need only change the content of theplatform.properties
file to the following:cluster.path=\
${nbplatform.active.dir}/apisupport:\
${nbplatform.active.dir}/harness:\
${nbplatform.active.dir}/ide:\
${nbplatform.active.dir}/java:\
${nbplatform.active.dir}/nb:\
${nbplatform.active.dir}/platform:\
${nbplatform.active.dir}/profiler:\
${nbplatform.active.dir}/websvccommon
disabled.modules=
nbplatform.active=defaultIn 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.
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 harness
and 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 SaveCookie, which gives the user a way to store the text typed in the text field. By offering the SaveCookie when changes are made in the text field, the Save button and the Save menu item under the File menu will become enabled. That is because the NetBeans Platform provides a context-sensitive Action calledSaveAction
. The SaveAction
becomes enabled whenever the capability of being saved, in other words, the SaveCookie
, is available. In this case, we will make the SaveCookie
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 dependency on the Dialogs API. Do this by right-clicking the project node in the Projects window, choosing Properties, and then using the Libraries tab to add the Dialogs API as a dependency of the module.
- Next, we define an implementation of the
SaveCookie
, somewhere within theAnagramTopComponent
class:private class SaveCookieImpl implements SaveCookie {
@Override
public void save() throws IOException {
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.
}
}
}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);
}
public void removeUpdate(DocumentEvent arg0) {
fire(true);
}
public void changedUpdate(DocumentEvent arg0) {
fire(true);
}
});Now we declare an
InstanceContent
at the top of the class. TheInstanceContent
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 ourSaveCookie
:InstanceContent ic;
SaveCookieImpl impl;Next, at the end of the constructor, we instantiate the
SaveCookie
and theInstanceContent
, while adding theInstanceContent
to theLookup
of theAnagramTopComponent
:impl = new SaveCookieImpl();
ic = new InstanceContent();
associateLookup(new AbstractLookup(ic));Now we can add the
fire
method, which dynamically adds and removes theSaveCookie
from theInstanceContent
:public void fire(boolean modified) {
if (modified) {
//If the text is modified,
//we add the SaveCookie implementation
//to the InstanceContent, which
//is in the Lookup of the TopComponent:
ic.add(impl);
} else {
//Otherwise, we remove the SaveCookie
//from the InstanceContent:
ic.remove(impl);
}
}- By default, you have a Save menu item under the File menu, but no Save button in the toolbar. For testing purposes, that is, to simplify checking whether the
SaveAction
is enabled, you may want a Save button in the toolbar. For this purpose, add the following to the layer:<folder name="Toolbars">
<folder name="File">
<file name="org-openide-actions-SaveAction.shadow">
<attr name="originalFile" stringvalue="Actions/System/org-openide-actions-SaveAction.instance"/>
<attr name="position" intvalue="444"/>
</file>
</folder>
</folder> Run the application again. Make a change in the "Guessed Word" text field and notice that the Save button and the Save menu item become enabled:
Select either the button or the menu item, click the "OK" button in the dialog...
...and notice that the Save functionality 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 Essential NetBeans Platform 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.
- Loading of images
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 WordLibraryclass in a separate module and expose it as a public API. The StaticWordLibrary would be put into another module, providing an implementation of the WordLibrary 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, theTopComponent 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 original JFrames.
- Copy the Java classes that do not provide user interface elements. We simply copied the originalWordLibrary.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.
http://platform.netbeans.org/tutorials/nbm-porting-basic.html
2.8.10
The Continuing March of ActionListener in the NetBeans Platform
One of the interesting enhancements to the NetBeans Platform in NetBeans Platform 6.5 was the fact that you can now use the standard JDK ActionListener class when creating 'aways enabled' menu items and toolbar buttons in your NetBeans Platform applications.
In the next release of the NetBeans Platform, i.e., after 6.7, this will go a step further. When you create 'conditionally enabled' actions, such as those that appear in the Java editor's contextual menu, you will be able to define your action like this:
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import org.openide.cookies.EditorCookie;
public final class BSomeAction implements ActionListener {
private final EditorCookie context;
public BSomeAction(EditorCookie context) {
this.context = context;
}
public void actionPerformed(ActionEvent ev) {
// TODO use context
}
}
So, again, you're using a plain old ActionListener, with the context that you're working with (the editor) being the only foreign class in your code.
Assuming you want the action to be invoked from a menu item on a node in the explorer view, the layer entries for the above would be as follows:
<folder name="Actions">
<folder name="Build">
<file name="org-demo-bla2-BSomeAction.instance">
<attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
<attr name="displayName" bundlevalue="org.demo.bla2.Bundle#CTL_BSomeAction"/>
<attr name="injectable" stringvalue="org.demo.bla2.BSomeAction"/>
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
<attr name="noIconInMenu" boolvalue="false"/>
<attr name="selectionType" stringvalue="EXACTLY_ONE"/>
<attr name="type" stringvalue="org.openide.cookies.EditorCookie"/>
</file>
</folder>
</folder>
<folder name="Loaders">
<folder name="text">
<folder name="x-java">
<folder name="Actions">
<file name="org-demo-bla2-BSomeAction.shadow">
<attr name="originalFile" stringvalue="Actions/Build/org-demo-bla2-BSomeAction.instance"/>
<attr name="position" intvalue="0"/>
</file>
</folder>
</folder>
</folder>
</folder>
If, on the other hand, the action class were to appear within the editor itself, the layer registration would be as follows:
<folder name="Actions">
<folder name="Build">
<file name="org-demo-bla3-BSomeAction.instance">
<attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
<attr name="displayName" bundlevalue="org.demo.bla3.Bundle#CTL_BSomeAction"/>
<attr name="injectable" stringvalue="org.demo.bla3.BSomeAction"/>
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
<attr name="noIconInMenu" boolvalue="false"/>
<attr name="selectionType" stringvalue="EXACTLY_ONE"/>
<attr name="type" stringvalue="org.openide.cookies.EditorCookie"/>
</file>
</folder>
</folder>
<folder name="Editors">
<folder name="text">
<folder name="x-java">
<folder name="Popup">
<file name="org-demo-bla3-BSomeAction.shadow">
<attr name="originalFile" stringvalue="Actions/Build/org-demo-bla3-BSomeAction.instance"/>
<attr name="position" intvalue="400"/>
</file>
</folder>
</folder>
</folder>
</folder>
The above is already possible with dev builds. Nevertheless, I'm looking forward to being able to do the above via annotations instead of layer entries.
Further reading:
http://bits.netbeans.org/dev/javadoc/org-openide-awt/apichanges.html#Actions.context
http://blogs.sun.com/geertjan/entry/the_continuing_march_of_actionlistener
How do I create an Action that is automatically enabled and disabled depending on the selection?
There are several ways to do this, depending on what exactly you need. The basic problems all of the available solutions are addressing is that:
- An action may be created and shown in a menu, toolbar or popup menu.
- While it is visible on-screen, the selected file (or whatever) can change.
- If it is context sensitive, it should run against the thing it was shown for not whatever is selected at the millisecond when it is actually called
- People want to write main-menu and toolbar actions which are enabled and disabled based on what is selected - in practice this means writing an object that enables and disables itself based on a particular type — a particular class or its subclasses — being selected (each logical window in NetBeans has its own "selection"; the "global selection" is whatever is selected in whatever window currently has focus)
NetBeans 6.9 allows context-sensitive actions to be registered declaratively in your module'sXML layer file. In the IDE, File > New File > Module Development > Action will generate some rather opaque layer-based registration code for you (on the first page of the wizard, specify that you want a context sensitive action):
<file name="mypackage-SomeAction.instance">
<attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
<attr name="displayName" bundlevalue="mypackage.Bundle#CTL_SomeAction"/>
<attr name="injectable" stringvalue="mypackage.SomeAction"/>
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
<attr name="noIconInMenu" boolvalue="true"/>
<attr name="selectionType" stringvalue="ANY"/>
<attr name="type" stringvalue="org.netbeans.api.project.Project"
/>
</file>
and a somewhat inscrutable ActionListener
implementation
public final class SomeAction implements ActionListener {
private final List<Project> context;
public SomeAction(List<Project> context) {
this.context = context;
}
public void actionPerformed(ActionEvent ev) {
for (Project project : context) {
// TODO use project
}
}
}
which will be called if and only if one or more projects is selected. The good news is that the code is lightweight, simple and works; the bad news is that it is utterly non-obvious how it ever gets called, and doesn't handle more complicated enablement logic.
If you need something more featureful, there are a few options, old and new:
NodeAction
NodeAction is somewhat more flexible, but requires more code to implement. It is just passed the array of activated nodes whenever that changes, and can choose to enable or disable itself as it wishes. Essentially this is just an action that automagically tracks the global Node selection.
Roll your own
The following is relatively simple and affords a way to perform whatever enablement logic you like (NodeAction can do that too, but this might be a little more straightforward and your code doesn't have to worry about nodes at all: DevFaqWhatIsANode). To understand how this works, see DevFaqTrackGlobalSelection:
public class FooAction extends AbstractAction implements LookupListener, ContextAwareAction {
private Lookup context;
Lookup.Result<Whatever> lkpInfo; public FooAction() {
this(Utilities.actionsGlobalContext());
} private FooAction(Lookup context) {
putValue(Action.NAME, NbBundle.getMessage(FooAction.class, "LBL_Action"));
this.context = context;
} void init() {
assert SwingUtilities.isEventDispatchThread()
: "this shall be called just from AWT thread"; if (lkpInfo != null) {
return;
} //The thing we want to listen for the presence or absence of
//on the global selection
lkpInfo = context.lookupResult(Whatever.class);
lkpInfo.addLookupListener(this);
resultChanged(null);
} public boolean isEnabled() {
init();
return super.isEnabled();
} public void actionPerformed(ActionEvent e) {
init();
for (Whatever instance : lkpInfo.allInstances()) {
// use it somehow...
}
} public void resultChanged(LookupEvent ev) {
setEnabled(!lkpInfo.allInstances().isEmpty());
} public Action createContextAwareInstance(Lookup context) {
return new FooAction(context);
}
}
Deprecated CookieAction
In many older (pre-NB 6.8) examples you may find CookieAction. It should be (but is not) deprecated. The original info is left here for reference and/or old code maintenance:
CookieAction is used to write actions that are sensitive to what is in the selected Node(s)Lookup. You can specify one or more classes that must be present in the selected Node's Lookup, and some other semantics about enablement.
Being an older class, under the hood it is using Node.getCookie(), so your action will only be sensitive to things actually returned by that method - in other words, only objects that implement the marker interface Node.Cookie can work here.
Not-Yet-Official spi.actions
This module is part of the platform as of 6.8, but has not yet become official API. Nonetheless it is there, it is stable and straightforward to use. The example below opens a visual editor window if an instance of RAFDataObject is selected and has a RandomAccessFile in its lookup:
public final class CustomOpenAction extends org.netbeans.spi.actions.Single<RAFDataObject>
{
public CustomOpenAction() {
super(RAFDataObject.class, "Open", null);
}
@Override
protected void actionPerformed(RAFDataObject target) {
//If an editor is already open, just give it focus
for (TopComponent tc : TopComponent.getRegistry().getOpened()) {
if (tc instanceof RAFEditor && tc.getLookup().lookup(RAFDataObject.class) == target) {
tc.requestActive();
return;
}
}
//Nope, need a new editor
TopComponent editorWindow = null;
editorWindow = new RAFEditor(target);
editorWindow.open();
editorWindow.requestActive();
}
@Override
protected boolean isEnabled(RAFDataObject target) {
//Make sure there really is a file on disk
return target.getLookup().lookup(RandomAccessFile.class) != null;
}
}
Use ContextAction
instead of Single
to create actions that operate on multi-selections.
How to Make the NetBeans Platform Sensitive to Customers
In your NetBeans Platform application, you may often find yourself in the situation where you need to create actions that are sensitive to their context. A simple case in point is shown below. We have a customer application with an action invoked from the toolbar, as well as from a menu item in the main menubar and from a node in an explorer view. In the screenshot below, the action is invoked from the button with the angry face (a typical customer expression) in the toolbar, from the "Customer Details" menu item on the node in the view window, as well as from a menu item in the File menu (which you can't see below). In this case, there is a Customer object on which the action can be invoked...
...while in this situation, the UI for invoking the action is disabled (as you can see, by looking at the button in the toolbar, which is disabled, and notice that other actions are enabled in this context, since those actions relate to the editor, which is the window that is currently selected) because here we no longer have a Customer object in the context of the application, since the cursor is currently in the editor window, instead of in the view window that exposes the Customer object from the currently selected node:
So the question in this article is how to create an action such as the above. The good news is that it's really easy to do so, but you do need to be aware of the steps you need to take, which is why I am writing this article.
Take the following steps:
- In the New Action wizard, in the module where you want to create your action, specify that you want to create a contextually aware action that should be sensitive to Customer objects, which is part of the model in my application:
Note: We (i.e., the NetBeans team) need to change the strings in the dialog above (i.e., in the NetBeans IDE source code), since in both cases an ActionListener will be created. I.e., when you select the "Conditionally Enabled" radiobutton, you will notget a CookieAction. Instead, you will get an ActionListener that is registered in the layer such that it is injected with context-sensitivity to Customer objects, as will be seen below. - Click Next and then specify that you want to create a menu item and a toolbar button:
- Click Next again and choose an icon on disk, as well as specifying a class name prefix and a display name:
Tip: Read this cool tip about icons and NetBeans Platform applications! - When you complete the above wizard, you will see you have a plain old ActionListener class, which is great news since this means you can port your own ActionListeners from your own application to the NetBeans Platform without needing to rewrite them in any way. In other words, the NetBeans Platform handles ActionListeners natively and does not require you to use some special NetBeans API for creating actions. Here's the ActionListener created from the above, which has access to the current Customer object (pretty handy!):
01.
package
org.shop.ui;
02.
03.
import
demo.Customer;
04.
import
java.awt.event.ActionListener;
05.
import
java.awt.event.ActionEvent;
06.
07.
public
final
class
CustomerDetailsAction
implements
ActionListener {
08.
09.
private
final
Customer context;
10.
11.
public
CustomerDetailsAction(Customer context) {
12.
this
.context = context;
13.
}
14.
15.
public
void
actionPerformed(ActionEvent ev) {
16.
// TODO use context
17.
}
18.
19.
}
Meanwhile, your layer.xml file has the following entries, created by the above wizard, turning your humble ActionListener into a context-sensitive action that is sensitive to Customer objects:
01.
<
folder
name
=
"Actions"
>
02.
<
folder
name
=
"Build"
>
03.
<
file
name
=
"org-shop-ui-CustomerDetailsAction.instance"
>
04.
<
attr
name
=
"delegate"
methodvalue
=
"org.openide.awt.Actions.inject"
/>
05.
<
attr
name
=
"displayName"
bundlevalue
=
"org.shop.ui.Bundle#CTL_CustomerDetailsAction"
/>
06.
<
attr
name
=
"iconBase"
stringvalue
=
"org/shop/ui/customer.png"
/>
07.
<
attr
name
=
"injectable"
stringvalue
=
"org.shop.ui.CustomerDetailsAction"
/>
08.
<
attr
name
=
"instanceCreate"
methodvalue
=
"org.openide.awt.Actions.context"
/>
09.
<
attr
name
=
"noIconInMenu"
boolvalue
=
"false"
/>
10.
<
attr
name
=
"selectionType"
stringvalue
=
"EXACTLY_ONE"
/>
11.
<
attr
name
=
"type"
stringvalue
=
"demo.Customer"
/>
12.
</
file
>
13.
</
folder
>
14.
</
folder
>
15.
<
folder
name
=
"Menu"
>
16.
<
folder
name
=
"File"
>
17.
<
file
name
=
"org-shop-ui-CustomerDetailsAction.shadow"
>
18.
<
attr
name
=
"originalFile"
stringvalue
=
"Actions/Build/org-shop-ui-CustomerDetailsAction.instance"
/>
19.
<
attr
name
=
"position"
intvalue
=
"1300"
/>
20.
</
file
>
21.
</
folder
>
22.
</
folder
>
23.
<
folder
name
=
"Toolbars"
>
24.
<
folder
name
=
"File"
>
25.
<
file
name
=
"org-shop-ui-CustomerDetailsAction.shadow"
>
26.
<
attr
name
=
"originalFile"
stringvalue
=
"Actions/Build/org-shop-ui-CustomerDetailsAction.instance"
/>
27.
</
file
>
28.
</
folder
>
29.
</
folder
>
Note: I tweaked line 11 above. By default, the type is set to "Customer", while it actually needs to be the fully-qualified name of the Customer class, which is "demo.Customer", since the Customer object is found in a package called "demo". Maybe in the New Action wizard, one should type the fully-qualified name, rather than just the name of the domain class. Need to check that. The "type" element in the layer determines the context available to the ActionListener, i.e., the currently available Customer object.
By the way, above, in line 10, we are ensuring that the action will be disabled if more than one node is selected (thanks to choosing "User Selects One Node" in the first page of the New Action wizard), as can be seen here: - Finally, let's add the action as a contextual menu item on our node. In this case, I know I have two actions in the "Actions/Build" folder, one from the current module, and the other from another one. You could also get all the actions within a particular folder, rather than specific ones, or you could get actions from different folders. Up to you.
01.
private
class
CustomerNode
extends
AbstractNode {
02.
03.
public
CustomerNode(Customer c) {
04.
super
(Children.LEAF, Lookups.singleton(c));
05.
setDisplayName(c.getName());
06.
setShortDescription(c.getCity());
07.
}
08.
09.
@Override
10.
public
Action[] getActions(
boolean
context) {
11.
return
new
Action[]{
12.
Utilities.actionsForPath(
"Actions/Build/"
).get(
0
),
13.
Utilities.actionsForPath(
"Actions/Build/"
).get(
1
),
14.
};
15.
}
16.
17.
}
And that's really all. You can use your plain old ActionListener class. The downside is you have a bunch of tags in the layer file to deal with, though (aside from the small tweak) it was all generated for you. Looking forward to an annotation for actions, so that my ActionListener can simply be decorated with an annotation that will create the necessary layer entries when the module is compiled.
Nevertheless, you now have a context-sensitive action for customer objects.
http://netbeans.dzone.com/how-to-make-context-sensitive-actions
How to Write an Action Listener
Action listeners are probably the easiest — and most common — event handlers to implement. You implement an action listener to define what should be done when an user performs certain operation.An action event occurs, whenever an action is performed by the user. Examples: When the user clicks a button, chooses a menu item, presses Enter in a text field. The result is that an
actionPerformed
message is sent to all action listeners that are registered on the relevant component.To write an Action Listener, follow the steps given below:
- Declare an event handler class and specify that the class either implements an ActionListener interface or extends a class that implements an ActionListener interface. For example:
public class MyClass implements ActionListener {- Register an instance of the event handler class as a listener on one or more components. For example:
someComponent.addActionListener(instanceOfMyClass);- Include code that implements the methods in listener interface. For example:
public void actionPerformed(ActionEvent e) {
...//code that reacts to the action...
}In general, to detect when the user clicks an onscreen button (or does the keyboard equivalent), a program must have an object that implements the ActionListener interface. The program must register this object as an action listener on the button (the event source), using the addActionListener method. When the user clicks the onscreen button, the button fires an action event. This results in the invocation of the action listener's actionPerformed method (the only method in the ActionListener interface). The single argument to the method is an ActionEvent object that gives information about the event and its source.
Let us write a simple program which displays how many number of times a button is clicked by the user. First, here is the code that sets up the TextField , button and numClicks variable:
public class AL extends Frame implements WindowListener,ActionListener {
TextField text = new TextField(20);
Button b;
private int numClicks = 0;In the above example, the event handler class is AL which implements ActionListener.
We would like to handle the button-click event, so we add an action listener to the button b as below:
b = new Button("Click me");
b.addActionListener(this);In the above code, Button b is a component upon which an instance of event handler class AL is registered.
Now, we want to display the text as to how many number of times a user clicked button. We can do this by writing the code as below:
Now, when the user clicks the Button b, the button fires an action event which invokes the action listener's actionPerformed method. Each time the user presses the button, numClicks variable is appended and the message is displayed in the text field.public void actionPerformed(ActionEvent e) {
numClicks++;
text.setText("Button Clicked " + numClicks + " times");Here is the complete program(AL.java):
More Examples:import java.awt.*;
import java.awt.event.*;
public class AL extends Frame implements WindowListener,ActionListener {
TextField text = new TextField(20);
Button b;
private int numClicks = 0;
public static void main(String[] args) {
AL myWindow = new AL("My first window");
myWindow.setSize(350,100);
myWindow.setVisible(true);
}
public AL(String title) {
super(title);
setLayout(new FlowLayout());
addWindowListener(this);
b = new Button("Click me");
add(b);
add(text);
b.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
numClicks++;
text.setText("Button Clicked " + numClicks + " times");
}
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
public void windowOpened(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
}Beeper
program example is available in this trail's introduction to events, Introduction to Event Listeners. You can find the entire program inBeeper.java
. The other example described in that section,MultiListener.java
, has two action sources and two action listeners, with one listener listening to both sources and the other listening to just one.
The Action Listener API
BecauseActionListener
has only one method, it has no corresponding adapter class.
Method Purpose actionPerformed(actionEvent) Called just after the user performs an action.
Method Purpose String getActionCommand() Returns the string associated with this action. Most objects that can fire action events support a method called setActionCommand
that lets you set this string.int getModifiers() Returns an integer representing the modifier keys the user was pressing when the action event occurred. You can use the ActionEvent
-defined constantsSHIFT_MASK
,CTRL_MASK
,META_MASK
, andALT_MASK
to determine which keys were pressed. For example, if the user Shift-selects a menu item, then the following expression is nonzero:actionEvent.getModifiers() & ActionEvent.SHIFT_MASK
Object getSource()
(injava.util.EventObject
)Returns the object that fired the event.
Examples that Use Action Listeners
The following table lists some of the many examples that use action listeners.Example | Where Described | Notes |
---|---|---|
Beeper | This section and Introduction to Event Listeners | Contains one button with one action listener that beeps when you click the button. |
MultiListener | Introduction to Event Listeners | Registers two different action listeners on one button. Also registers the same action listener on two different buttons. |
RadioButtonDemo | How to Use Radio Buttons | Registers the same action listener on five radio buttons. The listener uses the getActionCommand method to determine which radio button fired the event. |
MenuDemo | How to Use Menus | Shows how to listen for action events on menu items. |
TextDemo | How to Use Icons | Loads an image in an action listener. Because loading an image can take a while, this program uses a SwingWorker to load the image in a background thread. |
TableDialogEditDemo | How to Use Tables | Registers an action listener through a factory method on the OK button of a color chooser dialog. |
SliderDemo | How to Use Sliders | Registers an action listener on a timer that controls an animation loop. |
http://download-llnw.oracle.com/javase/tutorial/uiswing/events/actionlistener.html
NetBeans XML Editor Extension Module Tutorial
This tutorial demonstrates how to create a module that extends the functionality offered by one of the IDE's editors. The IDE has several editors—for example, the XML editor, the Java editor, the JSP editor, and the SQL editor. Normally all the IDE's editors are referred to collectively as the Source Editor. However, each of the editors is distinct—its functionality is targeted at the file type for which it exists. In this tutorial, you add an action to the XML editor. After you create and install the module, and you open an XML file, the editor's contextual menu will include a menu item that displays the XML file's tags in the Output Window.
Note: This is not the latest version of this document. It applies to NetBeans IDE 6.0/6.1 only. Click here to see the most up to date version.
Contents
Contents
To follow this tutorial, you need the software and resources listed in the following table.
Software or Resource | Version Required |
---|---|
NetBeans IDE | version version 6.1 or version 6.0 |
Java Developer Kit (JDK) | version 6 or version 5 |
Optionally, for troubleshooting purposes, you can download the completed sample.
Setting up the Module Project
Before you start writing the module, you have to make sure you that your project is set up correctly. The IDE provides a wizard that sets up all the basic files needed for a module.
Creating the Module Project
- Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Modules. Under projects, select Module Project and click Next.
- In the Name and Location panel, type ShowXMLStructure in Project Name. Change the Project Location to any directory on your computer, such as c:\mymodules. Leave the Standalone Module radiobutton selected. Select the Set as Main Project checkbox. Click Next.
- In the Basic Module Configuration panel, replace yourorghere in Code Name Base with myorg and changeShowXMLStructure to xmltree, so that the whole code name base is org.netbeans.modules.showxmlstructure. Add spaces the the default Module Display Name to change it to Show XML Structure. Leave the location of the localizing bundle and XML layer, so that they will be stored in a package with the nameorg/netbeans/modules/showxmlstructure. Click Finish.
The IDE creates the Show XML Structure project. The project contains all of your sources and project metadata, such as the project's Ant build script. The project opens in the IDE. You can view its logical structure in the Projects window (Ctrl-1) and its file structure in the Files window (Ctrl-2). For example, the Projects window should now look as follows:
Specifying the Module's Dependencies
You will need to subclass several classes that belong to NetBeans APIs. Each has to be declared as a module dependency. Use the Project Properties dialog box for this purpose.
- In the Projects window, right-click the Show XML Structure project and choose Properties.
- For each of the following APIs, click "Add..." in the Libraries panel, select the name from the Module list, and then click OK to confirm it:
- I/O APIs
- Nodes API
- Text API
- Utilities API
- Window System API
Click OK to exit the Project Properties dialog box.
- In the Projects window, double-click Project Metadata and note that the APIs you selected have been declared as module dependencies.
Coding the Module
Creating the Action
- Right-click the project node and choose New > Other. Under Categories, select NetBeans Module Development. Under Projects, select Action. Click Next.
- In the Action Type panel and click Conditionally Enabled. Select EditorCookie, which is the name of the class that lets the Source Editor access the action, as shown below:
Click Next.
- In the GUI Registration panel, select the 'Edit' category in the Category drop-down list. The Category drop-down list controls where an action is shown in the Keyboard Shortcuts editor in the IDE. Next, select Editor Contect Menu Item and then select the text/xml MIME type, as shown below:
Notice that you can set the position of the menu item and that you can separate the menu item from the item before it and after it. Click Next.
- In the Name and Location panel, type ShowXMLStructureAction as the Class Name and type Show XML Structureas the Display Name. Menu items provided by contextual menus do not display icons. Therefore, click Finish andShowXMLStructureAction.java is added to the package.
- In the Source Editor, add the following code to the performAction method:
public void performAction(Node[] activatedNodes) {
EditorCookie editorCookie = activatedNodes[0].getCookie(EditorCookie.class);
String tabName = NbBundle.getMessage(ShowXMLStructureAction.class,"LBL_tabName");
// "XML Tree" tab is created in output window for writing the list of tags
InputOutput io = IOProvider.getDefault().getIO(tabName,false);
io.select(); //XML Tree tab is selected
OutputWriter writer = io.getOut();
try {
writer.reset(); //clean the output window
java.io.InputStream is = ((org.openide.text.CloneableEditorSupport)editorCookie).getInputStream();
parse(new InputSource(is));
is.close();
for (int i=0;i<tags.length;i++) {
writer.println(tags[i]); //write tag to output window
}
} catch (IOException ex) {
} catch (SAXException ex){
writer.println("Parse Error: "+ex.getMessage());
}
writer.flush();
writer.close();
} - Add a new method:
/** Parses XML document and creates the list of tags
*/
private void parse(InputSource is) throws IOException, SAXException {
XMLReader xmlReader = XMLUtil.createXMLReader();
TagHandler handler = new TagHandler();
xmlReader.setContentHandler(handler);
xmlReader.parse(is);
tags = handler.getTags();
} - Declare the tags variable at the top of the source file:
private String[] tags;
- Add the display names to the Bundle.properties file:
ACT_name=Show XML Structure
LBL_tabName=XML Structure
Creating the Tag Handler
- In the Projects window, expand the project node, expand the Source Packages node, and then right-click theorg.netbeans.modules.showxmlstructure node. Choose New > Other. Under Categories, choose Java Classes. Under File Types, choose Java Class. Click Next and type TagHandler in Class Name. Click Finish. The new Java class opens in the Source Editor.
- Replace the default code with the code below:
package org.netbeans.modules.showxmlstructure;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/** Specific XML handler used for creating list of starting and ending tags, e.g. :
* <AAA>
* <BBB>
* <CCC/>
* </BBB>
* </AAA>
*/
public class TagHandler extends org.xml.sax.helpers.DefaultHandler {
private final int indentLength=2;
private java.util.List tagList;
private String indent;
private String space;
private String lastElement;
TagHandler() {
tagList = new java.util.ArrayList();
StringBuffer indentBuf = new StringBuffer();
for (int i=0;i<indentLength;i++) indentBuf.append(' ');
space=indentBuf.toString();
indent="";
}
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
tagList.add(indent+"<"+qName+">");
indent+=space;
lastElement=qName;
}
public void endElement(String uri, String localName, String qName) throws SAXException {
indent=indent.substring(indentLength);
if (qName.equals(lastElement)) {
int lastIndex = tagList.size()-1;
String lastInList = (String)tagList.get(lastIndex);
String replacedString = lastInList.replace(">","/>");
tagList.set(lastIndex,replacedString);
} else {
tagList.add(indent+"</"+qName+">");
}
lastElement=null;
}
String[] getTags() {
String[] tags = new String[tagList.size()];
tagList.toArray(tags);
return tags;
}
}
Building and Installing the Module
The IDE uses an Ant build script to build and install your module. The build script is created for you when you create the module project.
Installing the Module
In the Projects window, right-click the Show XML Structure project and choose Install/Reload in Target Platform.
The module is built and installed in the target IDE or Platform. The target IDE or Platform opens so that you can try out your new module. The default target IDE or Platform is the installation used by the current instance of the development IDE. Note that when you run your module, you will be using a temporary test user directory, not the development IDE's user directory.
Using the Module
- Choose File > New Project (Ctrl-Shift-N) and create a new project.
- In the Files window (Ctrl-2), expand the project node and then expand the nbproject node. Double-click build-impl.xml so that it opens in the Source Editor.
- Right-click anywhere in the Source Editor and notice the new popup menu item called "Show XML Structure": Choose the menu item and notice that the tag handler prints all the tags to the Output window, which is at at the bottom of the IDE, as shown below:
- Open a different file type in the Source Editor. For example, open a Java class. Right-click anywhere in the Source Editor and notice that the new popup menu item is not included in the contextual menu.
Creating a Shareable Module Binary
- In the Projects window, right-click the Show XML Structure project and choose Create NBM.
The NBM file is created and you can view it in the Files window (Ctrl-2):
- Make it available to others via, for example, e-mail.
Next Steps
For more information about creating and developing NetBeans modules, see the following resources:
Versioning
Version
Date
Changes
1
11 July 2005
Initial version
2
27 September 2005
- Added Action wizard
- Renamed from "NetBeans Tag Handler Plug-in Tutorial" to "NetBeans Source Editor Extension Module Tutorial".
- Added issue 7 below.
3
28 September 2005
- Renamed the tutorial, because 'Source Editor' doesn't cover the SQL editor, which could also be extended using the steps in this tutorial.
- Rewrote the introductory paragraph.
4
11 June 2007
Worked through whole tutorial, and cleaned up, for 6.0, also changed screenshots.
5
17 November 2007
Fixed spacing between steps. Tried out the attached sample, and it works as described.
Issue Number
Description
Status
1
Code and tutorial itself need to be reviewed.
To be fixed.
2
Tutorial needs to be updated once Phase III and IV are complete.
Done.
3
Some APIs used in this tutorial have deprecated methods. This will produce errors in the Output window, but should not impact functioning of module.
To be fixed.
4
Clear explanations -- and links to Javadoc -- to be added for all APIs, classes, and methods. Also Javadoc links for each of the dependencies and why they are needed in this tutorial.
To be fixed.
5
Maybe other identifiers for JSP editor, HTML editor, etc. should be mentioned. For example, instead of "xml" (in layer.xml), use "html", "x-properties", "base" etc.
To be fixed.
6
Explain what a cookie is. Explain what a cookie action is.
To be fixed.
7
Need to change downloadable, because currently the tag handler and the show XML action are separate files while in the downloadable code, they're in the same file. For the same reason, must change screenshots where one file instead of two are shown.
To be fixed.
http://platform.netbeans.org/tutorials/60/nbm-xmleditor.html