This article is based on Griffon in Action, to be published on September 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) ebooks and pbooks. MEAPs are sold exclusively through Manning.com. All print book purchases include an ebook free of charge. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information.
also read:
- Java Tutorials
- Java EE Tutorials
- Design Patterns Tutorials
- Java File IO Tutorials
Official Builder Extensions
Introduction
We are going to take a close look at some Griffon-related builder extensions that can make your life as a developer easier. SwingBuilder is one of the builders that can be configured on a Griffon application. SwingXBuilder is one of the official builder extensions. We’ll cover each one of them briefly. Let’s start with SwingXBuilder.
SwingXBuilder
SwingXBuilder provides threading facilities in the form of the withWorker() node, an abstraction over Swinglab’s SwingWorker and JDK6’s SwingWorker. There is more to SwingXBuilder that just that. The SwingX project was born as an incubator for ideas and components that may eventually find their way into the JDK. SwingWorker is one of those few that actually made the transition.
You’ll find a good number of interesting components in the SwingX component suite. Many were designed as a replacement for the existing Swing components; for example, JXButton would replace JButton. SwingX offers more than just components; it also provides a painters API that lets you override how a component is painted on the screen without actually needing to create a subclass of said component.
The easiest way to install SwingXBuilder on an application is by installing its companion plugin. That’s right; let the framework do the hard work for you. The plugin will configure the builder on the application’s metadata and config files. It will also copy required libs and dependencies when needed. Invoke the following command on your command prompt to install the plugin and builder:
griffon install-plugin swingx-builder
Once you have it installed, you’re ready to work. One last piece of information before you begin—remember we just said that some SwingX components were designed as replacements? Well, SwingXBuilder took that into consideration, which means that some of its node names may clash with SwingBuilder’s. That is the reason why this builder is configured with a different prefix—more precisely, jx. This means that whenever you see a node name that starts with that prefix, it’s a node that has been contributed by SwingXBuilder. Alright, figure 1 shows a simple application that contains two main elements: a header and a TaskPane container.
The header component is using the painters API. This painter, in particular, is a compound of three other painters:
- The first painter provides the base color, a very dark shade of gray—almost black.
- The second painter provides the pinstripes. You can change and tweak the stripes’ angle, color, and spacing.
- The last painter provides a glossy look. Everything looks better with a bit of shine.
The second component emulates the behavior seen on earlier versions of the Windows file explorer. It is a TaskPaneContainer and, as its name implies, it serves as a place to embed TaskPanes. There are two TaskPanes on this container. Clicking on Task group 2 will expand its contents (with a smooth animation); clicking it again will collapse it. You may embed any Swing components in a TaskPane. What’s that? What about the code, you say? Sure. It couldn’t be any easier, as listing 1 shows.
Listing 1 Some SwingXBuilder components in action
import java.awt.Color import org.jdesktop.swingx.painter.GlossPainter gloss = glossPainter( aint: new Color(1f, 1f, 1f, 0.2f), position: GlossPainter.GlossPosition.TOP) #1 stripes = pinstripePainter(paint: new Color(1f, 1f, 1f, 0.17f), spacing: 5.0) #1 matte = mattePainter(fillPaint: new Color(51, 51, 51)) #1 compound = compoundPainter(painters: [matte, stripes, gloss]) #2 application(title: 'swingx-test', pack: true, locationByPlatform: true, iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/griffon-icon-48x48.png').image, imageIcon('/griffon-icon-32x32.png').image, imageIcon('/griffon-icon-16x16.png').image]) { borderLayout() jxheader(constraints: NORTH, title: "SwingXBuilder Example", #3 description: "A brief example of SwingXBuilder in action", titleForeground: Color.WHITE, descriptionForeground: Color.WHITE, icon: imageIcon("/griffon-icon-48x48.png"), preferredSize: [480,80], backgroundPainter: compound) ) jxtaskPaneContainer(constraints: CENTER) { #4 jxtaskPane(title: "Task group 1") { jxlabel("Action 1") } jxtaskPane(title: "Task group 2", expanded: false) { label("Action 2") } } } #1 Painters' definitions #2 Compound painter definition #3 JXHeader definition #4 JXTaskPaneContainer definition
At #1, you see how each of the painters is defined. SwingXBuilder exposes a node for each of the basic painters you’ll find on the Painters API. The compound painter is created in #2; it only requires a list of painters to be used. At #3, the header component is built. Note that all it takes is to set a few properties on that component. Setting the compound painter is done as with every other property; there is simply no magic to it. Finally, at #4, we see the TaskPanes being added to their container. Note that the first TaskPane refers to a jxlabel node, while the second refers to a label node. This means that the first pane should have a JXLabel instance, while the second should have a JLabel instance. The use of the jx prefix makes switching from one type of node to the other a simple task.
What’s more, the CompositeBuilder makes mixing nodes from two builders a trivial task. We won’t show how you can achieve the same goal by regular SwingBuilder means. After all this is one of the main reasons why Griffon came to be.
Make sure to check SwingX‘s other components. We’re sure you’ll find interesting the following ones:
- JXErrorPane—Useful for displaying errors caused by an exception. Highly configurable.
- JXTitlePanel—A JPanel replacement that comes with its own title area.
- JXTable—Row highlighting and sorting are but a few of the features you’ll find in it. Some of its features have been merged into JDK’s JTable.
- JXHyperLink—A button that behaves like a hyperlink as seen on web pages.
The next builder we’ll cover is also quite popular.
JideBuilder
The announcement of the JIDE Common Layer (JCL) project’s becoming an open source project back in 2007 surprised many. Up to that point, JIDE was a closed-source component suite developed by JideSoft. Many components have been open sourced since the announcement. You’ll find some easily recognizable components like split buttons (a component that is both a button and a menu), lists that can display checkboxes (without the need of a custom renderer made by yourself), comboBoxes that support auto completion; just to name a few. Despite the number of components that you’ll find in the JCL, it only represents close to 35 percent of all components provided by JideSoft; the rest are available through the purchase of a commercial library. Suffices to say those components are not supported by JideBuilder itself; however, adding them is not that difficult, should you chose to purchase a license.
Installing the builder is done by installing its companion plugin. This is a recurring theme, isn’t it? We did mention that plugins came in handy and, as you’ll soon see, all officially supported builders can be installed via plugins. The following command should do the trick:
griffon install-plugin jide-builder
Take a look at the builder’s documentation site to find out more about the components that are now at your disposal. We’re sure you’ll find SplitButton useful, especially for an application that requires a more enterprise-like behavior. Other components that fall into that category are:
- AutoCompletionComboBox—A comboBox that comes with an auto completion feature. Options will be selected as you type on the comboBox’s input field.
- TriStateCheckBox—When enabled and disabled are just not enough.
- SearchableBar—Adds searching capabilities to tables and trees.
- DateSpinner—A spinner component that knows how to work with Dates.
Figure 2 presents an application that showcases three JIDE components: checkboxList, jideButton, and jideSplitButton.
The list on the left does not require additional properties to be set in order to display a checkbox per entry, just set the data you need and that’s all. The first four buttons on the right belong to the same type but are rendered with different styles. Hovering on Toolbar will make the button draw a raised edge, like the one Toolbox has. The Flat button stays like it currently is, flat. The fourth one, Hyperlink, will draw its text with an underline when the mouse is posed over it. The fifth button is the special button we’ve been talking about: a combination of button and menu. It will trigger a normal action once you click on it, like a regular button. But, if you keep the button pressed, it will not fire that action but rather present a popup menu with more choices. The code is straightforward as listing 2 shows.
Listing 2 JIDE’s CheckboxList and Buttons
import com.jidesoft.swing.ButtonStyle data = (1..20).collect([]){"Option $it"} as Object[] #1 application(title: 'jide-test', pack: true, locationByPlatform: true, iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/griffon-icon-48x48.png').image, imageIcon('/griffon-icon-32x32.png').image, imageIcon('/griffon-icon-16x16.png').image]) { borderLayout() panel(border: titledBorder(title: "CheckBoxList"), constraints: WEST) { scrollPane(constraints: context.CENTER) { checkBoxList(listData: data) #2 } } panel(border: titledBorder(title: "Buttons"), constraints: CENTER) { panel{ gridLayout(cols: 1, rows: 5) ["Toolbar", "Toolbox", "Flat", "Hyperlink"].each { style -> #3 jideButton(style, buttonStyle: ButtonStyle."${style.toUpperCase()}_STYLE") #3 } jideSplitButton("Button + Menu", #4 customize: { m -> m.removeAll() (1..2).each{ m.add "Option $it" } }) } } } #1 Sample date used on the list #2 CheckBoxList definition #3 Four types of JideButtons #4 A button plus menu
CheckboxLists (#2) can be created in the same way as regular lists, just pass an array of object (#1) or a model as data. JideButtons have a style property that controls how they are rendered to the screen. You can see a fairly common Groovy trick (#3) used to read a constant field from a Java class that follows a pattern. In our case, each element on the style list serves as the button’s text and the basis to read a constant field on the ButtonStyle class. Creating the menu of a JideSplitButton (#4) is a matter of defining a closure for its customize property. Notice that all menu items are removed first and then some are added. Duplicate menu items would start piling up every time you display the menu if it’s not done this way. This is due to JideSplitButton’s behavior of keeping a reference to the menu it created the first time.
The next builder is sure to catch your eye.
CSSBuilder
Hold on a moment! Is that CSS as in Cascading Style Sheets? As in a technology that is typically associated with web content but not desktop? Happily, the answer is yes! Styling desktop components by means of CSS or a CSS-like solution is one of those goals that desktop developers often look for besides better threading and binding.
CSSBuilder is actually a wrapper on a handful of classes that belong to the Swing-clarity project, whose creators are Ben Galbraith and Dion Almaer, from ajaxian.org fame. That’s true! Those guys used to work on the desktop side before riding the Ajax wave revolution.
The CSS support provided by Swing-clarity is able to parse CSS2 selectors and colors. On top of it, CSSBuilder adds support for 71+ custom properties that are specific to Java Swing. Figure 3 depicts a trivial application where all of its visual components have received a facelift via CSS.
There are two main sections in figure 3. The left section’s background is darker than the right. Labels have an italicized style, while buttons have a bold weight. All buttons share the same background color regardless of the section where they are placed. There is one button and one label with red foreground. Notice that the text on all components is centered. Are you ready to see the CSS style sheet? Listing 3 contains all that we’ve just described in CSS format.
Listing 3 Swing CSS style sheet
* { #1 color: white; font-size: 16pt; swing-halign: center; #2 } #group1 { #3 background-color: #303030; border-color: white; border-width: 3; } #group2 { #4 background-color: #C0C0C0; border-color: red; border-width: 3; } jbutton { #5 font-weight: bold; background-color: #777777; color: black; } jlabel { font-style: italic; } #6 .active { color: red; } #7 #1 General purpose styles #2 Custom css property #3 Left group style #4 Right group style #5 Style applied to all buttons #6 Style applied to all labels #7 Targeted style
As you can see, CSSBuilder lets you apply a generic style (#1) to all components. The custom swing-halign (#2) property is but one of the many Swing-specific properties you may use; this one in particular will center the text of a component. Next, we see CSS properties for the left (#3) and right (#4) groups, where background and border settings can be seen. Notice that groups use a selector that starts with a # character; its nature will be revealed soon in the View code. Next, we see button (#5) and label (#6) properties. They are defined using another type of selector; this selector matches the class name of the target component. Lastly, we see another selector (#7) that simply defines a foreground property with red as value. The View code is actually very simple, as listing 4 can attest.
Listing 4 A CSS-styled View
application(id: "mainFrame", title: 'css-test', pack: true, locationByPlatform: true, iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/griffon-icon-48x48.png').image, imageIcon('/griffon-icon-32x32.png').image, imageIcon('/griffon-icon-16x16.png').image]) { gridLayout(cols: 2, rows: 1) panel(name: "group1") { #1 gridLayout(cols: 1, rows: 4) label("Label 1.1") label("Label 1.2") button("Button 1.1") button("Button 1.2", cssClass: "active") #2 } panel(name: "group2") { #1 gridLayout(cols: 1, rows: 4) label("Label 2.1") label("Label 2.2", cssClass: "active") #2 button("Button 2.1") button("Button 2.2") } #1 Resolves to named selector #2 Resolves to class selector
There is not a lot of information revealing that this View can be styled with CSS, other than the obvious cssClass (#2) property. Hold on a second, that property is applied to a JLabel and JButton but those components know nothing about CSS. Their node factories also know nothing about CSS. How then, is the application able to apply the correct style? The answer lies in attribute delegates. CSSBuilder registers a custom attribute delegate that intercepts the cssClass attribute and applies the style. Remember, we said that groups used a special selector? Well, now you know how it can be defined (#1). As a rule, any node that has a name property defined (this is a standard Swing property, by the way) will be accessible via the # selector (like #group1 and #group2), whereas any node that defines a cssClass property will be accessible via the . selector (like .active).
There are additional features to be found on CSSBuilder, such as JQuery-like component finders using the $() method. Make sure to review the builder’s documentation no learn more about all the properties and methods.
You must perform two additional tasks before trying this example for yourself. First, the CSS stylesheet must be saved in a file with a conventional name and location. Save the contents of listing 3 as griffon-app/resources/style.css. Don’t worry, you’ll be able to use a different name and location if needed; placing the file there with that particular name simply saves you the trouble of additional configuration. The second step is to tell the CSS system which elements need to be styled. Look carefully at listing 4 and you’ll see that the application node has an id declared with value mainFrame. We’ll use this id to instruct the CSS system that the frame and its contents need styling. Open up griffon-app/lifecycle/Stratup.groovy on your favorite editor. We’ve chosen this life cycle script because all views have been constructed by the time it is called. This means the mainFrame will be ready to be styled. Type in the following snippet into the script, save it, and run the application.
import griffon.builder.css.CSSDecorator CSSDecorator.decorate("style", app.builders.'css-test'.mainFrame)
So far, we’ve covered component suites and component styling. Let’s jump into graphics and drawings for a change.
GfxBuilder
The JDK comes with a number of drawing primitives and utility classes that are collectively known as Java2D. Every Swing component is drawn using Java2D; it makes sense then to review what you can do with Java2D. That is, if you want to go the long route. Java2D suffers from the same problems you will encounter in plain Swing code. That’s why GfxBuilder was born in the first place, exactly as SwingBuilder was for Swing.
Based on this information, you can expect GfxBuilder to provide a node for each of the Java2D drawing primitives (like Rectangle, Ellipse, and Arc). It also provides nodes for setting antialiasing (remove those ugly jaggies), rendering hints, area operations, and much more. The following is summarized list of the types of nodes that become available to you when using GfxBuilder:
- Canvas—The surface area to place your drawings.
- Standard shape nodes—rect, circle, arc, ellipse, path.
- Additional shape nodes—asterisk, arrow, cross, donut, lauburu, and more. These shapes come from the jSilhouette shape collection.
- Standard and custom strokes—Think of strokes as shape borders.
- Area operations—add, subtract, intersect, xor.
- Utility nodes—color, group, clip, image.
However, what really makes using GfxBuilder a better experience than just plain Java2D (other than the use of Groovy features) is that it comes with a scene graph baked right in. Scene graphs allow graphics to be defined in retained mode, whereas Java2D works in direct mode. Direct mode means that graphics primitives will be drawn to
the screen immediately as soon as the code that defines them is processed. Retained mode means that a scene graph is created and a node is assigned to each drawing instruction. The scene graph controls when the graphics should be rendered to the screen, also when they should be updated. This relieves you of the burden of keeping track of areas that need to be redrawn or updated—the scene graph does it for you.
Figure 4 is a computerized rendition of what many of us drew as children while at elementary school: a red-roof house sitting on a patch of green grass, with the sun shining over it and a clear blue sky.
We know—it’s a bit childish. It will surely never grace the halls of a respected art gallery but one can dream, right? The previous picture is the composition of geometric shapes, colors, and strokes. How complicated can it be? We’ll let the code speak for itself. Listing 5 shows all that there is to it.
Listing 5 Drawing a happy house with Groovy
application(title: 'gfx-test', pack: true, locationByPlatform: true, iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/griffon-icon-48x48.png').image, imageIcon('/griffon-icon-32x32.png').image, imageIcon('/griffon-icon-16x16.png').image]){ canvas(preferredSize: [300, 300]) { group { antialias true background(color: color(r: 0.6, g: 0.9, b:0.8)) #1 rect(y: 230, w: 300, h: 70, f: color(g: 0.8), bc: color(g:0.5)){ #2 wobbleStroke() } rect(x: 75, y: 150, w: 150, h: 100, f: 'white') #3 triangle(x: 55, y: 150, w: 190, h: 50, f: 'red') #4 rect(x: 130, y: 190, w: 40, h: 60, f: 'brown') #5 circle(cx: 50, cy: 50, r: 30, f: 'yellow', bc: 'red') #6 } } } #1 Blue sky #2 Green grass #3 White house #4 Red roof #5 Brown door #6 Bright and shiny sun
That’s pretty straightforward, isn’t it? A background color (#1) turns out to be the blue sky. A patch of green grass is seen at #2, complete with some grass leaves (the wobbly stroke). The house is composed of a white wall (#3), the red roof (#4), and a brown door (#5). Lastly, the sun shining over the whole scenery is listed at #6.
Perhaps this is lost in the code’s simplicity but notice that Swing nodes and graphics node merge in a seamless way. There is no artificial bridge between them. This is precisely the kind of power that Griffon‘s CompositeBuilder puts at your finger tips.
There are other features to be found on GfxBuilder; however, we must keep things simple. Suffices to say that every gfx node is also an observable bean, almost every property triggers a PropertyChangeEvent, which means you’ll be able to use binding with them. Another powerful feature is the ability to define your own nodes via node composition, and not just by subclassing a node class. Figure 5 is a remake of an example show in the Filthy Rich Clients book (highly recommended if you want to learn the secrets for good looking and well-behaving applications). It is a sphere created from circles and gradients alone, in other words, 2D primitives giving the illusion of a 3D object.
This time, the code is partitioned in two: the View and a custom node that knows how to draw spheres. Let’s see the View first as it’s the simplest of the two (listing 6).
Listing 6 The SphereView view script
application(title:'sphere', pack: true, locationByPlatform:true, iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/griffon-icon-48x48.png').image, imageIcon('/griffon-icon-32x32.png').image, imageIcon('/griffon-icon-16x16.png').image]) { canvas(preferredSize: [250, 250]) { group { antialias true background(color: color('white')) customNode(SphereNode, cx: 125, cy: 125) #1 } } } #1 Custom node definition
As you can see, the View code is similar to the first GfxBuilder example; however, this time there is a new node (#1) used to render a sphere object. The customNode node takes either a class or an instance of CustomNode, in our case, SphereNode, whose entire definition is found in listing 7.
Listing 7 SphereNode definition
import java.awt.Color import griffon.builder.gfx.Colors import griffon.builder.gfx.GfxBuilder import griffon.builder.gfx.GfxAttribute import griffon.builder.gfx.DrawableNode import griffon.builder.gfx.CustomGfxNode class SphereNode extendsCustomGfxNode { #1 @GfxAttribute(alias="r") double radius = 90 #2 @GfxAttribute double cx = 100 @GfxAttribute double cy = 100 @GfxAttribute Color base = Colors.get("blue") @GfxAttribute Color ambient = Colors.get(red: 6, green: 76, blue: 160, alpha: 127) @GfxAttribute Color specular = Colors.get(r: 64, g: 142, b: 203, a: 255) @GfxAttribute Color shine = Colors.get("white") SphereNode() { super("sphere") } DrawableNode createNode(GfxBuilder builder) { #3 double height = radius * 2 builder.group(borderColor: 'none') { // shadow circle(cx: cx, cy: cy+radius, radius: radius, sy: 0.3, sx: 1.2) { #4 radialGradient { stop(offset: 0.0f, color: color('black')) stop(offset: 0.6f, color: color('black').derive(alpha: 0.5)) stop(offset: 0.9f, color: color('black').derive(alpha: 0)) } } // sphere circle(cx: cx, cy: cy, radius: radius) { #5 multiPaint { colorPaint(color: base) radialGradient(radius: radius) { stop(offset: 0.0f, color: ambient) stop(offset: 1.0f, color: rgba(alpha: 204)) } radialGradient(cy: cy + (height*0.9), fy: cy + (height*1.1)+20, radius: radius) { stop(offset: 0.0f, color: specular) stop(offset: 0.8f, color: specular.derive(alpha: 0)) transforms{ scale(y: 0.5) } } radialGradient(fit: false, radius: height/1.4, fx: radius/2, fy: radius/4){ stop(offset: 0.0f, color: shine.derive(alpha:0.5)) stop(offset: 0.5f, color: shine.derive(alpha:0)) } } } } } } #1 Must extend from CustomNode #2 Defining an observable property #3 Must implement with custom drawing code #4 The shadow's circle and gradient #5 The Sphere's circle and gradients
These are the code highlights. First, every custom node must extend the CustomNode class (#1). Second, creating observable properties on custom nodes is similar to creating observable properties on Model classes. The only difference is the usage of @GfxAttribute instead of @Bindable. Third, every custom node must have a drawing code that renders the node (#3); you’ll find that you can use the same nodes as if it were a regular drawing, even other custom nodes! #4 and #5 demonstrate how gradients are created. In particular, #5 shows a unique feature: multipaints. If it were not for multipaints, which are a series of paint instructions all applied to the same shape, we would have to define a circle for each gradient. This would complicate matters because some of those gradients are scaled and transformed according to the base circle. It’s easier to calculate the transformations this way.
Additional builders
There are, in fact, more builders and plugins than the ones we just explained. You’ll find a detailed list of available builders. A quick survey of the additional builders follows.
FlamingoBuilder
The Flamingo component suite was created and is maintained by Kirill Grouchnikov, the same mastermind behind the Substance Java look-and-feel project that makes Swing applications look great. Perhaps the most interesting component found in this suite is JRibbon, a pure Java implementation of the Ribbon component found in Microsoft Office 2007. There’s also SVG support for creating icons and other goodies that complement the ribbon component.
MacWidgetsBuilder
MacWidgets is a project started by Keneth Orr, whose aim is to provide components that follow Apple’s Human Interface Guidelines. They are 100 percent Java but blend seamlessly with Apple’s look-and-feel, even when not running on Apple’s OSX! That’s true, with MacWidgets, you can create an application that looks like a native OSX application but runs on Windows or Linux. How crazy is that?
TridentBuilder
Trident is a general purpose animation library created and maintained by the powerhouse that is Kirill Grouchnikov. Trident takes off where Chet Haase’s TimingFramework left, and then adds many more things. To be honest, Trident follows TrimingFramework in spirit. It is not based on the latter’s codebase at all. Kirill has put a lot of effort into this animation library; it is very small and practical, up to the point that it is now the one used by both Substance and Flamingo to drive their animation needs. Be sure to follow Kirill’s blog and watch some of the videos2 related to Trident, Flamingo, and Substance that he has created over the years.
SwingxtrasBuilder
This is a collection of projects that do not warrant a builder of their own because their individual set of components is rather small. Here you’ll find xswingx. There’s also l2fprod commons, providing task panes, outlook bar, and a properties table. As fate put it, SwingX‘s task panes are actually based on l2fprod’s; the code was contributed from one project to the other. There’s also BalloonTip. If you ever wanted to have a friendly popup like the ones you can see appearing on your operating system’s task bar, then the components provided by BalloonTip are the way to make it happen.
AbeilleFormsBuilder
This builder is the workhorse behind the Abeille Forms plugin. You may recall that Abeille Forms Designer is an open-source alternative for designing forms and panels that rely on JGoodies FormLayout or the JDK’s GridbagLayout to place their components.
Summary
We surveyed the list of official extensions in the form of builders. There are many third-party component suites. These builders reduce the time gap you’d have to spend hunting for them; they also reduce the learning curve because they follow the same conventions as SwingBuilder.