Wednesday, May 18, 2011

 

Desktop Java GUI programming made easy -- An amateur's learning path on Groovy SwingBuilder and Miglayout - part 2

3. SwingBuilder + MigLayout

The confused searching

Now we are familiar with MigLayout, how can we use it with SwingBuilder?

There are many articles and tutorials about SwingBuilder, but most of them only showed the built-in layout. Griffon has MigLayout plugin so some articles showed MigLayout in Griffon, but that is not what we need. There was this identical question in the mailing list:

how can I register a LayoutManager in SwingBuilder, i. e. MigLayout?

But all the answers make me totally confused:

1.With the exception of JComponents that follow the JavaBean spec, you usually have to write a Factory to allow an object to be used as a DSL inside SwingBuilder.

2.If you can use the layout via no-args constructors, and rely on setters to configure you can just register a bean factory:

SwingBuilder seingBuilder = new SwingBuilder
swingBuilder.registerBeanFactory('MigLayout', MigLayout)

swingBuilder.build {
frame(title:'test frame', pack:true, show:true,
layout: new migLayout(layoutConstraints:'ins dialog',
columnConstraints:'[][]', rowConstraints:'')) {
label('hello world')
}


----------------------------------------------------------------------

3. This works for me

def swing = new SwingBuilder()
swing.registerFactory("MigLayout", new LayoutFactory(MigLayout))

...

scrollPane(id: 'eventScrollPanel') {
panel(id: 'eventPanel') {
MigLayout(layoutConstraints: 'wrap 10', columnConstraints: "[para]0[][100lp, fill][60lp][95lp, fill]")

}

Answer 2 didn't compile because of obvious spelling error. I changed "seingBuilder" to "swingBuilder", then I was told "unable to resolve class MigLayout" for line xxxxx . I changed it to "MigLayout", the code worked. But that means "MigLayout" was never used. I removed the registerBeanFactory line, the code still works! It seemed that there is actually no need for that line. But why would the expert said that?

And, now we know MigLayout can be used in SwingBuilder directly, how can that happen? Obvious some magic happened, but I don't know the trick of the magic!

I almost gave up at that point, until I found this blog

Groovy Builders, JCR (and MigLayout to the rescue)

I didn't try to compile it because there are some libraries unknown for me. However, once I formatted the code in IDEA for better readability, I found a mystery


def queryTextField = swing.textArea(rows: 5, columns: 20)

queryTextField.text = "/jcr:root/blueprint/element(*,nt:unstructured)"

...

def gui = swing.frame(title: 'JCRQuery', defaultCloseOperation: WC.EXIT_ON_CLOSE) {

panel(layout: new MigLayout("fill")) {

widget(queryTextField, constraints: 'grow')

button(text: 'Query', constraints: 'span, aligny bottom', actionPerformed: {

resultTextField.text = queryAction(queryTextField.text, valueTextField.text)

})

What's that "widget"? A google search took me to here, which is the old wiki page on Groovy site

The topic was beyond my interest so I didn't read it before.

What's widget?

"SwingBuilder has two 'magic' elements that pass through the value argument or the named attribute to the parent container.”

Is this the magic I'm looking for?

I took out Groovy in Action and read the SwingBuilder chapter again, this time I have a better picture of SwingBuilder magic. It turned out that I had several misunderstandings on basic concepts, which are not obvious with all the examples I read before, because they are very simple, until the question raised by using MigLayout cannot be answered by these examples!

Once I come back to the basic concepts and read all the examples carefully again, the mystery began to dissolve. I didn’t find the best syntax at once, but the method I found worked, so it kept me going a long way, until now, after more experiments and reading, I think I can answer the questions above thoroughly.


Back to the basics

First, how does SwingBuilder weave its magic?

Let's start from the simplest example of SwingBuilder. I removed any layout from it so we can see the framework itself, while I also kept some minor details neglected by some example, because it's important for practical applications. I actually generated a file template based of this as the base of any Swing program I'll write in future.

(You may note that it is different from many SwingBuilder examples. First it uses build method, which will build the UI inside the Event Dispatch Thread (EDT). I'm not very familar with threading, so I just copy it here and use it as default. The "pack: true, show: true" replaced the "frame.pack():frame.show()" in many examples. Without defaultCloseOperation: WC.DISPOSE_ON_CLOSE, the application will not terminate after you closed the window.)

import groovy.swing.SwingBuilder

import javax.swing.WindowConstants as WC

def swing = new SwingBuilder()

swing.build {

frame(title: "test", pack: true, show: true, defaultCloseOperation: WC.DISPOSE_ON_CLOSE){

label('hello')

}

}


The line of container (like frame, panel, scrollPanel) is often vastly different from line of component (like label, button), and there are several kinds of variations, so I just realized they are essentially quite similar until very recently.

SwingBuilder will build a node tree from the code inside the build section. Every component writes like this


button(parameterName:parameterValue, parameterName:parameterValue, parameterName:parameterValue)

SwingBuilder first checked "button", find that it is a registered component name (see Groovy official website for a full list, most time it is just a variation of the counterpart component name in Swing ). So the builder will generate a component object with corresponding parameters provided, put it as a node into a node tree. The pair of parameterName:parameterValue are named parameters, actually a key:value map which is used a lot in Groovy.

The container will have a line like this



panel(parameterName:parameterValue, parameterName:parameterValue) { some component lines }

It's all the same, just this time any component found inside the {} will be registered as child node of this container in the node tree. So we know in last paragraph, that component will go to its parent container too. Basically SwingBuilder use the closure syntax of {} to create nested objects, which you have to do it manually in Swing programming:

panel = new JPanel();

button = new JButton()

panel.add(button)

We can think the whole {} as a closure work as the last parameter of the container. If you remember, you can write actionPerformed logic inside the component line, which follows the same format:

button(parameterName:parameterValue, actionPerformed:{do something})

So we can see it's still the same syntax. One of the power of Groovy is you can often have many variations for doing one thing, which may confused some people, that's why I only talked with the most complete format of syntax in above examples, instead of the simpler one like these:

button()

button("this is a button")

Yes, it is simple, but what kind of magic was behind? Now you can know that the first one have no parameter, while the second one only have one parameter and it is the default one, so that string will be explained as the text on the button. If you have more parameters, you have to make effort to differentiate them so you write

button(text:"this is a button", actionPerformed:{do something})

So all of above are based on the name of the component is known. What if you have a customized component not registered in SwingBuilder, like "SuperButton" ? If SuperButton is still a subclass of JButton, you can still use the button line, together with the constructor of SuperButton, and the parameters can be understood by button, like this.

button(new SuperButton("custom constructor"), enabled:false)

Although the first parameter is a constructor for component, we can still think it as a parameter in the parameter list, only it is an Object this time.

What if your customized component doesn’t have a matching type? You have widget and container to pack them, make it just looks like other ducks. Actually most customized Swing component should subclass JComponent, and widget will return java.awt.Component. So you can see this is still similar to what happens above.

container(container:new CustomOutlookBar(), constraints:BorderLayout.WEST) {

widget(widget:new MyCustomButton())

}

Currently container and widget have no real difference. They were supposed to match container and component accordingly to give some hint about that.

After these review of basics, what do we have for the questions above?


Comments: Post a Comment

Subscribe to Post Comments [Atom]





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]