Lab-5540: Save Time—Build Desktop Applications Quickly with the NetBeans Platform

Expected Duration: 100 minutes

Exercise 2: Extend the NetBeans Platform application using NetBeans IDE (25 minutes)

 

In this exercise we will learn about the basics of building applications using the NetBeans Platform, visualizing application data in a tree-like component, registering your own actions into NetBeans Options framework and else notable skeletons.

We've learned how to brand a NetBeans Platform application to our business needs. We know how to port a Swing Application to the NetBeans Platform. Next, we would like to extend it with a new features.


Background Information

 

Visualizing data

 

There are many ways of visualizing data in a NetBeans Platform application. One of the options is to show your data in a tree-like structure with nodes showing values. With the NetBeans Platform, you can use the Nodes API to create your tree-like structure and Explorer & Property Sheet API to show it in the application.
To manipulate the data you'll need some actions. That's where the Actions API comes to play. The Actions API is widely used in NetBeans Platform in many other places.

List of used APIs
  • Actions API
    The Actions API allows users to interact with module code, by invoking actions via menus, toolbars or keyboard shortcuts. You do not need to use any special interfaces - just create a javax.swing.Action and register it.
  • Nodes API
    The Nodes API controls the usage and creation of nodes, which are a variant of JavaBeans that may have adjustable property sets; provide interfaces to other capabilities and actions; be visually displayed in the Explorer with full hierarchy support; and other features.
  • Explorer API
    Explorer views are UI components which render nodes - the most prominent in NetBeans being the Project and Files tabs on the left side of the main window. The Explorer API provides UI components to render nodes in trees, lists, combo boxes, menus, tree tables. These components handle things like cut/copy/paste, drag and drop, displaying popup menus for nodes, which contain the actions the clicked node provides from getActions().
  • Preferences API
    NetBeans uses standard Java Preferences API as a way for NetBeans modules to store and retrieve user and system preference, configuration data.

Steps to Follow

 
Step 1: Register a new action
  1. The Anagram application can start a new Anagram Game on each launch of application. But it's not enough. We would like to allow users to start more games then only once. To do so, we need a new action which opens a new game window and start an new instance of Anagram game.

    NetBeans Platform support offers a template for creating new action in NetBeans module. Invoke File->New File (Crtl-N) or from a pop-up on the org.javaone.anagram sources package. Select Action in Module Development category.

  2. On the next panel choose a Always enabled option. It will create action that will be available all the time. The other option "context-sensitive" action would create action that is enabled only under some circumstances. For example when you select a node in Explorer or when a particular file is opened in Editor.

    1. Type Anagram into Category field.
    2. Check Global Menu Item and choose File menu item.
    3. Check Separator After option.



  3. Click Next button.
  4. On the last panel set prefix of action name, e.g. NewGame and then set a display name of this action in the menu bar, e.g.&New Game.
    NetBeans Platform allows to specify mnemonics of menu items, buttons, labels, etc. by the & character before the character of the mnemonic. It means that in our case you can invoke the New Game action by N in menu.



  5. Click Finish button.
  6. Open the class NewGame in editor. There is generated a method actionPerformed() which will be called on each invocation of File->&New Game in the menu bar.

    The method actionPerformed() should open a next window in the Editor area like the initial Window Component does. Only descendants of org.openide.windows.TopComponent are allowed to be placed in NetBeans Window System. Thus we need to create new instance of TopComponent and then open it in the Editor mode.

         public void actionPerformed(ActionEvent e) {
             Mode editorMode = WindowManager.getDefault ().findMode ("editor");
             assert editorMode != null && WindowManager.getDefault ().isEditorMode (editorMode);
             TopComponent tc = new GameTopComponent (new Anagrams ());
             tc.open();
             tc.requestActive();
         }
    
         private class GameTopComponent extends TopComponent {
             private Anagrams game;
             public GameTopComponent (Anagrams game) {
                 initComponents ();
                 this.game = game;
                 add (game.getContentPane (), BorderLayout.CENTER);
             }
             private void initComponents() {
                 this.setLayout (new BorderLayout ());
             }
    
             @Override
             public String getDisplayName() {
                 return "Anagram Game";
             }
    
             @Override
             public int getPersistenceType () {
                 return PERSISTENCE_NEVER;
             }
    
             @Override
             protected String preferredID () {
                 return Integer.toString (System.identityHashCode (game));
             }
             
         }
         
                                              

    Implementation of New Game Action allows users of Anagram application to start another Anagram games when one is finished.

  7. Fix Imports. Mode should be org.openide.windows.Mode



  8. Step 2: Add a Save Score Button
    1. Open Anagrams class from org.javaone.anagram.ui package. You can see that there is Save Score button. We made it invisible. Go to source code and comment out following lines of code in Anagrams constructor
           public Anagrams() {
               wordLibrary = WordLibrary.getDefault();
               passed = 0;
               timer = new Timer ();
               startTime = System.currentTimeMillis ();
               initComponents();
           //        saveScore.setVisible(false);
           //        saveScore.setEnabled(false);
               lScoreTitle.setText("Time: ");
               lScore.setForeground(Color.green);
               timer.schedule (new TimerTask () {
                                                        
    2. We will use Dialogs API to show simple dialog to user. User can then provide name that will be saved into high scores list that we will create at the end of the exercise.
      Furthermore, we will use Preferences API for for storing the high scores in module's preferences storage. There we can store and then load back any primitive data or Strings. It will make them persistent across restarts.
      Use NbPreferences.forModule(Anagrams.class) to acquire a storage for storing scores. On this storage we will call both methods getInt, setInt for storing and loading score values.

      Copy and paste following code into saveScoreActionPerformed(...) method in Anagrams.java class. The saveScoreActionPerformed method handles the action binded to the button.
           private void saveScoreActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveScoreActionPerformed
               // setup the text of the dialog
               NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine ("What is your name?", "Save Score");
               nd.setInputText(System.getProperty("user.name"));
               DialogDisplayer.getDefault().notify(nd);
               if (NotifyDescriptor.CANCEL_OPTION.equals(nd.getValue())) {
                   // no name
                   return;
               }
               saveScore.setEnabled(false);
               String name = nd.getInputText();
               Preferences pref = NbPreferences.forModule(Anagrams.class);
               int best = pref.getInt(name, -1);
               if (best > passed) {
                   NotifyDescriptor.Confirmation nd2 = new NotifyDescriptor.Confirmation(name + 
                          " achieved a better score " + best +
                          ". Overwrite?", "Overwrite best score");
                   DialogDisplayer.getDefault().notify(nd2);
                   if (NotifyDescriptor.YES_OPTION.equals(nd2.getValue())) {
                       pref.putInt(name, passed);
                   }
               } else {
                   pref.putInt(name, passed);
               }
               lScore.setText(name + " has the best score " + pref.getInt(name, 0));
           }
                                                    
    3. You will have to call Source > Fix Imports (Ctrl+Shift+I). It won't fix all the compilations errors because some of the calls need other NetBeans Platform libraries that provide these APIs and their functionality. The API that we need is Dialogs API.
    4. How to add new library into module's libraries? Either go to project properties select Libraries and then press the Add Dependency... button or select Add Dependency... from popup on Libraries node under your module's project. You can type name of an class in a library or the library name to find it easier in the list of APIs.

    5. Press OK will add the dependency to the Dialogs API and you can finish the Fix Imports in Anagrams class.


    Step 3: Visualize Best Scores

    1. Create new always-enabled action with display name &Show Score in class org.javaone.anagram.ShowScore class. Follow the same steps as you created the New Game action.
    2. The Show Score action creates and opens new TopComponent with list of saved scores in "explorer" area when the actionPerformed method is called. Add following code to the actionPerformed method.
    3. Find the "explorer" mode. It is on left side of application.
           public void actionPerformed(ActionEvent e) {
               Mode explorerMode = WindowManager.getDefault ().findMode("explorer");
           }
                                                    
    4. Then create new ExplorerTopComponent and dock it into the explorer's mode.
           public void actionPerformed(ActionEvent e) {
               Mode explorerMode = WindowManager.getDefault ().findMode("explorer");
               TopComponent tc = new ExplorerTopComponent ();
               explorerMode.dockInto(tc);
           }
                                                    
    5. And now we open the TopComponent and request the focus for it. So at the end your actionPerform method will look like
           public void actionPerformed(ActionEvent e) {
               Mode explorerMode = WindowManager.getDefault ().findMode("explorer");
               TopComponent tc = new ExplorerTopComponent ();
               explorerMode.dockInto(tc);
               tc.open();
               tc.requestActive();
           }
                                                    
    6. Now we will add the content to the TopComponent that we opened with the Show Score action.

      As was said before, let's use Explorer API to visualize best scores in views. At first, we need to implement ExplorerManager.Provider which provides an Explorer Manager. It is a class that manages a selection and root context for a set of explorer views

      The next step is building nodes, nodes are based on best scores achieved in the Anagram Game. The root node is a simple ″Best Scores″ root node. This node has own children, each child is one of best scores.
      It means, we will implement AbstractNode and Children.Keys.

      Copy and paste following code into the ShowScore class.
       public final class ShowScore implements ActionListener {
      
           public void actionPerformed(ActionEvent e) {
               Mode explorerMode = WindowManager.getDefault ().findMode("explorer");
               TopComponent tc = new ExplorerTopComponent ();
               explorerMode.dockInto(tc);
               tc.open();
               tc.requestActive();
           }
      
           private class ExplorerTopComponent extends TopComponent implements ExplorerManager.Provider {
               private ExplorerManager em;
      
               public ExplorerTopComponent () {
                   initComponents ();
                   em = new ExplorerManager();
                   em.setRootContext(new BestScoresNode());
               }
      
               private void initComponents() {
                   this.setLayout (new BorderLayout ());
                   TreeView tv = new BeanTreeView ();
                   tv.setRootVisible(true);
                   add (tv, BorderLayout.CENTER);
               }
      
               @Override
               public String getDisplayName() {
                   return "Best Scores";
               }
      
               @Override
               public int getPersistenceType () {
                   return PERSISTENCE_NEVER;
               }
      
               public ExplorerManager getExplorerManager() {
                   return em;
               }
      
               @Override
               protected String preferredID () {
                   return Integer.toString (System.identityHashCode(em));
               }
           }
      
           private class BestScoresNode extends AbstractNode {
               private String displayName;
               public BestScoresNode(String name, Children ch) {
                   super(ch);
                   this.displayName = name;
               }
      
               public BestScoresNode(String name, int score, Children ch) {
                   super(ch);
                   this.displayName = name + " has the best score " + score;
               }
      
               public BestScoresNode() {
                   super(new BestScoresChildren());
                   this.displayName = "Best Scores";
               }
      
               @Override
               public String getDisplayName() {
                   return displayName;
               }
      
           }
      
           private class BestScoresChildren extends Children.Keys<String> {
               private Preferences pref;
               public BestScoresChildren () {
                   try {
                       pref = NbPreferences.forModule(Anagrams.class);
                       String[] keys = pref.keys();
                       if (keys != null && keys.length > 0) {
                           setKeys(keys);
                       } else {
                           setKeys(Collections.singleton(""));
                       }
                   } catch (BackingStoreException ex) {
                       Exceptions.printStackTrace(ex);
                   }
               }
      
               @Override
               protected Node[] createNodes(String key) {
                   if (key.length() == 0) {
                       return new Node[] { new BestScoresNode("No scores", Children.LEAF) };
                   } else {
                       return new Node[] { new BestScoresNode(key, pref.getInt(key, -1), Children.LEAF) };
                   }
               }
           }
      
       }
                                                    
    7. To fix all the imports and dependencies you'll have to add another two libraries with the used APIs.
      Add Dependency on Nodes API and Explorer & Property Sheet API. You should have following libraries under Libraries node of your module.
    8. Fix Imports in the SaveScore class.
      • Children - org.openide.nodes.Children
      • Mode - org.openide.windows.Mode
      • Nodes - org.openide.nodes.Node

Summary

In this exercise we enhanced the simple functionality of the Anagram Game with new actions that makes the game more usable and interesting. We created new action that allows to run more games. User can save their best scores too. And the best scores are visualized in nice tree-like structure. All these new features benefited from the NetBeans APIs.

 

Back to top
Next exercise