Creating UIs with SWT or RAP is a common task in many projects. While we have a comprehensive set of widgets at our disposal, we only have a handful of layouts to choose from. One of the more popular layouts is the GridLayout. Together with its child the GridData it allows you to cover most of your layout needs. Sometimes nesting several composites is required but usually you will achieve your goal.
Although the GridLayout has proven to be very powerful, it is by no means a pleasure to work with. Having to declare the layout and the accompanying layout data on your widgets clutters up your code and makes it very verbose. In addition, you have to deal with several default values (eg. default layout margins) which might not suit your needs.
A basic solution
The first impulse is to create little utility methods which provide common methods like the following:
composite.setLayout( createGridLayout( 2, true ) ); control.setLayoutData( createGridDataFillHorizontal() );
While this approach helps us to provide reasonable default values and reduces the amount of code we have to write, it still doesn’t allow us to configure the layout the way we need it in a particular situation.
A clumsy solution
To create a factory you use the fillDefaults()/swtDefaults() methods. Both these classes use the same entry point methods so you cannot statically import them, making your code more verbose. The exact configuration of the “defaults” might not be what you want.
Although the configuration methods are fluent, they abstract away the simplicity of the underlying object. For example, you cannot use .marginLeft( 12 ), but you have to specify all four margins in the extendedMargins( .. ). Also, the method names do not match the layout object properties exactly, making them harder to map.
An efficient solution
To have a more flexible layout description mechanism we have created two utility classes, the GridLayoutUtil and the GridDataUtil. They allow you to describe your GridLayout and GridData with an efficient syntax that is easy to understand.
The main idea is to use a fluent interface to configure the layout or data object. The entry point is an .apply* method to set the layout object and to configure it further. A simple call looks like this:
GridLayoutUtil.applyGridLayout( composite ).numColumns( 2 ); GridDataUtil.applyGridData( control ).grabExcessHorizontalSpace( true );
The ability to chain the configuration calls helps us to reduce the number of lines wasted while also making the flow more readable. Of course in practice the two utility classes should be statically imported (as we will do from now on in this blog post).
As you can see above, we configure the control to have a GridData object which grabs its available space horizontally. While this is very precise it is still a little verbose. Commonly you have a couple of GridData constellations you use all the time, the classic ones being “fill horizontal”, “fill vertical”, “fill entire available space” etc. To support this common scenario the GridDataUtil has several .with* methods that encapsulate a default configuration:
applyGridData( label ).withHorizontalFill(); applyGridData( button ).withVerticalFill().horizontalIndent( 8 );
With these calls the controls are configured to have values for the grab and align GridData properties. The exact implementation of these .with* methods might depend on your project’s preferences. The above snippet also shows that you can still override the default properties or extend them, as in the button control.
While it is great to have the .apply* methods, you sometimes want to adjust the layout object after it has been created. For that scenario we have created .on* methods (eg onGridData( control )) which pick up the GridData object of the control and allow further configuring:
onGridData( label ).horizontalSpan( 2 );
A benefit of the two static entry point methods is that you don’t litter your auto completion imports with several entries. Hitting control space and typing “apply” gives you very focused results.
We found working with this fluent approach much more efficient than creating the layout objects by hand or using the .create* helper methods. The JFace factory methods are a step in the right direction but in the end, they also fail in practice.
A downside of our approach (as with the JFace factories) is that you create one additional util object per layout object. But, we are more than happy to pay this price for the benefits we experienced.
You can download the utility classes from this gist: https://gist.github.com/mpost/6077907