create a dynamically loadable application

Intro

The entrypoint of a GUI-application is SRStarter, which is part of SRJRCFrames and is the same for all applications. You "only" need to create the loadable part of the application. From a thechnical point of view this means, that you won't create a root-frame window, but use the space offered by the application frame SRStarter. In the sense of MVC your application will be the C - the controller. The root window of the application is of type MainFrame and you should make no assumptions about the JFC-type of that window. Depending on the active window manager, that the user has choosen, it could be an JFrame, an JInternalFrame, or simply an JPanel.

application class

To ease the creation of an application class, the framework offers the base class AbstractApplication, wich implements the interface Application. The "M" of MVC stands for model (the data-container) and the data of an application is its configuration data. Such a configuration class should be derived from ApplicationConfig, so the saving and restoring of size and position of the application window works out of the box. The said leads to this definition

public class SampleApp extends AbstractApplication<SampleCfg> {
} 

The default configuration of the application will be provided by a context-definition you write (see Configuration) and the application frame SRStarter will overwrite those settings with the values read from the users environment. Of cause, this will be performed before giving control to the instance of your application.

Ingredients of a minimalistic application

Let's look a bit closer at each point ...

menue and toolbar

To give all windows and icons of all applications the same look, neither the menue, nor the toolbar will be defined by using Actions. Instead they will be created by Enum-entries, which will be interpreted by an AbstractActionHandler.

private enum Command {
   FILE_NEW, SEP_FILE, FILE_OPEN,
   EDIT_COPY, EDIT_RELEASE, SEP_EDIT, EDIT_DELETE,
   VIEW_REFRESH
} 

As the naming suggests, there's some logic in it. The actionhandler splits the Enum-names at the "_" so the FILE_NEW results in a menue entry "New" in the menuepage "File", SEP_EDIT will create a separator in the Edit menue. All menue entries will be substituted by the text found by a messageSource using the menueentries name as key. (Find more information at Configuration).

The application frame SRStarter adds some menue entries by its own. That includes an action to close all applications, an action to switch to another applications window, to open the common preferences editor or to start the browser for the help pages.

methods for menue-entries

There are no restrictions on the menue callbacks, so any method is valid for menue usage.

To get a method executed at selection of a menue entry, both need to marry - that's what's AbstractActionHandler is for. Additionally it takes care, that all entries follow the same look and feel and checks, whether the user is allowed to execute that method (currently the interface is implemented, but the restrictions are not active yet). The action handler caches the menue-entries, so it can be asked to retrieve given action.

class SampleActionHandler extends AbstractActionHandler
                          implements ApplicationActionHandler {
   public SampleActionHandler(String name) {
      super(name);
   }


   @Override
   public void init() {
      setupAction(getName(), Command.FILE_NEW, new AbstractActionCallback() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            doCreateSample(ae);
         }
      }, KeyEvent.VK_N
       , AccessMode.APP_READ);

       ...
   }


   public Enum[] getCommands() {
      return Command.values();
   }


   public Enum[] getToolBarCommands() {
      return Command.values();
   }
}
   

The constructor has a name-parameter just to know for whom it is working for.

init is used to attach menue entry to application function. The simplest function is one without parameters. Those who like to differ work based on the menue-entries origin, could pass the ActionEvent to the method.

The methods getCommands and getToolBarCommands can be used to use different subsets of Command for menue and toolbar.

To support context menues, there's a variant of init, that takes a context object as parameter:

@Override
public void init(Object contextObj) {
   setupAction(getName(),
               Command.FILE_OPEN,
               new AbstractActionContextCallback(contextObj) {
                   @Override
                   public void actionPerformed(ActionEvent ae) {
                      doEditSample(new ActionContextEvent(ae, getContext()));
                   }
               },
               KeyEvent.VK_O);
    ...
}
   

For this usecase there's an extended callback-class AbstractActionContextCallback. The constructor of that class will receive the context object, wich is accessible by their methods. Finally the ActionHandler needs to get registered to play its role in the game. The constructor of your application class is a good place for that:

public SampleApp() {
   super(SampleApp.class.getSimpleName());
   setActionHandler(new SampleActionHandler(getName()));
}
   

window content

A GUI application without window content doesn't make much sense. As we don't know, whether we would like to change that content one day, we don't code it in the application, but use another class:

@Override
protected JComponent createPane() {
    if (content == null) {
       view = new AddressTableView(getInitialData(),
                                   EventSelectionModel.SINGLE_SELECTION,
                                   null);
       view.setSelectionChangedExecutor(new AbstractAction() {
          private static final long serialVersionUID = 1L;
             
             
          public void actionPerformed(ActionEvent e) {
             selectionChanged(e);
          }
       });
       view.setPopDoubleLeftExecutor(new AbstractAction() {
          private static final long serialVersionUID = 1L;
             
             
          public void actionPerformed(ActionEvent e) {
             popupDoubleLeft(e);
          }
       });
       view.setPopSingleRightExecutor(new AbstractAction() {
          private static final long serialVersionUID = 1L;
             
             
          public void actionPerformed(ActionEvent e) {
             popupSingleRight(e);
          }
       });
       content = new ApplicationPage(getName(), view.getView());
    }
    return content;
} 

The instance of AddressTableView is the "V" of the MVC pattern, which means "view". Now our MVC-application is complete.

The class AddressTableView will be explained at the page Overview. The Overview shows a list of addresses. The (main-)functions, that respond to user actions are part of the controller (our application), so they get registerd at the view here.

Statusbar

The Statusbar is responsable for simple messages, that don't force user interaction. The statusbar will be shown below the application content and consists in the simplest form of an messagepart only.

The messagepart of all Statusbar-instances will be synchronized, so a message will be shown at all application windows. A simple Statusbar may be declared like this:

public class SampleStatusBar extends AbstractStatusBar {
   private static final long serialVersionUID = 1L;
    
    
   @Override
   protected void checkExtends() {}
    
    
   @Override
   protected void updateExtends() {}
} 

Synchronization

is a major design goal of SRJRCFrames, so you need a listener for the events from other applications. If you don't care about others, leave that methodbody empty ...

@Override
public void onApplicationEvent(ApplicationEvent arg0) {
   // do nothing
} 

The counterpart of the listener is the tossing of events. There's an application service for that purpose:

if (aePublisher == null) {
   aePublisher = (ApplicationEventPublisher) 
                 ApplicationServiceProvider.getService(ApplicationEventPublisher.class);
} 

When we have an instance of that service, we could inform others of our existance:

aePublisher.publishEvent(new SampleAppStartedEvent("hello world")); 

preferences editor page

If the user selects the menue-entry "Preferences", the application frame SRStarter asks every loaded application for an edtior page and assembles a preferences dialog from that pages. The application frame adds standardpages for common use and shows all pages in a tabbed view - so each page can be randomly activated. To offer such an editor page, ...

public JComponent getConfigPage() {
   if (cfgEdit == null)
      cfgEdit = new ConfigEditor(getAppConfig());

   return cfgEdit;
} 

If your application does not have any userlevel properties, your function may return null. Otherwise you should create an editor for your applications config instance, in our sample app its SampleCfg. Such an editor could look like:

class ConfigEditor extends AbstractEditor<SampleCfg> {
   private static final long serialVersionUID = 1L;


   public ConfigEditor(SampleCfg instance) {
       super(instance, false);
   }


   @Override
   protected JComponent buildPanel() {
      if (msgSource == null)
         msgSource = ApplicationServiceProvider.getService(MessageSource.class);
      FormLayout layout = new FormLayout("left:max(100dlu;pref), 3dlu, 100dlu:grow");
      DefaultFormBuilder builder = new DefaultFormBuilder(layout);
      String prefix = SampleCfg.class.getSimpleName() + ".";
      PresentationModel pm = getModel();

      builder.setDefaultDialogBorder();
      builder.append(msgSource.getMessage(prefix + "userProperty.label",
                                          null,
                                          prefix + "userProperty.label",
                                          null),
                     componentFactory.createTextField(pm.getBufferedModel("userProperty")));

       ...

      return builder.getPanel();
   }
} 

buildPanel must return a JComponent - but the GUI-elements need to be attached to your configuration properties, that's where JGoodies-comes into play. The entity-instance is already wrapped by a PresentationModel, so its easy now, to ask for a buffered model with PresentationModel.getBufferedModel("attribute-Name"). Your editor does not need to care about buttons like "Ok" or "Cancel", nor what to do on these events. The preferences dialog of the application frame already does it all for you.

Summary

Your sample application will now look like this:

public class SampleApp extends AbstractApplication<SampleCfg> {
   private enum Command {
      FILE_NEW, SEP_FILE, FILE_OPEN,
      EDIT_COPY, EDIT_RELEASE, SEP_EDIT, EDIT_DELETE,
      VIEW_REFRESH
   }
   class SampleActionHandler extends AbstractActionHandler
                             implements ApplicationActionHandler {
      public SampleActionHandler(String name) {
         super(name);
      }


      @Override
      public void init() {
         setupAction(getName(), Command.FILE_NEW, new AbstractActionCallback() {
             @Override
             public void actionPerformed(ActionEvent ae) {
                 doCreateSample(ae);
             }
         }, KeyEvent.VK_N
         , AccessMode.APP_READ);
         ...
      }


      @Override
      public void init(Object contextObj) {
         setupAction(getName(),
                     Command.FILE_OPEN,
                     new AbstractActionContextCallback(contextObj) {
                         @Override
                         public void actionPerformed(ActionEvent ae) {
                             doEditSample(new ActionContextEvent(ae, getContext()));
                         }
                     },
                     KeyEvent.VK_O);
         ...
      }


      public Enum[] getCommands() {
         return Command.values();
      }


      public Enum[] getToolBarCommands() {
         return Command.values();
      }
   }
   public class SampleStatusBar extends AbstractStatusBar {
      private static final long serialVersionUID = 1L;


      @Override
      protected void checkExtends() {}


      @Override
      protected void updateExtends() {}
   }


   public SampleApp() {
      super(SampleApp.class.getSimpleName());
      setActionHandler(new SampleActionHandler(getName()));
   }


   @Override
   public void onApplicationEvent(ApplicationEvent arg0) {
      // do nothing
   }


   public void selectionChanged(ActionEvent ae) {
      ...
   }


   public void popupDoubleLeft(ActionEvent ae) {
      ...
   }


   public void popupSingleRight(ActionEvent ae) {
      ...
   }


   @Override
   protected JComponent createPane() {
      if (content == null) {
         view = new AddressTableView(getInitialData(),
                                     EventSelectionModel.SINGLE_SELECTION,
                                     null);
         view.setSelectionChangedExecutor(new AbstractAction() {
            private static final long serialVersionUID = 713L;


            public void actionPerformed(ActionEvent e) {
               selectionChanged(e);
            }
         });
         view.setPopDoubleLeftExecutor(new AbstractAction() {
            private static final long serialVersionUID = 713L;


            public void actionPerformed(ActionEvent e) {
               popupDoubleLeft(e);
            }
         });
         view.setPopSingleRightExecutor(new AbstractAction() {
            private static final long serialVersionUID = 713L;


            public void actionPerformed(ActionEvent e) {
               popupSingleRight(e);
            }
         });
         content = new ApplicationPage(getName(), view.getView());
      }
      return content;
   }


   protected void doCreateSample(ActionEvent ae) {
      ...
   }


   protected void doEditSample(ActionEvent ae) {
      ...
   }
}