Wednesday, May 18, 2011

 

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

Solve the puzzle


The thread in the mailing list is very intrigue. Actually with current Groovy 1.8.0 distribution, the original poster should already have the right answer. The question was posted on 2009, so it’s possible that it didn’t work at that time, but worked now with some Groovy updates. No matter what, I’ll try to explain everything based on current Groovy status.

The question myself have actually is different from the post in mailing list. My original question is how to use Miglayout with SwingBuilder. The normal answer should be: just like any other layout. Normally the layout will be a value for named parameter inside the container (), like this:

 
         panel ( layout : new BorderLayout ( ) ) {
           label ( constraints : BorderLayout.NORTH ,  text : SU.isEventDispatchThread ( ) ? 'EDT-friendly' : 'Deadlock-friendly' )
           button ( constraints : BorderLayout.CENTER , text : 'hello', actionPerformed : {
               println ( 'button pushed' )
             }) 
         }

 

If the component need some layout constraints, the constraints will go to the component line just like code above. So a MigLayout example will be

 

swing.build {

frame(pack: true, show: true, defaultCloseOperation: WC.EXIT_ON_CLOSE, layout: new MigLayout("fill")) {

panel() {

label(text: "Configuration", constraints: "")

}

}

}

 

It’s same with the example of BorderLayout. I call this method “use layout as parameter”.

There is also another method available, which write layout into an independent line inside the builder process, so the layout line was processed by builder just like any other component. This time the layout name should be registered and recognized by SwingBuilder. You also need to use named parameters in this method, just like any component. I call this “use layout as a line”.

Let’s have a look at the original question in the mailing list:

I would like to use other layout managers with the SwingBuilder as easily as for example the GridLayout:
 
SwingBuilder.build {
frame(title:'test frame', pack:true, show:true) {
gridLayout(cols:2, rows:3)
label('hello world')
/* other components ... */
}
}
 
But with MigLayout I need to write something like this:
SwingBuilder.build {
frame(title:'test frame', pack:true, show:true, layout: new MigLayout('ins dialog', '[][]', '')) {
label('hello world')
/* other components ... */
}
}

I already tried it with SwingBuilder.registerFactory('migLayout', new LayoutFactory(MigLayout)) but I had no success.

Could anyone please help me how to get other layouts the groovy way?

The first example with gridLayout used the method of “use layout as a line”. He already has Miglayout worked with method “use layout as parameter”, but he want to use it with the method of “use layout as a line”.

The answer 1 said he needs to write a Factory. But answer 2 said he can just register a bean factory. This method did work, like following

import groovy.swing.SwingBuilder

import net.miginfocom.swing.MigLayout

import groovy.swing.factory.LayoutFactory

import javax.swing.WindowConstants as WC

def swing = new SwingBuilder()

swingBuilder.registerBeanFactory('migLayout', MigLayout)

swing.build {

frame(id: 'eventScrollPanel', pack: true, show: true, defaultCloseOperation: WC.EXIT_ON_CLOSE) {

migLayout(layoutConstraints:"fill,debug", columnConstraints:"", rowConstraints:"")

panel() {

label(text:"test")

}

}

}

What happened here? Line 2 generated a LayoutFactory from MigLayout, registered that factory as migLayout. After this migLayout can be used just like gridLayout inside the build process. It will be recognized as a layout Object, just like gridLayout.

The answer 3 used another method to register factory, which didn’t use Bean factory but regular Factory, and Layout Factory was used to convert MigLayout. This is also the method the original poster tried but failed, which should work actually. We only need to replace line 2 with

swing.registerBeanFactory('migLayout', MigLayout)

But the example provided in answer 2 will not work

SwingBuilder seingBuilder = new SwingBuilder
swing.registerFactory('migLayout', new LayoutFactory(MigLayout))


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

You either use layout as a parameter, which requires

1. Use original layout constructor and parameters, no named parameters

2. Put layout as a value of named parameter “layout”, inside the parameters () of a container

Or use layout as a line, which requires

1. Use registered factory name, not the original constructor. Also use named parameters.

2. Put the line inside builder as an independent line.

The example code in answer 2 mixed the two methods so will not work.

First, new migLayout() will not work because there is no migLayout class. If you are going to use migLayout, you have to take the method of use layout as line. If are going to put layout in parameters, then you have to use MigLayout. And you don’t need the line of registerFactory too.

In summary, I prefer the method “use layout in parameter”. Because

1. No need to register factory, and no need to use named parameters, which are rather long for MigLayout.

2. If you put layout as a line into a container’s {}, it’s not guaranteed that all the components in that container will be after this line. What will happen if a component was declared before the layout line? Better go with another method to avoid this problem.

The widget() usage

If you remember, the blog “Groovy Builders, JCR (and MigLayout to the rescue)” used widget in his code, what’s that for? Although we talked about the usage of widget, his usage is not exactly same, because it was not used in a customized component:

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)

})

Here the queryTextField is a regular textArea. But it was built outside of frame first. I noticed it was still built by SwingBuilder methods. So when he need to put the component into the layout, he cannot use the regular syntax, because the component is already there, how can you declare the same component again? He also cannot use the variable name “queryTextField” inside the frame directly, because it is not a registered component. So he used widget to pass through the object and parameter, just pretend it was a customized component, which did work.

Of course, if there is no special reason, the line about “queryTextField” can be put inside the builder process, like this:

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

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

textArea (id:”queryTextField”, constraints: 'grow')

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

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

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

})

We added an id parameter so that we can reference the component. The queryTextField.text line actually can be put inside the parameter field, but we can write it independently to show any other operations you need for queryTextField can be done this way. Of course, in some cases the operations better be done outside the builder process, then you still need the widget method.

Nested MigLayout


MigLayout is Grid based. This represents a different approach from many of the Swing Layout. Some people may have the habit to divide a complex UI into sections, put a panel for each section, and then put components into each panel with a special layout for that panel. This divide and conquer method looks intuitive, and it may be the only way to build a complex UI with the limited Swing Layout methods.

However, Miglayout don’t need nested layout to do a complex UI. Most complex Ui can be built without nested layout by grid, cell split and span, flow change etc. This method has many advantages compare to nested layout. Although it’s logical to put separate section into separate panels, but you have no control on the relationships among the components across the panel boundary. User may expect different components in different sections aligned, which is easily achieved with Miglayout’s grid. You can also adjust them by column or row together.

Still, in some case, you may need a nested component, how should you do it?

If the component inside the component don’t need any layout options, for example you want to put a list into a scrollPane. Then you can just write

scrollPane(constraints: "spany 1,wrap,grow"){list(id: 'fontsList', model: fontsModel)}

Here we defined the constraints for the scrollPane, and put the list as the child node of scrollPane with the {} syntax.

I met a combined problem of nesting and outside variable recently. At first I have a list declared outside the builder process, so I used widget method:

def fileList = new JList()

//inside the builder process

widget(fileList, constraints: "hmin 140, spany 5,grow")

Later I found I need to add a scrollPane to this list, how should I write it?

Through experiment, it should be like this

scrollPane(id:'fPane', constraints: "hmin 140, spany 5,grow" ){widget(fileList)}

So the constraints go to scrollPane now, and the list will be inside the closure of the scrollPane, which is wrapped by widget too.

If you do need some control for components inside the container, you can nest the MigLayout like this, make sure you are clear who the constraints’ receiver is.

frame(id: 'eventScrollPanel', pack: true, show: true, defaultCloseOperation: WC.EXIT_ON_CLOSE, layout: new MigLayout("fill", "", "")) {

panel(layout: new MigLayout("fill,debug", "", ""),constraints:"align right") {

label(text:"test",constraints:"align right")

}

}

So we have a top level MigLayout declared in frame, a sublevel MigLayout declared in panel. We wrote the component constraints of panel, which is controlled by the top level layout. We also wrote the component constraints of label, which went to the sub level layout in the panel.

That’s it. I wish my journey could be a little help to you. Any comment is welcome!

Update: I uploaded the source code of my pet project here, you can check them as a reference example if you are interested.


Comments:
I will have to re-read your posts several times before I can claim that I understand them.

But: you fall into the same trap as all of those who came before you. You just don't provide a code sample that works ... even a simple "Hello World" program. That is the reason you began this blog in the first place, and you missed the mark a bit.

You either use layout as a parameter...Or use layout as a line
Could you please give a working example of each? And maybe some reasons why you would choose one or the other?

Still, I appreciate all your hard work on this. When you said that you almost gave up at one point ... well I *did* give up at exactly the same point. Congratulations to you for pressing on.

Tom.
 
I gave a complete example in the comments of the part 1. Did you try it?
If it didn't work, what's the error message? Of course you have MigLayout added to classpath, right?

I think I gave the reason why I choose one format, just in this blog, starting from "In summary, I prefer the method “use layout in parameter”. Because..."
 
Besides, the reason I start to write this is NOT because the documents and articles didn't provide a working code example!

If you remember, the blog that made me start to understand how to use it did have a code example, but it was impossible for me to compile it because it involves some library I don't even know. All I need was several lines in that example. So what I really need is not a code that WORKING, but some answers right for my question. There are lots of working examples on SwingBuilder, and many examples on MigLayout. But none of them answered my question in details.

So most of my blog is trying to explain this point in detail. It's not about working example, it's about the syntax, format and usage of MigLayout in SwingBuilder.

I didn't explain details about Groovy, SwingBuilder used in this article, because that is not my point. So there could be some places not easily understood to you if you not familiar with Groovy and SwingBuilder. There are lots of good articles and books about them, you may need to read them first.
 
Post a Comment

Subscribe to Post Comments [Atom]





<< Home

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

Subscribe to Posts [Atom]