Some time ago I wrote about a quick experiment I made to figure out if Fuse could be used with Griffon. The results were promising however there were some implementation aspects that I didn't like too much
- Instances participating in injection had to be tracked explicitly. A custom Injector wrapper had to be built as a helper.
- The custom injector had a hard dependency on Swing. That wasn't much of a problem at the time as Griffon only supported Swing, but now things are different as Griffon supports a good number of UI toolkits.
- Fuse supports Swing and SWT but nothing more. Furthermore, resource conversion relied on custom converters that are tied to the Fuse API.
Months passed by then Griffon 1.1.0 was released. This particular version delivers a feature that can be used to replicate Fuse's behavior: resource injection. However there was still a missing piece of the puzzle, mainly how to perform proper cleanup when a managed instance gets disposed. This is where Griffon 1.2.0 comes into play thanks to GRIFFON-547.
Sorting out the last hurdle made possible to build the theme plugin. This plugin delivers the same behavior as Fuse while providing additional benefits
- Instances participating in injection can be tracked automatically if their class is annotated with @griffon.plugins.theme.ThemeAware. If they do not then injection will be performed once during application bootstrap.
- Theme injection is UI toolkit agnostic, it works with all currently supported toolkits and future ones.
- Resource conversion is carried out via java.beans.PropertyEditor, no need for another conversion API.
- Theme definition files may use the groovy format not just properties files.
The following screenshots show a re-implementation of the FuseDemo application using Griffon 1.2.0 and the theme plugin. Full source code is available here.
black theme
red theme
Let's decompose the application piece by piece. At the heart of the application we find custom UI components that leverage resource injection. One such component is ContentPanel, shown next
This is pretty much a carbon copy from FuseDemo except for two things:
- Use of @griffon.core.resources.InjectedResource instead of @org.jdesktop.fuse.InjectedResource.
- The class is annotated with @griffon.plugins.theme.ThemeAware.
All other UI components (HeaderPanel, TitleLabel, FooterPanel) had the same changes applied to them. Next is the View, where all these components get organized.
Notice that our custom components get instantiated using custom nodes. The reason for that is that we must somehow inform the Griffon runtime that we'd like those instances to participate in resource injection; invoking new ContentPanel() will simply not do. So we hook a custom factory that performs the job
The factory uses a standard facility provided by the application for instantiating classes. The theme addon reacts to events triggered by invocations of the newInstance() method, this is how it can determine if an instance must participate in resource injection. Ok, so we have a custom node factory, how do we hook it up? There are a couple of options, perhaps the most elegant is to use a neat feature called inplace addon, which allows you to define factories in Builder.groovy as it's shown next
More information about inplace addons can be found in Chapter 12 of Griffon in Action. With these definitions in place we can now make use of contentPanel on any View script. Finally we visit the Controller, whose sole responsibility is to change the theme whenever the Black or Red theme buttons are pressed. Because we only have 2 theme definitions the implementation is quite straight forward
That's pretty much it. Switching the currentTheme property found in ThemeManager does the trick. All tracked instances will have their injected resources refreshed with values coming from the selected theme.
Keep on Groovying!