Samstag, 19. Januar 2013

Using Swing in a Bundle

If a bundle shall provide a user interface, it could create a JFrame, add some components and present it on screen. (For instance assume you built some home automation and a fridge monitoring bundle shall present some feedback when the temperature is too high.)

Create and Dispose the JFrame

One approach is to create the JFrame when the bundle's start method is called. When the bundle is stopped, it should clean up and thus dispose the JFrame. Bundles may have an "Activator" which is an implementation of the BundleActivator interface. The interface defines a start method - which is called by the framework when the bundle is started - and a stop method - which is called by the framework when the bundle is stopped.

The OSGi snippets project contains a "bundleswing1" project which contains exactly one class:

public class Activator implements BundleActivator { Logger logger = LoggerFactory.getLogger(Activator.class); volatile JFrame frame; @Override public void start(BundleContext context) throws Exception {"Swing1 Bundle: start()"); // All access to Swing must be on the EDT. This start method // may have been called outside the EDT, so the JFrame // creation must be put into the EDT. And this is how // it is done: SwingUtilities.invokeLater(new Runnable() { @Override public void run() { frame = new JFrame("Swing1 Bundle"); Container contentPane = frame.getContentPane(); contentPane.setLayout( new BoxLayout(contentPane, BoxLayout.PAGE_AXIS)); contentPane.add(new JLabel( "JFrame created in Activator.start(), stop bundle to dispose")); contentPane.add(new JButton(new HelloAction())); contentPane.add(new JButton(new HelloSharedOwnerAction())); frame.setSize(300, 200); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } @Override public void stop(BundleContext context) throws Exception {"Swing1 Bundle: stop()"); // Access Swing in EDT - see comment in start method: SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (frame!=null) { frame.dispose(); frame = null; } } }); } ... (two action classes) }

In the guitool, start the framework, install the bundle file named "bundleswing1" from the files list and start it in the bundles list. (Bundle files are on the right hand side and bundles are on the left hand side in the guitool scteen. Bundles appear when bundle files are installed.)

The bundle starts and a JFrame is shown:

Pressing the "Stop" button in the guitool stops the bundle and closes the JFrame.

To use Swing classes, they must be available to the bundle, otherwise a ClassNotFoundException is thrown. The bundle has a MANIFEST file which among other things contains these two lines:

Bundle-Activator: example.swing1.impl.Activator Import-Package: org.osgi.framework, org.slf4j, javax.swing

The entry Bundle-Activator defines the class which is called when the bundle shall be started or stopped. The entry Import-Package defines the java packages which shall be available to the bundle which in this case must include javax.swing.

Playing with Import-Package

As a little exercise, you may want to change the Import-Package entry and see what happens. The build scripts create the Import-Package entry from information stored in the file in the project. This file is in the root of project bundleswing1. In the line prj.bundle.import-package=org.osgi.framework, org.slf4j, javax.swing remove the javax.swing package and save the file. Then build the bundle by either running build-bundles.xml or the project's build.xml script. Install the bundle and start it. The bundle information will contain "Activator start error in bundle" and the Eclipse console will show " java.lang.ClassNotFoundException: javax.swing.JLabel not found by example.swing1 [2]".

The Shared Owner Frame

When creating a JDialog with a null owner, Swing creates a so called "shared owner frame". This will prevent a clean exit, when - as usual - System.exit shall not be used.

The JFrame contains two buttons. The first refers to an Action which uses JOptionPane.createDialog with the main JFrame as owner. As a test, do these steps:

  1. Start guitool
  2. Start Framework
  3. Install and start bundleswing1
  4. Press the "Hello" button (a message box will apear - do not close it!)
  5. Stop the bundle
  6. Exit guitool
  7. The application will terminate and the JVM exits. This is the expected behaviour.

The class HelloSharedOwnerAction looks like shown below. Interesting are the lines marked with bold font. When passing null to createDialog, Swing sets a Shared Owner Frame as parent of the JDialog. The two bold lines in the propertyChange method fetch the owner and dispose it.

class HelloSharedOwnerAction extends AbstractAction { private static final long serialVersionUID = 1L; public HelloSharedOwnerAction() { super(); putValue(NAME, "Hello Shared Owner"); } @Override public void actionPerformed(ActionEvent e) { JOptionPane p = new JOptionPane( "Hello there! My owner is the Shared Owner Frame"); final JDialog dlg = p.createDialog(null, "Hello"); dlg.setModalityType(ModalityType.MODELESS); p.addPropertyChangeListener("value", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { dlg.dispose(); Window parent = dlg.getOwner(); parent.dispose(); } }); dlg.setVisible(true); } }

As a test, comment these two lines out (the ones around parent.dispose()) and repeat the steps listed above, except that the button "Hello Shared Owner" is used. The guitool will disappear when pressing "Exit", but the JVM will not terminate. It does so, because a displayable component still exists. The two commented lines were disposing that component.


Keine Kommentare:

Kommentar veröffentlichen