Matthew is a DZone employee and has posted 155 posts at DZone. You can read more from them at their website. View Full User Profile

Easier Custom Components with Swing Fuse

01.10.2008
| 2442 views |
  • submit to reddit

I was sifting through my personal Eclipse workspace, and it occurred to me that I haven't written any real articles about Fuse yet.  I've thoroughly canvassed ActiveObjects, but I seem to have completely overlooked this clever little framework for Swing and SWT applications.

Fuse 101

Swing Fuse (note: the term "Swing Fuse" is my naming, so as to differentiate the project from AppFuse, F.U.S.E. and other similarly named projects) is the brainchild of notorious Swing expert, Romain Guy.  Basically, he was working on the UI for the Aerith demo for JavaOne when he began to notice a certain gap in his toolset.  He was facing the problem of tweaking the colors, gradients, dimensions, images and overall style of the application, making tiny changes over and over again. 

Anyone who's developed any sort of rich UI application will attest to how annoying this can be.  Usually, changing a single style involves making subtle changes in dozens of classes, so as to keep the UI consistent.  Not only that, but it's a major pain to have to dig out the code and find the precise initialization every time you want to change #0f0f0f to #101010. 

Now this problem is very similar to one faced by developers in the "enterprise world" countless times when dealing with dependencies.  A single interface might have dozens of different implementations, each doing things slightly differently.  Going through and changing the constructor calls in hundreds, sometimes thousands of classes can be a major bottleneck in the development process.  Of course, the solution to this problem is dependency injection, first provided by the Spring library, and now more sanely implemented using Guice.  This really seems to be on the right track for solving our resource problem, but it's not quite what we want.

Spring and Guice are really geared toward injecting generic class instances.  These instances are either reflectively created based on a classname stored in an XML file (or in Guice's case, a Module class), or are simply supplied to the DI framework upon initialization.  Either way, it's not exactly what the situation called for.

What Romain was looking for was a framework which could parse human-readable resource definitions from an external config file and then inject them into the appropriately typed fields in the component instance.  For example, something like this:

public class MyComponent extends JComponent {
	@InjectedResource
	private Color myPrimary;

	@InjectedResource
	private GradientPaint gradient;

	// ...
}
MyComponent.myPrimary=#0f0f0f
MyComponent.gradient=0, 0 | 5, 10 | #000000 | #ffffff

As you can see, the method for parsing the resources needed to differ depending on the type of the resource to be injected. This was something that existing DI frameworks really didn't handle too well (still don't). So in true open-source fashion, Romain started with the syntax, evolved a simple API around it and donated the result to the community.

Making Use of Fuse

In true "how-to" style, let's start at the very beginning.  There really isn't any simpler control than a text label.  Just to add a little twist (and double the number of resources required), our label will render its text using that ever-popular "indented text" style that's so over-used in Leopard:

@Override
protected void paintComponent(Graphics g) {
	Graphics2D g2 = (Graphics2D) g;
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			RenderingHints.VALUE_ANTIALIAS_ON);
	g2.setFont(g2.getFont().deriveFont(Font.BOLD).deriveFont(
			(float) g2.getFont().getSize() + 1));
	
	float x = 2;
	float y = getHeight() - ((getHeight() - g2.getFontMetrics().getHeight()) 
			 / 2) + offset;
	
	g2.setColor(accentColor);
	g2.drawString(text, x, y);
	
	y -= offset;
	g2.setColor(textColor);
	
	g2.drawString(text, x, y);
}

label

Of course, the accentColor and textColor fields need to come from somewhere.  With Fuse, all we need to do is tag the appropriate instance fields with the @InjectedResource annotation and invoke ResourceInjector#inject on this:

@InjectedResource
private Color accentColor, textColor;

@InjectedResource
private float offset;

public StyledLabel(String text) {
	this.text = text;
	
	ResourceInjector.get("components.style").inject(this);
}

Starting to get the picture? Our ResourceInjector instance contains resource values which have been loaded from some external source, usually a properties file.  In our example, the properties file contains the following:

StyledLabel.accentColor=#80ffffff
StyledLabel.textColor=#0f0f0f
StyledLabel.offset=1

ResourceInjector itself is initialized in the following code somewhere in the startup of our application:

ResourceInjector.addModule("org.jdesktop.fuse.swing.SwingModule");
		ResourceInjector.get("components.style").load(
				"/com/codecommit/fuse/components/style.properties");

In this example, we're availing ourselves of a Fuse feature which allows multiple ResourceInjector singletons.  This allows us to separate the resources into separate files based on what section of the UI we're dealing with.  For example, in my sample application I have two injectors, one for ui.style and one for components.style.  This makes finding the properties I need to tweak that much easier, also aiding in the refactoring process if I ever need to move components around.

Another important thing of note in the initialization code is the addition of the SwingModule into ResourceInjector.  Fuse is designed to be extremely modular.  Out of the box, it doesn't even have support for Swing, just a few core JDK types.  Swing support is of course included in the default download, but the overhead of the Swing types is not forced upon would-be adopters.  We could just as easily be building an entirely SWT application, in which case we would want to add the SWTModule instead (also included in the default distribution).

More Advanced Resources

Of course, a text label really isn't that interesting as far as use-cases go.  Any framework can look stellar in that kind of micro-example.  The real test of a framework's power is how well it deals with the more complex applications.  To this end, let's try our hand on a simple UI delegate for JButton:

@InjectedResource
private Color borderColor, highlightColor, textColor, textAccent, textAccentHot;

@InjectedResource
private GradientPaint topGradientUp, topGradientDown, bottomGradientUp, bottomGradientDown;

@InjectedResource
private int height, padding;

private StyledButtonUI() {
	ResourceInjector.get("components.style").inject(this);
}

@Override
public void paint(Graphics g, JComponent c) {
	JButton button = (JButton) c;
	boolean pushed = button.getModel().isPressed();
	
	Graphics2D g2 = (Graphics2D) g;
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			 RenderingHints.VALUE_ANTIALIAS_ON);
	
	int height = button.getHeight() / 2;
	
	g2.setPaint(pushed ? topGradientDown : topGradientUp);
	g2.fillRect(0, 0, button.getWidth(), height);
	
	g2.setPaint(pushed ? bottomGradientDown : bottomGradientUp);
	g2.fillRect(0, height, button.getWidth(), height);
	
	g2.setColor(borderColor);
	g2.drawRect(0, 0, button.getWidth() - 1, button.getHeight() - 1);
	
	g2.setColor(highlightColor);
	g2.drawRect(1, 1, button.getWidth() - 3, button.getHeight() - 3);
	
	int x = (button.getWidth() - 
			g2.getFontMetrics().stringWidth(button.getText())) / 2;
	int y = button.getHeight() - 
			((button.getHeight() - g2.getFontMetrics().getHeight()) / 2) - 3;
	
	y += 1;
	g2.setColor(pushed ? textAccentHot : textAccent);
	g2.drawString(button.getText(), x, y);
	
	y -= 1;
	g2.setColor(textColor);
	g2.drawString(button.getText(), x, y);
}

button        button-pushed

It may seem a bit overwhelming, but it's really quite straightforward.  As you can see, there are now several resources which are being utilized in the paint process, all of them injected instance fields.  The properties file now looks like this:

StyledLabel.accentColor=#80ffffff
StyledLabel.textColor=#0f0f0f
StyledLabel.offset=1

StyledButtonUI.borderColor=#5f5f5f
StyledButtonUI.highlightColor=#40ffffff
StyledButtonUI.textColor={StyledLabel.textColor}
StyledButtonUI.textAccent=#50ffffff
StyledButtonUI.textAccentHot=#70ffffff
StyledButtonUI.topGradientUp=0, 0 | 0, 12 | #b0b0b0 | #909090
StyledButtonUI.topGradientDown=0, 0 | 0, 12 | #a9a9a9 | #808080
StyledButtonUI.bottomGradientUp=0, 13 | 0, 25 | #898989 | #595959
StyledButtonUI.bottomGradientDown=0, 13 | 0, 25 | #707070 | #404040
StyledButtonUI.height=25
StyledButtonUI.padding=40

You can imagine how much easier the design process becomes with this sort of centralization.  Say for example my client decided it wanted to go with a less depressing color scheme.  Instead of hunting through my sources to find exactly the right property to change, I can just tweak the values in a single properties file and watch the magic happen.  What's even better, is theoretically any designer could make changes to these files (which can really be considered Swing stylesheets) and effect sweeping changes in the UI.

Do not Repeat Yourself

An important feature that I didn't point out in the button example is the StyledButtonUI.textColor property.  You'll notice that it's not an actual value, but some sort of reference to StyledLabel.textColor.  Well as it turns out, Fuse is perfectly content performing substitutions (both forward and backward) within the resource values.  Thus, there's no need to be redundant in the definition of resource values.  This comes in handy when you have more than one component, all of which need to be themed similarly.  To demonstrate this, let's create a simple (and very lazily implemented) drop-down control:

@InjectedResource
private Color borderColor, highlightColor, textColor, textAccent, textAccentHot, divider;

@InjectedResource
private GradientPaint topGradientUp, topGradientDown, bottomGradientUp, bottomGradientDown;

@InjectedResource
private int width, height, padding;

@InjectedResource
private Image arrow;

private boolean pushed;

public StyledDropDown(String[] items) {
	this.items = items;
	
	if (items.length < 0) {
		throw new RuntimeException("Lazy coder alert!!  Can't handle 0 length items");
	}
	
	ResourceInjector.get("components.style").inject(this);
	
	addMouseListener(new MouseListener() {...});
}

@Override
protected void paintComponent(Graphics g) {
	Graphics2D g2 = (Graphics2D) g;
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			RenderingHints.VALUE_ANTIALIAS_ON);
	
	int height = getHeight() / 2;
	
	g2.setPaint(pushed ? topGradientDown : topGradientUp);
	g2.fillRect(0, 0, getWidth(), height);
	
	g2.setPaint(pushed ? bottomGradientDown : bottomGradientUp);
	g2.fillRect(0, height, getWidth(), height);
	
	g2.setColor(borderColor);
	g2.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
	
	g2.setColor(highlightColor);
	g2.drawRect(1, 1, getWidth() - 3, getHeight() - 3);
	
	int x = padding / 2;
	int y = getHeight() - 
			((getHeight() - g2.getFontMetrics().getHeight()) / 2) - 3;
	
	y += 1;
	g2.setColor(pushed ? textAccentHot : textAccent);
	g2.drawString(getText(), x, y);
	
	y -= 1;
	g2.setColor(textColor);
	g2.drawString(getText(), x, y);
	
	x = getWidth() - (arrow.getWidth(this) + padding) - 1;
	y = 4;
	
	g2.setColor(divider);
	g2.drawLine(x, y, x, getHeight() - 5);
	
	x += padding / 2;
	y = (getHeight() - arrow.getHeight(this)) / 2;
	
	g2.drawImage(arrow, x, y, this);
}

drop-down

And the properties file now becomes:

*.textColor=#0f0f0f

StyledLabel.accentColor=#80ffffff
StyledLabel.offset=1

StyledButtonUI.borderColor=#5f5f5f
StyledButtonUI.highlightColor=#40ffffff
StyledButtonUI.textAccent=#50ffffff
StyledButtonUI.textAccentHot=#70ffffff
StyledButtonUI.topGradientUp=0, 0 | 0, 12 | #b0b0b0 | #909090
StyledButtonUI.topGradientDown=0, 0 | 0, 12 | #a9a9a9 | #808080
StyledButtonUI.bottomGradientUp=0, 13 | 0, 25 | #898989 | #595959
StyledButtonUI.bottomGradientDown=0, 13 | 0, 25 | #707070 | #404040
StyledButtonUI.height=25
StyledButtonUI.padding=40

StyledDropDown.borderColor={StyledButtonUI.borderColor}
StyledDropDown.highlightColor={StyledButtonUI.highlightColor}
StyledDropDown.textAccent={StyledButtonUI.textAccent}
StyledDropDown.textAccentHot={StyledButtonUI.textAccentHot}
StyledDropDown.divider={StyledDropDown.highlightColor}
StyledDropDown.topGradientUp={StyledButtonUI.topGradientUp}
StyledDropDown.topGradientDown={StyledButtonUI.topGradientDown}
StyledDropDown.bottomGradientUp={StyledButtonUI.bottomGradientUp}
StyledDropDown.bottomGradientDown={StyledButtonUI.bottomGradientDown}
StyledDropDown.arrow=/images/arrow.png

# we'll be lazy and just set a fixed width
StyledDropDown.width=250
StyledDropDown.height={StyledButtonUI.height}
StyledDropDown.padding=10

You'll notice the main theme of this revised file is "reuse".  Throughout the whole file, not a single value is repeated.  More importantly, the properties which should share commonality at all times (like the gradients on the drop down and the button) are maintained in a single location.  Changing the gradient for the button delegate will also change the drop down to match.  This is an incredibly powerful feature which allows for extreme ease in maintaining complex, custom components.

Another thing worthy of notice is the new *.textColor property.  All three components have a textColor field which needs to receive the same value.  Rather than writing three separate textColor definitions linking back to the first, we define a single, global property which Fuse will use for all three fields.  To avoid accidentally overriding components that you don't want to share values, Fuse will only check the global (*. syntax) property if no specific property is found for the class in question.

SWT

Just as a small item of interest, this same resource file can be literally reused verbatim if we were rebuilding the UI using SWT.  Obviously, there might be minor changes for clarity reasons (SWT doesn't have delegates, so the class-name would probably be "StyledButton"), but all of the resource values could be retained.  The only change in code we would have to make (besides the obvious changes in graphics calls and library class names) would be to load the SWTModule instead of the SwingModule.  Also, we would need to set the Display instance as a global property within ResourceInjector, giving it the full flexibility to instantiate SWT resources.

Fuse literally abstracts all of the specifics of resource loading and initialization.  The same syntax for correspondent resources works across both Swing and SWT, allowing commonality and familiarity with developers switching between the two.  This abstraction is so convenient that I find myself using Fuse even for trivial applications with a minimum of custom resources, just to give myself the easy resource loading and instantiation.

Efficiency

Fuse is an incredibly lean library.  In fact, not only are the JAR files small, but the actual fraction of the library that's used in cases such as the above is really quite small.  Not utilized above are features like hives (allowing theme changes "mid-flight" as it were), auto-injection, different resource formats and so on.  Practically speaking, the number of classes loaded by Fuse to satisfy cases such as the example is very very minimal.

Even more importantly, Fuse is very clever about reusing resources where possible.  For example, both our button and our drop-down use many of the same resource values.  (e.g. borderColor=#5f5f5f)  When Fuse parses the resource file, it's only concern is reading the text values into memory and performing basic text substitutions.  Thus, the resource file is fully resolved when loaded by Fuse, there are no references between elements.  The String value "#5f5f5f" will appear twice in-memory, associated with two different properties. 

In order to maintain clean separation of concerns, the original design for Fuse calls for resource loading to be handled separately from type-specific parsing and instantiation.  It is this decision which allows for Fuse's clean modularity.  However, this also meant that in the original version of Fuse, two identical values would lead to two separate resource instances.  This is how it would fall out if the resources were embedded within the code itself (as most components are written), but this isn't the optimal case.

As of Fuse 0.4 (the current release), value caching is supported.  This means that if Fuse detects two fields with the same text value and type, it will use the exact same resource instance for both injections.  Thus, not only is the color value for borderColor the same in both StyledButtonUI and StyledDropDown, but also the actual instance of java.awt.Color is shared.  At face value, this seems like a bad idea, but when you consider that most UI resources are immutable, it starts to make a lot more sense.  This sort of optimization is something which would be a major headache to implement in a conventional scenario, but with Fuse, it's handled automatically.  Note that the property names are irrelevant to the cache.  Fuse matches entirely based on property value and type.

Conclusion

Fuse can be a very, very powerful tool in facilitating your next "filthy rich" application.  It provides a small mountain of features while maintaining a great deal of simplicity and elegance in the API.  Without getting in your way, it offers added flexibility, control and efficiency, satisfying real-world requirements which would otherwise be difficult, if not impossible to attain.  Fuse: don't leave home without it.

Download Fuse 0.4 from Java.net  (project page) (javadoc)

Legacy
Article Resources: