In this example we are going to use Substance Look And Feel within a JFrame that is also an OSGi component.
Here's the setup (again, I use iPojo for all my components/services within the Felix implementation of OSGi):
Here's a @Component JFrame. It's a little different than the standard main() created by Visual Editor within Eclipse or Jigloo. Instead of calling initialize() where I build all the visual components of my JFrame I call start() which is also the entry to building when I'm running within OSGi.
@Component
public class Example extends JFrame {
public Example() {
super();
}
public Example(BundleContext bundleContext) {
super();
context = bundleContext;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Example thisClass = new Example();
thisClass.start();
thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
thisClass.setVisible(true);
}
});
}
...
The start() method looks pretty standard now. I really don't want to shut down Felix or anything when this component is stopped, so I set the close operation appropriately. When the component is stopped I just want to get rid of the JFrame.
public void start() {
try {
UIManager
.setLookAndFeel( "org.jvnet.substance.skin.SubstanceMistAquaLookAndFeel");
SwingUtilities.updateComponentTreeUI(this);
} catch (Exception e) {
System.out.println("Substance Mist Aqua failed to initialize...");
e.printStackTrace();
}
initialize();
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setVisible(true);
}
@Invalidate
public void stop() {
setVisible(false);
dispose();
}
Now for the secret sauce. This comes directly from my previous post. start() is now called from the @Validate function. Note that the method is called osgiValidate() instead of something like validate() as it is already used within java.awt.Container which JFrame inherits from four levels deep. We don't want to override that function.
The @Validate function gets the OsgiEnvironmentClassLoader and calls start().
protected OsgiEnvironmentClassLoader getOsgiEnironmentClassLoader(
BundleContext bundleContext) {
return new OsgiEnvironmentClassLoader(bundleContext, Thread
.currentThread().getContextClassLoader(), bundleContext
.getBundle());
}
@Validate
public void osgiValidate() {
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new ClassLoaderContext(getOsgiEnironmentClassLoader(context))
.execute(new ClassLoaderContextCallback() {
public void doAction() {
start();
}
});
}
});
}
In my case this produces something like this:
If you want the window decorated as well then you need to add a little static initializer:
static {
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
}
Then you get this:
As a little side note, if you wish to shutdown your entire OSGi instance from your swing UI you can use the BundleContext:
Bundle bundle = context.getBundle(0);
bundle.stop();
Some people may ask, "Why use this classloader work around instead of setting the org.osgi.framework.bootdelegation to include all the needed classes?" My answer... I'm not really sure that this way here is any more correct. They are both work-arounds for cases where 3rd party libraries are loading dependencies under the scenes... against what OSGi is trying to accomplish. I like this way because it feels better, not because it is better.


I think it's a great post, but I would like to get the source code of your project, I don't know if you can send it to my email (hector@ahoraresulta.com)
ReplyDeleteThanks!
Here's is my problem:
ReplyDeleteI want to have a swing application that dynamically looks for look and feels as services. On service registered event the application get the service which can return an instance of the JMenu containing all the newly installed look and feels as menu items.
The application also register action handler for the lnf menu items.
Then handler would get the actionCommand from the source of event containing the look and feel.
Then the handler sets the look and feel as
UIManger.setLookAndFeelName(((JMenuItem)actionEvent.getSource()).getActionCommand()).
But the application cannot load the look and feel.
Please give me a workaround for this.
If you could please tell me about swing class loader for look and feels