Friday, May 01, 2009

DesignGridLayout: where do we stand?

It's been a long time I haven't blogged on DesignGridLayout. Several events have concurred to this lack of communication:
  1. I have bought a brand new notebook (for those interested: that's a Sony VAIO Z)!
  2. It's taken me a long time to have DesignGridLayout tests run on both my old notebook (Windows XP) and my new one (Vista)
  3. I have plaid with Hudson on my old notebook to setup a continuous build for DesignGridLayout
Now that all that works (after a lot of pain for the second point, and thanks to the active help of Alex for quickly improving FEST "specially" for me), I can finally resume my work on the project.

Where do we stand?

Version 1.2, which I am currently working on, shall have the following features:
  1. alignment synchronization of multiple panels (all using DesignGridLayout): particularly useful to make smooth panels transitions in Tab panes and Wizards.
  2. optional consistent baselines spacing between consecutive rows (only for fixed height rows).
  3. addition of a new API to notify DesignGridLayout of special components behavior with regards to vertical resize.
For these features, I have already made a big refactoring of the source code.

Enhancement #2 above is already implemented in Subversion trunk, here are 2 screenshots comparing the same layout with and without consistent baseline spacing, respectively:

Note the consistent spacing between labels baselines for all rows but the second one (because it has variable height) and the last one (because it has an emptyRow() before it to visually separate that row from the previous one).

Without consistent baselines spacing, all vertical gaps conform to the values provided by the installed look & feel, but due to different heights of components and different vertical gaps, the inter-row spacing doesn't look consistent and is not very eye-friendly (this is particularly true when observing the column of labels).

Regarding synchronization of alignments between different panels (enhancement #1 above), the following screenshots help showing what works today (excerpts from DesignGridLayout automatic tests).

The example below is a dialog with a tabbed pane, embedding two different tabs. The first 2 screenshots show both tabs, built with DesignGridLayout, without synchronization.

Please note that there are no horizontal or vertical alignments between both tabs, this is particularly noticeable when running the application and changing from one tab to the other.

Now, let's add just one line of code to the layout building code, as follows:
Synchronizer.synchronize(layout1, layout2).alignGrids().alignRows();
Then the screenshots become different (note that I have put the 2nd tab both on the right side and on the bottom of the first tab so that you can easily check alignments in both directions):

You can notice the alignment of the origins of both columns of fields, and you can also check that all rows baselines from both tabs are aligned with one another.
This example is particularly interesting due to the use of Java -default- Metal look & feel, in which different kinds of components have different heights (compare the heights of JTextField and JComboBox); other look & feels may have consistent heights for all components, which would not show the difficulty of aligning each and every row from one tab to its matching row in the other tab. I am not sure other LayoutManagers can do that, I haven't checked yet though.

Back to DesignGridLayout V1.2 schedule, I still have some work with:
  • baseline synchronization
  • new API for vertical resize behavior customization (point #3 above)
  • layout calculation optimization (avoid multiple recalculation)
For DesignGridLayout 1.2, I plan to release a beta with new features and the new API so that I have time to polish it before a release candidate. Here is the expected schedule:
  • End May: 1.2 beta 1 out, beta testing will last one month
  • End June: 1.2 rc 1 out, followed by final release after 2 weeks (or 2 weeks after latest rc if bugs are reported)
What's next?

Later, for V1.3, I foresee enhancements to non-grid rows API & functionality:
  • possibility to separate 2 components with unrelated gaps
  • smart positioning of standard buttons (OK, Cancel...) based on the runtime platform
  • make component sizes consistent across several rows (currently, sizes are consistent inside individual rows only)
It's hard to predict what a release date could be, but since I don't see too much complexity in these features (but as usual, the difficulty will be in finding the right API for them), I think it should be possible to have a first release candidate less than 2 months after final release of V1.2, around September-October.

Friday, April 24, 2009

Bean properties without hard-coded names?

The problem

Java Beans specifications have been around for more than a decade and, although they were good for tools (IDE), their full capabilities (bound properties in particular) were not much used in 3nd party libraries until only recently (e.g. JSR-295 "Beans binding", JSR-303 "Beans Validation", Glazed Lists...)

I have never been a big fan of Bean properties for many reasons:
  1. they incur a lot of boilerplate code (in particular for bound properties)
  2. there's no way to refer to a bean property by anything else than its name, a hard-coded String, hence this doesn't bear refactoring
  3. they offer no compile-time safety (for the same reason as above, e.g. how can you be sure that "SSID" property is a String, it might be an int, or even an instance of a custom class!)
OK, I know some points are easily worked around:
  • Issue 1, for instance, is not a problem for most IDE: they will generate getter/setter for all your properties in no time; however, you have to remember that software maintenance cost increases with the number of lines of code to maintain (whether manually produced or automatically generated).
  • Refactoring of properties (issue 2) can be correctly handled by your IDE as well (with just some little extra effort)
Nevertheless, I see no workaround to issue 3, no IDE -as far as I know- will check that a hard-coded name refers to a property of some given type!

For developers working on Swing applications in particular (but developers in other architectures may be concerned as well), beans usage is a must, so they face those issues everyday.

The solution

I will further demonstrate a proof of concept of how we can solve issues 2 & 3 above. The complete prototype also includes a solution to issue 1 (i.e. transforming "normal" bean properties into bound properties) but I won't discuss it here because many solutions have been blogged about for a couple of years. There's even an example of that in cglib snippets.

Before getting to the solution, I'd like to describe how I came to it. It's quite simple. For unit testing, I like to use EasyMock. In EasyMock, here is how you create a mock and define its expectations:
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.createMock;
...
CustomerService mock = createMock(CustomerService.class);
expect(mock.getNextAppointment()).andReturn(new Date());
...

What happens here is that EasyMock generates a mock implementation of CustomerService interface so that any call to any method is recorded. Then the generic method expect(T) allows EasyMock to have compile-time safety in the andReturn(T) call.

Hence I had the thought of using the same kind of API to get access to bean property:
import static net.sf.beanutils.BeanUtils.mock;
import static net.sf.beanutils.BeanUtils.property;
import net.sf.beanutils.Property;
...
MyBean mock = mock(MyBean.class);
Property<MyBean, String> property = property(mock.getCustomerId());
System.out.println(property.name());

Here, I would introduce a specific generic class Property<T, U> that encapsulates description of a given property of a specific bean class.

Of course, in the example above, you've probably seen that -technically- this is different from EasyMock because EasyMock deals with interfaces while we have to deal with beans (non abstract classes). But there is an EasyMock extension that supports just that, it is based on cglib.

I have decided to also use cglib for my proof of concept, because its API seems quite easy and I found the provided examples quite straightforward.

First of all, let me introduce Bean<T> class, which is the main factory for Property instances of a given bean:
public class Bean<T>
{
// The only way to get a Bean<T> instance is to use this factory method
public static <T> Bean<T> create(Class<T> clazz) {...}

// Initializes all members (uses cglib)
protected Bean(Class<T> clazz)
{
_clazz = clazz;
_properties = ReflectUtils.getBeanProperties(_clazz);
_mockInterceptor = new MockInterceptor(_properties);

// Create a mock immediately with cglib
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(_mockInterceptor);
_mock = clazz.cast(enhancer.create());
}

public Class<T> type() {...}

// Returns a T mock, used in conjunction with property() method (as argument)
public T mock() {...}

// Returns a beans property reference without using its hard-coded string name
// This pattern will always survive bean refactoring (compile-safe)!
// The returned reference can be used to get/set property value or get its
// name (in a safe way)
public<U> Property<T, U> property(U mockCall)
{
PropertyDescriptor property = _mockInterceptor.lastUsedProperty();
checkType(mockCall, property);
return Property.create(property);
}

// Create a new bean that delegates to this one but makes all its
// properties bound
public T proxy(T source) {...}

// Global cache of Bean objects (each class T should have only one
// Bean<T> instance)
private static final Map<Class<?>, Bean<?>> _cache =
new HashMap<Class<?>, Bean<?>>();

private final Class<T> _clazz;
private final PropertyDescriptor[] _properties;
private final MockInterceptor _mockInterceptor;
private final T _mock;
}

Only the relevant methods & API are shown above. The "meat" of the code is essentially in Bean<T> constructor and the property() method. The proxy() method is not shown here but is also interesting.

The second interesting piece of code is the MockInterceptor class, which is automatically called by cglib for any call to a method of _mock:
class MockInterceptor implements MethodInterceptor
{
public MockInterceptor(PropertyDescriptor[] properties)
{
_properties = properties;
}

public PropertyDescriptor lastUsedProperty()
{
PropertyDescriptor property = _lastUsedProperty;
_lastUsedProperty = null;
return property;
}

public Object intercept(
Object target, Method method, Object[] args, MethodProxy proxy)
throws Throwable
{
_lastUsedProperty = null;
// Check this is a getter
for (PropertyDescriptor descriptor: _properties)
{
if (method.equals(descriptor.getReadMethod()))
{
_lastUsedProperty = descriptor;
break;
}
}
//TODO try to return something non-null when possible
//TODO should we call super method if not abstract of course)?
return null;
}

private final PropertyDescriptor[] _properties;
//FIXME should be in a ThreadLocal no?
private PropertyDescriptor _lastUsedProperty = null;
}

The principles are quite simple actually: every time a getter of _mock is called, the matching java.beans.PropertyDescriptor is stored in _lastUsedProperty. The latest is used in Bean<T>.property() method to create a new Property<T, U> instance:
public class Property<T, U>
{
static<T, U> Property<T, U> create(PropertyDescriptor descriptor)
{
return new Property<T, U>(descriptor);
}

protected Property(PropertyDescriptor descriptor)
{
_descriptor = descriptor;
}

public Class<?> type() {...}
public U get(T bean) {...}
public void set(T bean, U value) {...}

public String name()
{
return _descriptor.getName();
}

private final PropertyDescriptor _descriptor;
}

As you can see, Property<T, U> is merely a wrapper to a java.beans.PropertyDescriptor instance, with additional type information (T: type of the bean, U: type of the bean property) added as generic parameters, making it typesafe.

With this little design, here is what we have achieved:
import static net.sf.beanutils.Bean.*;
...
Bean<MyBean> helper = create(MyBean.class);
MyBean mock = mock();
Property<MyBean, String> prop1 = helper.property(mock.getMyFirstProperty());
Property<MyBean, Integer> prop2 = helper.property(mock.getMySecondProperty());
...
System.out.println(prop1.name()); // prints "myFirstProperty"
MyBean bean = new MyBean();
prop1.set(bean, "Something");
prop2.set(bean, 123);
prop1.set(bean, 123); // Compile-time error!

This is not exactly like the foreseen use shown at the beginning of this post, but that's not very far!

Evaluation & Limitations

All initial issues have been solved:
  1. no boilerplate code for bound properties (performed by Bean<T>.proxy(T source) method)
  2. no need for hard-coded property names thanks to Property<T, U>.name() that will return the right name
  3. thanks to heavy use of Java 5 generics, this proof of concept provides compile-time safety

There are still some limitations with the current design:
  1. this works only with classes that comply to java-beans specifications (but that was a pre-requisite of the proof of concept)
  2. this won't work with final classes or final getters of bean classes (limitation of cglib)
  3. the current prototype is not thread-safe (that one is very easy to fix, with a ThreadLocal)
  4. write-only properties (no getter) are not supported (that could be added if needed)
  5. type-safety may be not guaranteed in case of misuse of the API (e.g. not following the usage example shown above)

Next?

The current proof of concept just demonstrated what was feasible. Some possible next steps would be:
  1. fix limitations #3 & #4 above (quite easy)
  2. add possibility to add/remove property listeners directly in Property<T, U> (would ease creation of new frameworks such as binding or validation)
  3. provide a work-around to limitation #2 (like logging a warning that some methods are final)
  4. check that generated proxy can work with Hibernate and Beans-bindings
  5. create an Open Source project somewhere (I thought about SourceForge which I am familiar with)

For step #5, I am not sure I am willing to start such a project alone (I already have a bunch of other OSS projects currently and I never can find enough time for them), so if someone is interested, send me a note!

You can find the complete project (maven 2 and eclipse) here.

Any comments are appreciated. Have fun with this prototype!

Sunday, February 15, 2009

Announce: DesignGridLayout 1.1 released!

Two weeks after the fourth release candidate of DesignGridLayout 1.1 (1.1-rc4), I have decided to release the official final 1.1 release.

Compared with previous official 1.0 release, this version brings the multiple rows span components feature, in addition to a few enhancement and bugs fixes:
  • fixed several problems with baseline alignment (only on Java 5): issues #3 and #27
  • fixed a problem with smart vertical resize of JList (issue #28)
  • fixed an exception with multi-grid feature (issue #26)
  • fixed an exception when creating a row but adding no component to it (issue #30)
  • fixed a bad look with grids having just a label but no component (issue #31)
  • optimized the size of the example jar (removed all screenshots that are used during automatic tests)
  • completely refactored the examples application
  • added an option to disable "smart vertical resize" feature (issue #34)
Now I can go back to work on next release (1.2). Future version 1.2 will have two major features:
  1. Synchronization of several layouts: with this feature it will be possible to ensure correct alignments (vertical or horizontal) across several panels using DesignGridLayout. This will be particularly useful for use inside JTabbedPanes or in Wizard dialogs.
  2. Extension API to customize policies related to components vertical resize (the current -internal- policy recognizes only vertical JSliders and JScrollPanes as variable height components, but some users have expressed the need to recognize also other specialized components)
First enhancement is on the way but will take some more time since it is a quite complex feature. I intend to release a 1.2-beta with this feature only, for users to check if it fits their needs well in synchronizing alignments. 1.2-beta is expected by mid-March if I can progress well.

If you're not afraid of using a "work-in-progress" version, you can get it from Subversion trunk, build it according to the instructions on the web site, then take a look at the new Synchronizer class.

I hope to have a first 1.2 release candidate by end of March or early April.

Saturday, February 07, 2009

JInternalFrame lesson learned

I am not a big fan of MDI (Multiple Document Interface) UI in general. I much prefer SDI or, better, UIs with docking capability.

Hence I don't have much experience with Swing JDesktopPane & JInternalFrame (the basic building bricks for creating an MDI UI in Swing).

However, a few days ago, an engineer from my company, developing a sample MDI UI (for an exercise related to a "GUI training"), was using DesignGridLayout LayoutManager and found a strange behavior in one of his windows, as demonstrated in the following screenshot, taken after a few pixels increase of the frame height (note the table overlapping the "Open" button):


The layout code is there:
UIHelper.addGroup(layout, "Criteria");
layout.row().grid(projectNameLabel).add(projectName).grid(projectNumberLabel).add(projectNumber);
layout.row().grid(workloadMinLabel).add(workloadMin).grid(workloadMaxLabel).add(workloadMax);
layout.row().grid(startDateFromLabel).add(startDateFrom).grid(startDateToLabel).add(startDateTo);
layout.row().center().add(searchButton, resetButton);

UIHelper.addGroup(layout, "Results");
layout.row().center().fill().add(projectScrollPane);
layout.row().center().add(openButton);
I found it strange because I had never faced this bug with DesignGridLayout before. Hence I have started investigating. The first surprise to me was that I could not reproduce this problem in a JFrame or a JDialog!

Hence I've written the simplest Test Case application, using JInternalFrame, to reproduce this problem:
JFrame mainFrame = new JFrame();
mainFrame.setName(getClass().getSimpleName());
mainFrame.setBounds(30, 30, 800, 600);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JDesktopPane desktop = new JDesktopPane();
mainFrame.setContentPane(desktop);
mainFrame.setVisible(true);

JInternalFrame frame = new JInternalFrame("", true, true, true, true);
frame.setLocation(30, 30);

JPanel top = new JPanel();
JTable table = new JTable(CONTENTS_PLAYERS, COLUMNS_PLAYERS);
setTableHeight(table, 3);
DesignGridLayout layout = new DesignGridLayout(top);
layout.row().center().fill().add(new JScrollPane(table));
layout.row().center().add(new JButton("OK"));

frame.setContentPane(top);
frame.pack();
desktop.add(frame);
frame.setVisible(true);
try
{
frame.setSelected(true);
}
catch (java.beans.PropertyVetoException e)
{
}
Here is the screenshot (at preferred size):

After a long debugging session, I found out that the preferred height of the JScrollPane in the above example was incorrect at the time Swing calls LayoutManager.preferredSize(); but when the frame is displayed (LayoutManager.layoutContainer() is called), the height of the JScrollPane is correct!

Further investigation (and pixels counting on the screen!) has shown me that during the call to pack(), the preferred height of the JScrollPane is equal to the preferred height of the embedded JTable, plus the JScrollPane borders, but it does not include the JTableHeader!

After checking Swing source code and trying to change slightly the code that packs and shows the JInternalFrame, I found out that the problem is that pack() is called too early!

The following change just removed the problem:
// Order of calling desktop.add() is important wrt frame.pack() call!
desktop.add(frame);
frame.pack();
//desktop.add(frame);
frame.setVisible(true);

The rant

This problem is specified nowhere in JDesktop or JInternalFrame javadoc! In addition, the JInternalFrame section of the Swing tutorial is totally unclear and even misleading about that point!

Well that's a lesson learned for me:
Always make sure you add JInternalFrame to the JDesktopPane before calling pack().

Event better: avoid MDI like the plague (I remember, a long time ago, being allergic to Swing MDI because of various other problems...)

Thursday, February 05, 2009

Why I do open source and how I came to it

As prompted by Alex Ruiz (himself prompted by Kirill), I will explain in this post my reasons for doing open source.

But as a way to explain my motivation, I will draw a history of my involvement into open source.
I have to admit that remembering that history was not easy, thanks Google I could find quite a lot of facts I had almost forgotten about my own past;-)

Let's start with my first projects, actually never open sourced, some even never released "in the wild"!
  • ObjectBase: never completed (only reached specification stage in 1993)
  • EasyMage: released in 1994 (C++, MacOS, shareware actually, source code never disclosed). At that time I developed this application as a passionate. And as a dreamer, I also hoped making some money out of it (IIRC, I did not pass over 100 licenses sold;-))
  • SCADA (C++ & Java 1.2, around year 1997-2000): never released. But this project helped me a lot learning Java along with CORBA.
  • CORBA WhiteBoard (Java 1.4, around years 2000-2003): never released. But this project gave me more experience with Java Swing. And more impotantly, it has served as inspiration roots for my second Open Source project, HiveBoard.
All projects above have brought me a lot in terms of pure technical knowledge (C++ & Java languages, CORBA technologies, Swing GUIs). However, the whole process to implement these projects was quite awkward. Open Source has helped me getting more "professional" in the whole software lifecycle (in particular in the areas of automated testing, planning, issues management).

Now let's go further into my progressive involvement into Open Source. First of all, all projects above were developed with a spirit of "implement everything from scratch" (the DRY principle did not really exist in these times;-) and using only little third-party -open source- libraries)

My first involvement in Open Source can be traced back to 1999, it was quite limited: I have reported bugs to the omniORB library and then contributed some answers to questions in the forum. I also evangelized omniORB to my students in 1999 (I was a part-time IT professor in Vietnam at that time).

This may not look much but actually, this is very important in Open Source: contribution doesn't have to be just committing code, but also using open source code in your own projects, reporting problems, suggesting enhancements, helping other users when you can, evangelizing the products you use and like...

At that period, I have also contributed problems reports to ORBACUS/Java (which was open source at that time).

Then, much later on (mid 2004), at this time Spring started to take off seriously, I was interested in Dependency Injection but hated Spring because of its too verbose XML configuration. Then I discovered HiveMind (it was in beta at that time) and found it much more interesting than Spring. However, it was lacking all that Spring had which is not DI-related (integration with persistence libraries in particular). I found HiveMind so good a DI framework that I wanted to evangelize it, but it always failed in comparison to Spring because of the lack of 3rd-party integration. That's why I decided, late 2004, to start HiveTranse my first OSS project. Unfortunately it seems it didn't help wider adoption of HiveMind. But at least it has served me to start my second OSS project, HiveBoard, at the end of 2004.

Both projects have really absorbed my time during several years and they have helped me improving my skills in:
  • Java technical area: I started to get acquainted with Hibernate, iBatis, securityfilter... I also worked with useful products like Tomcat and Jetty.
  • Software lifecycle process: I have learned to use tools to help me build (ant, el4ant), and test (JUnit, jmock, dbunit, abbot). I also got used to managing issues. I had to perform quite a bunch of efforts in terms of documentation (good for changing a "standard" developer's bad habits;-))
  • Soft skills: when you commit into Open Source, you have to be careful about not growing too big an ego, and rather be humble towards the mistakes you make (and the remarks you get about these mistakes). You have to listen to what others tell you and accept criticism on your project. In other words, you have to "open up", this is one quality I believe I have grown during my Open Source years (although I am far from perfect to this regard;-))
More recently (mid 2008), I have joined the DesignGridLayout project. The reason why I did so was that because I found this library excellent, I had started to use it in HiveBoard, and I wanted to "give back" some of my efforts to the community, from which I had already benefited a lot (I would be very embarrassed to enumerate the exhaustive list of OSS libraries and tools I have use in the past 10 years).

What I have learned from DesignGridLayout is:
  • how to design a good API (I believe)
  • how to "market" the project (although the results don't seem compelling so far)
  • how to keep my cool (or at least, try to;-)) when facing some "attacks" from other people
I consider the third point important because, although Open Source contribution is generally very rewarding, it can also bring you some problems you would never have imagined. I consider it a positive point however: most often, attacks arise from some fear of competition, which in a way means that what I have done so far is probably not too bad;-) Anyway, I believe the only response to attacks is "go ahead, don't listen, try to do better and show it".

To sum up with the reasons I do open source:
  • learn more about new technologies (very important in our profession where technology evolves at a fast pace) without any external constraint (you can choose the latest "cool stuff" to try and integrate in your work)
  • learn more about tools to help the release management process (these tools I can later on push them in a professional environment in my daily job)
  • give back to the community what the community has given me
  • hopefully help the others by giving away something you consider useful to other people
  • get some visibility (open sourcing the projects you originally used for yourself brings some extra "cost" in terms of time, so you definitely want to have these efforts "paid back": the reward here is the kind comments on your projects, the bugs reports, the suggestions for improvements...)
  • get some "technical respect" in my daily job (engineers in my company respect me, not just because I'm older than them and I am their boss, but because they can see that I have a proven record of technical background and I am not just a stupid manager who understands nothing to software development)
To summarize further I could reduce this to just one word "Passion" of everything related to IT (not just coding but all tools and practices around software engineering).

Actually I think that's not very far from what Kirill and Alex have mentioned: such a long post to say the same as others already have! Promised, next time I blog I'll try to talk only about new stuff;-)

For those who have read until there to see who would be the next ones to be prompted for the reasons they do open source, sorry guys, you have read all my prose for nothing, I have nobody left on my list (or contrarily I have potentially too many to enumerate);-)

Thursday, January 15, 2009

Swing UI layout: best practices

Today, I will show my best practices about designing Swing UI forms.

This post focuses exclusively on UI layout, not on other -common- UI problems such as MVC, binding, actions... I may post about all these in future posts however.

This is quite a long post, but this is partly due to screenshots showing do's and don'ts.

The best practices exposed here use my DesignGridLayout as an example, but most of them (if not all) should be suitable for most modern LayoutManagers (like GroupLayout) and even some old-fashioned ones (like GridBagLayout).

For each practice, I'll show screenshots before/after the practice along with code snippets.

Code presented here works with Java 5 (baseline alignment may require the additional swing-layout library) and Java 6.

1. Always use Baseline alignment for components that have a meaningful baseline
Most modern LayoutManagers have an option to align components in a row on their baselines.
DesignGridLayout gives you no choice: baseline alignment is automatic (and cannot be disabled).

Without baseline alignment

With baseline alignment


2. Avoid using JEditorPane and JTextPane
Most Swing components in Java 6 have a meaningful baseline. However, some seem to have no correct baseline (for no apparent good reason). JEditorPane and JTextPane are examples of such components. This means that it is impossible to have these components aligned on their baseline.
Thus, when using those components, you don't know exactly how they will be aligned with other components in the same row; this will depend on the LayoutManager you use.
DesignGridLayout aligns these components on the top of their "box", which is not very beautiful, but nothing better is possible until those components are able to return a decent baseline value.

Screenshot sample

Hence the best is to not use such components at all if possible. You should prefer JTextArea when it fits your needs (no need for rich text style).

3. Call setColumns() on JTextField and JTextArea
Most LayoutManagers use (or can use) components preferred size to perform an optimal layout.
In Swing, many components are able to determine the optimal preferred width based on their content. For instance, JLabel, JButton, JList, JTable belong to this category and for these you don't need to explicitly set the preferred width.
For JTextField and JTextArea, however, this is not the case, by calling setColumns() (or using the constructor that takes a columns argument), you make sure these components will have the right preferred width.

Without setColumns()

With setColumns(10)

4. Put all components that can vary in height in a JScrollPane
Some components allow you to display several "lines" of information (JTable, JList, JTextArea...) In most cases, it is impossible to know exactly how many lines will be displayed. Hence by putting those components in a JScrollPane you make sure the user will be able to vertically scroll to see all available data.
In addition, some components (such JTextArea) were specifically coded to be embedded in a JScrollPane, if you don't, they will look ugly (e.g. no border).

Without JScrollPane

With JScrollPane

5. Call setRows() on JTextArea
Like setColumns(), it will help optimize the preferred height for the component (by default its preferred height is equivalent to the height of 1 line of text).

Without setRows()
Note how the JTextArea looks like a simple JTextField!

With setRows(4)

6. Call setVisibleRowCount() on JList
As for JTextArea where you should call setRows(), JList has a useful method that enables you to set the preferred number of rows to be visible, which will then automatically compute the preferred height (depending on the actual content of those rows).

Without setVisibleRowCount()

With setVisibleRowCount(4)

7. Call setPreferredScrollableViewportSize() on JTable
Unfortunately, JTable does not have a setVisibleRowCount() method as in JList. Hence, you need to find another way to set a preferred size (in terms of number of rows) to the JTable but avoid that it displays "partial" rows. The default JTable preferred height shows 20 rows (independently of the actual number of rows in the model) which is generally more than what you would want to show.

The practice I show here has worked quite well for me:
static public void setTableHeight(JTable table, int rows)
{
int width = table.getPreferredSize().width;
int height = rows * table.getRowHeight();
table.setPreferredScrollableViewportSize(new Dimension(width, height));
}

Without setPreferredScrollableViewportSize()

With setPreferredScrollableViewportSize()

8. Force minimum width on JTextField
In best practice 3 above, I have shown how to set a correct preferred width for JTextField, this will allow the LayoutManager to show the panel in its preferred size with correct sizes for all fields. However, when resizing the panel, the minimum size is generally used by the LayoutManager to make sure that components never shrink smaller than this minimum size.

Unfortunately, JTextField minimum width is meaningless and generally needs to be set by hand to avoid ridiculously small fields when the user shrinks the panel width.

However, you should always avoid setting sizes in pixels to avoid bad layouts on different kinds of monitors (you should strive to be resolution independent so that your UI will look good on low and high DPI screens).

What I do is to use setColumns() again but as an intermediate step to setting the minimum width:
static public final void setTextField(JTextField field, int min, int pref)
{
field.setColumns(min);
field.setMinimumSize(field.getPreferredSize());
if (pref != min)
{
field.setColumns(pref);
}
}

Before (trying to shrink width as much as possible)

After

9. Don't use TitledBorder to separate groups of information
A lot of people use Swing TitledBorder around several sub-panels in order to separate groups of information inside a form. The major problem with this approach is that every sub-panel has its own LayoutManager, and LayoutManagers are disconnected from each other, hence you are likely to have bad alignment between sub-panels:


If you follow Karsten Lentszch's advice, you could use a JLabel and a JSeparator instead:


Here is how you can do it with DesignGridLayout:
_lblInfo.setForeground(Color.BLUE);
layout.row().left().fill().add(_lblInfo, new JSeparator());
layout.row().grid(_lblFirstName).add(_firstName);
layout.row().grid(_lblSurname).add(_surname);

layout.emptyRow();
_lblOffice.setForeground(Color.BLUE);
layout.row().left().fill().add(_lblOffice, new JSeparator());
layout.row().grid(_lblCompany).add(_company);
layout.row().grid(_lblAddress).add(_address);
layout.row().grid(_lblZip).add(_zip);
layout.row().grid(_lblCity).add(_city);

10. Set consistent sizes for all JButtons in a row
Swing automatically calculates JButton preferred size based on its content (text, icon). However, this means that all buttons in your form will have a different width!
Depending on the LayoutManager you use, you may have to individually set the preferred sizes of all buttons, based on the preferred size of the largest one.

Most modern LayoutManagers will do that for you, though. Here is an example with DesignGridLayout:
layout.row().center().add(new JButton("OK"), new JButton("Cancel"));


11. Special considerations for components spanning several rows
Some LayoutManagers (including DesignGridLayout) allow you to define components (like JList or JTable) to span several rows.

When I use such components, I make sure that their preferred height (which defines the height of the JScrollPane in which they will be embedded) is larger than the total height of the rows that are spanned. Why so? Just a matter of taste. See for yourself:

With height smaller than spanned rows

With height larger than spanned rows

Conclusion

For some of these best practices, it may prove useful to create a class with a few helper methods (such as setTableHeight() and setTextField() above) or create a factory for your components.

If you follow those best practices, you should achieve a better user experience in your UI forms. DesignGridLayout, if you use it, will take advantage of these best practices in an effective way.

Hope that this can be useful to all Swing developers. Any comments are welcome.

Wednesday, January 07, 2009

Announce: DesignGridLayout 1.1-rc1 released!

After one month of heavy work, I am proud to announce the first release candidate of DesignGridLayout 1.1.

This version brings one major new feature and fixes a few bugs:
  • new support for components to span several rows (RFE #10)
  • fixed problems with baseline alignment in JRE5 (issues #3 and #27)
  • fixed a problem with smart vertical resize of JList (issue #28)
  • fixed a potential exception that could occur in very specific layouts (issue #26)
In addition, the examples demo application has been completely rewritten in order to show all DesignGridLayout features along with description and source code. This application can now constitute a very effective way to learn DesignGridLayout from scratch in no time. It is also useful to current DesignGridLayout users who want to learn new features.

The new support for components spanning multiple rows allows you to define layouts that look like this:


The source code for that is quite straightforward:
layout.row().grid(label1).add(field1).grid(label2).add(list);
layout.row().grid(label3).add(field3).grid().spanRow();
layout.row().center().add(button);
It is important to notice that "smart vertical resize", one of DesignGridLayout unique features, is still active on components spanning multiple rows. You can see on the following screenshots the same layout as above during vertical resize (note the list always show only entire rows and never truncates any row):





Of course, you can also see this behavior "live" if you launch the examples application!

I consider this release candidate to be ready for production and, if no bugs are reported, I expect a final release in less than one month.

Enjoy!

Friday, January 02, 2009

My resolutions for 2009

Nowadays almost everybody feels the urge to claim their private resolutions for the new year that has just started.

Although I don't really feel with the same urge, I think expressing my own resolutions for 2009 in this blog will put some pressure on me to try my best following those resolutions, else one could remind these to me in one year ;-). I could also refer to this post myself to remind me what I promised myself

So here they are (in no particular order):
  • release 2 versions of DesignGridLayout (1.1 and 1.2)
  • release -finally- my guice-gui framework
  • learn how to play the guitar at least 15 minutes per day
  • learn and play with groovy (buy and read "Groovy in Action")
  • play with grails (and write some new Open Source project with it)
  • play with griffon (and see how I could possibly integrate DesignGridLayout to it;-))
  • read "Programming in Scala"
  • spend more time with my family (this resolution is quite incompatible with all others...)
  • resume my work on HiveBoard and release one new version during the year
  • don't ever download JavaFx or even mention it in any of my blog posts (damn I have just broken that resolution, OK, that's the last one)
From this, you can guess more or less where I am heading for this year.

Thursday, January 01, 2009

DesignGridLayout: row-span support soon ready!

December 2008 has been a busy month for me on DesignGridLayout.

Although not yet completely ready for a first 1.1 release candidate, the current Subversion trunk already includes some interesting changes:
  • added support for components spanning multiple rows (issue #10): this is further discussed below
  • fixed several problems with baseline alignment (only on Java 5): issues #3 and #27
  • fixed a problem with smart vertical resize of JList (issue #28)
  • fixed an exception with multi-grid feature (issue #26)
  • optimized the size of the example jar (removed all screenshots that are used during automatic tests)
  • completely refactored the examples application: see below

New showcase application

Much of my past efforts on DesignGridLayout were spent on reworking the original examples application which was overly simplistic and not a very good way to "sell" DesignGridLayout. Hence I have written from scratch a new showdown application that:
  • demonstrates all DesignGridLayout features from the basics to the more advanced uses (arranged in a tutorial-oriented way)
  • describes each feature
  • always shows the relevant source code that produces every sample
  • allows you to launch the layout in a separate frame in order to test its resize behavior
  • asks you to choose your preferred Look & Feel so that you are not limited to the old-fashioned default Metal look & feel
Here is a screenshot of this new showdown application (with Windows XP look & feel):


Now the good news is that, although there is no official release of this new DesignGridLayout application, you can already launch it today! Just click the jnlp link below:


Multiple row span

This is the main improvement to the future DesignGridLayout 1.1.
I had to slightly change the API (completely backward compatible with 1.0), as described in one previous post on this blog. As a reminder, here is a simple example:
layout.row().grid(label("lbl11")).add(field("field11")).grid(label("lbl12")).add(list());
layout.row().grid(label("lbl21")).add(field("field21")).grid().spanRow();
layout.row().center().add(button());

Note the spanRow() call on the second line of code. This states that "in this position of the current row, span the component that is in the same position on the row above".

The result looks like this:


Please note there's a catch in this snapshot: the list size does not obey the "smart vertical resize" feature of DesignGridLayout (first introduced here). That's one open point in the current source code.

This open point is to decide whether the layout should keep the "smart height" for a multi row span component or not:
  • if we don't keep it, then at preferred size, the bottom border of the list is aligned with the bottom border of the field (example above)
  • if we keep it, then there won't be such alignment, which may look quite ugly as in the snapshot below


I am currently inferring about what to do here. I wonder if this should be left as an option (new API) to the end user. Among the available options:
  1. disable smart vertical size for multi row span components (current situation)
  2. enable smart height with loss of bottom border alignment (as in snapshot above)
  3. enable smart height but keep bottom border alignment (or baseline alignment with the last visible row of the list). This means that there will be a strange vertical spacing between first and second field. I am not even sure that this is feasible actually, I'll need to investigate further.
Additional decisions would concern the form of the API to select the behavior, in particular, I have to find out a light API (that does not make the current one heavier) and which granularity to give this API, more precisely, at which level the behavior should be selectable: the whole layout, each row, or every single multi row span component? The finer the level, the heavier the API, the more complex it is to use...

The behavior of spanRow() is described in the future 1.1 javadoc (not released, but you can already build it from the Subversion trunk).

A second catch (not visible in the example above) with the "spanRow()" API is that, unfortunately, it is now possible to code (and compile) a layout that cannot be rendered at runtime, because it just makes no sense, as in the following example:
// spanrow() called on a subgrid with different number of columns
layout.row().grid(label("lbl1")).add(field("field1"), field("field2"), field("field3"));
layout.row().grid(label("lbl4")).add(field("field4")).spanRow();

The spanRow() call on the second row does not match a single component of the first row, but there are 2 candidate components to be spanned: field2 and field3.

Since there is no way to discover this problem at compile-time, DesignGridLayout performs the check at runtime and will replace each incorrect spanRow() call with a special "marker" component (with a tooltip that gives further information) to show that the original source code has to be reworked.

The following screenshot shows many examples of incorrect API usage:


This screen is also available from the showdown application.

Roadmap

Once I can progress on the open point presented above, I will be able to propose a first release candidate of version 1.1. That should be ready by mid-january hopefully. The final 1.1 version will be released 3-4 weeks after the latest stable (no open bug) release candidate.

Enjoy!