Showing posts with label Swing. Show all posts
Showing posts with label Swing. Show all posts

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, 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!

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!

Saturday, December 27, 2008

Should we fork swing-layout project?

The swing-layout project was the initial effort to bring Swing a better layout system that would in particular take into account the specificities of the installed Look & Feel, alongside baseline alignment support.
Swing-layout works with Java5 (maybe Java1.4, I don't know, I have never checked).

This is where the new Java6 GroupLayout has been elaborated before integration into Java6.

Besides bringing a new LayoutManager that provides better layouts (at the expense of higher complexity in use, except if you use NetBeans Matisse designer), swing-layout also brought utilities available to other third-party layouts, in particular the aforementioned baseline support.

DesignGridLayout is one of those LayoutManagers that relies on swing-layout for baseline alignment. It is also one of my Open Source projects.

Swing-layout was available before Java 6 and, of course, the release of Java 6 has made it somewhat irrelevant (at least for the rare developers who could jump to Java 6 immediately).

Now it is strange to discover that Java 6 baseline support is better than swing-layout itself (when using Java 5). Indeed, the swing-layout project on java.net has been left in limbo for about 2 years, and it looks nobody is really responsible for it, several issues are still open and nobody cares!

So what should happen to this project? Should it definitly be buried in favor of Java 6?
Or, stated differently:
Do we necessarily need to upgrade to Java 6 to have good Swing layouts?

I don't think so! Java 5 is still mainstream nowadays, and it is planned to reach EOSL in one year (that's still some amout of time!). So I strongly believe Java 5 users should not be left behind.

Why do I talk about this topic here? As a matter of fact, I have hit swing-layout bugs in DesignGridLayout and I am facing a tough decision:

Should I leave DesignGridLayout Java 5 support behind and require Java 6 as a minimum?

Or should I try my best to keep Java 5 support -with no difference in the provided features- at least for one more year?

If so, how should I deal with swing-layout bugs?

One idea I had was to fork the swing-layout project and create my own, with the same license (LGPL). Although it seemed to me a good idea, the problems I got with this is that:
  • I don't have the facilities to test all cases (MacOSX, Linux GTK)
  • I don't have much time left for Open Source (I already have 4 OSS projects and I can't deal with them all, I always have to put one in top priority -currently DesignGridLayout- while the others have to wait, sometimes for several months)
  • I don't want to support GroupLayout which I don't use and which is a competitor of my own DesignGridLayout!
Indeed, yesterday I have refactored the whole baseline support in swing-layout (because currently everything is in one huge class with a lot of terrible code, very difficult to maintain and extend). I have fixed the two problems I have in DesignGridLayout (JScrollPane and JTableHeader baselines). This works but I don't know yet if I'll keep it (because it is LGPL and DesignGridLayout is Apache License 2) as part of DesignGridLayout source code.

Thus I think that, unfortunately, I'll have to throw away this code (is there someone interested out there?) and try to stick with the "official" swing-layout release and find some workaround that can be implemented directly in DesignGridLayout as a caller (no license incompatibility).

Of course, if someone is motivated and ready to take over the effort of forking swing-layout, then I would happily give back my work to that new project. Just drop me a note.

The rant

Now the status of this library, sponsored by Sun, reminds me of other "currently on-going" (:->) efforts such as: JSR-295 (beans-binding), JSR-296 (Swing Application Framework).
Once again, although Sun claims they don't leave Swing behind, they actually do, in favor of that half-baked JavaFX thingy, which is not even comparable to its competitors, which we may wonder if it deserves the "1.0" version number. Layout support in JavaFX made me laugh big times (it all boils down to HBox and VBox).

Maybe it's time to start forgetting Java (and Swing?) and learn something new (anything but JavaFX).

Sunday, December 07, 2008

Announce: DesignGridLayout 1.0 released!

One month after the third release candidate of DesignGridLayout 1.0 (1.0-rc3), no new bugs being received, I have decided to release the official 1.0 version of DesignGridLayout.

I trust this version to be stable and bug-free (however, if you do encounter a bug with it, do not hesitate to report it, I will be glad to provide a fix in a timely manner).

For me, this means I can now start seriously working on the next enhancement (for 1.1 version) that will allow users to define some components spanning several rows. You can already have an overview of the future API (up to the current state of my reflection) if you are interested.

If you have further questions about DesignGridLayout, please don't hesitate to ask in the corresponding project lists.

Sunday, November 16, 2008

My presentation on JSR-296 at Jazoon 08

I have just seen that Jazoon has removed all presentations files of Jazoon 08 talks.
That's the occasion for me to post the content of my talk on "JSR-296 Swing AppFramework from the trenches".

The full content (in PPT format) is here (I have just removed the Jazoon template stuff). If you don't have PowerPoint, here is a PDF version (unfortunately without the original animations). Unfortunately I could find no way to convert it to ODP format (for OpenOffice users) without big losses in animations but also in diagrams (most of them got terribly ugly after conversion). I am afraid I'll have to give up on that one (if anyone can convert the original PPT to ODP please email me the result and I'll post it here).

If you want to have an idea of the content before download, here is the agenda:
  • Introduction
    • why do we need a Swing framework
    • what is JSR-296? what does it bring (in a nutshell)?
    • about the presented applications
  • Application Lifecycle
    • standard application lifecycle
    • adding dependency injection (Spring, Hivemind, Guice)
    • adding docking capability (MyDoggy)
    • tips & pitfalls
  • Resources & I18N
    • resource injection principles
    • what about JTable?
    • tips & pitfalls
  • Actions & Tasks
    • a better, more responsive, javax.swing.Action
    • where should I put my @Action?
    • when should I use a Task?
    • what about Exception handling?
    • tips & pitfalls
  • Persistent Session State
    • state restoration (or users' happiness)
    • what is stored? what is not? where?
    • tips & pitfalls
  • What is missing for a complete GUI framework?
    • GUI framework architecture
    • Example: EL4J Swing stack
  • Future
    • JSR-296 standpoint
    • what's likely to change?
Please note that, considering the recent events about Sun in general, and Swing in particular, the "Future" section of the presentation is probably largely obsolete:-(
Still, I believe that Swing is a better platform for GUI development than many others (not just talking about Java here), and developers will still use it for a couple of years more (maybe not in pure Java, though, other options exist: Groovy, Scala). Wait and see...

Enjoy!

Wednesday, November 05, 2008

Announce: DesignGridLayout 1.0-rc1 released!

I am particulaly glad to announce the first release candidate of DesignGridLayout V1.0.

DesignGridLayout is a Swing LayoutManager, revolutionary by its API, simple but powerful. Its main advantages are:
  • Good looking forms (alignment, spacing, sizing, visual balance): this is entirely taken over by DesignGridLayout itself without any special hint from the developer
  • Reduced learning curve for developers, thanks to its fluent API which is simple, effective, compile-safe and IDE code-completion friendly
  • No graphics designer needed: the API is the graphical designer
  • Readability and maintainability: you can literally "visualize" the layout by browsing the code that sets it up; inserting a new row of components is done by simply inserting a new line of code in the layout setup code...
  • Free: the project is open source and released under Apache License 2.0
Version 1.0-rc1 is available here or through the java net maven 2 repository (for more info, you can check my previous post and replace "0.9" with "1.0-rc1").
This version brings the following improvements:
  • #13: support for multiple groups of fields, each with its own label column
  • #5: smart vertical resize: DesignGridLayout automatically determines which rows should grow vertically and also make sure that components height is suitable to display an entire line of information (useful for JList, JTable, JTextArea)
  • #18: smarter horizontal resize behavior: now DesignGridLayout won't resize components under their minimum size
  • #9: automatic support of right-to-left text orientation based on Locale
  • #16: smarter gaps for empty rows
  • #15: resolution independence
  • #20: fixed ugly layout problem when container has a border
  • #12: now setLayout() is automatically called by DesignGridLayout constructor
Please note that V1.0 required API changes that I could unfortunately not keep compatible with previous 0.9 release. This should be the last time that happens (V1.1 should only extend the current API).

I consider the current version ready for production as the current test suite of DesignGridLayout is quite comprehensive and covers all its features.
However, I have decided to prepare a release candidate to give myself a chance to fix any problems that users may find but that I could not discover by myself (in particular, problems related to platforms that I don't have: MacOS-X, Linux, Solaris).

If needed, I will create further release candidates. I will wait about one month after a rc until I cut a final release.

What's next?
  • first of all, I'll get some rest;-)
  • then I'll spend some weeks on my other open source project, guice-gui
  • finally I'll start working on DesignGridLayout V1.1, which should include just one improvement (issue #10: support for components spanning several rows) which should be released as a Christmas present;-)
Enjoy it and don't hesitate to report any problems or enhancements!

Sunday, October 26, 2008

DesignGridLayout: final 1.0 soon released! Or not?

Today, I have finished fixing the last of the high priority issues of DesignGridLayout.
All fixes are already available on Subversion trunk, and also as snapshots there (note: it is not available through maven).

The past 3 weeks were quite tough in terms of issues fixing, with a lot of refactoring, improvements, and clean-up.

Now I am quite happy with the current standpoint about new features:
  • "smart vertical resize" (already discussed in this post)
  • automatic support of right-to-left orientation Locales (discussed there)
  • smarter -and simpler- API for empty rows (no need to specify a number of pixels: that's one step toward resolution independence)
  • better minimum size management (previously, DesignGridLayout would calculate a minimum size equal to the preferred size of the Container, but during very narrow resize, it would shrink each component to ridiculously small widths)
  • automatic call to setLayout() from DesignGridLayout constructor
  • last, but not least, smart support for forms with multiple label columns (this required some extension of the "spirit" of canonical grids, rising to the concept of "canonical sub-grids")
The concept of "multi-label grids" (or "canonical sub-grids" as I often name them) still needs to be introduced on the project web site (or on this blog) but that should not be a very heavy task.

So I consider it is soon time for an official 1.0 release!

Or is it?

I mean, there are still several open issues (enhancements only, not bugs), some of which might be worth having fixed in the 1.0 version. Of course, willing to fix these issues would then mean postponing the official 1.0 release (might be end November -or later, depending on how many issues we want solved- instead of early November).

So I ask the question here (I have also asked it there actually): what do you, DesignGridLayout users, prefer?
  1. Version 1.0 in about one week, all current issues open solved only in later versions?
  2. Version 1.0 with more issues closed (which ones?), but released later (between mid November and end December, depending on which issues to solve)
If you don't know, then I'll choose on my own (but I have to admit that I am not sure about what I prefer).

For the time being, even if you don't want to answer this question, you can always check the latest snapshot and report problems if you find any. Also, some of the new API method names I have chosen may be argued (I lacked inspiration for some), so don't hesitate to comment also on these (but please suggest a replacement for the new methods which name you don't like).

Enjoy!

Thursday, October 09, 2008

DesignGridLayout: real-time resizing of JScrollPane

Hi,

In my quest to solve the issue #5 of DesignGridLayout (namely: "Layout does not allow additional height usage after resize"), I have created a special dialog for testing my fixes for this issue. You can see a snapshot below (at default -ie preferred- size).


In this sample, I have put several rows, containing various kinds of components, of which some should be given extra height when the user resizes the dialog (eg JTable), and some should never grow taller than their preferred size (eg JTextField).

I could find out that there are only 2 categories of components that want extra height when it becomes available:
  • any Component that is set as the view of a JScrollPane (in particular JTextArea, JTable, JList)
  • any JSlider using JSlider.VERTICAL policy
Besides these, I did not find any component that should grow height when its embedding dialog is resized.

Based on these observations, I have implemented a simple internal mechanism into DesignGridLayout for distinguishing these 2 kinds of components:
interface HeightGrowPolicy
{
/**
* Checks if a {@link Component} can grow in height.
* @param component the component to test
* @return {@code true} if {@code component} has a variable height;
* {@code false} if {@code component} has a fixed height.
*/
public boolean canGrowHeight(Component component);
}
This interface is implemented by several classes:
  • HeightGrowPolicyMapper (allows to map different Component classes to their own specific HeightGrowPolicy),
  • JScrollPaneHeightGrowPolicy (special implementation for JScrollPane),
  • JSliderHeightGrowPolicy (special implementation for JSlider)
The mechanism itself is easily extensible because I can easily add further policies for other kinds of Components.

My first working prototype for issue #5 was roughly that simple (of course I also had to make some little changes in a few existing classes of DesignGridLayout library).

But I was not fully satisfied with it. The main reason for this was that when you extend the height of the dialog, all resizable components get a few pixels more for their height, which is absolutely ugly for a JTable, a JList or a JTextArea, because these components would then start to show, on the bottom side, some "incomplete" row or line of text.

Whenever I see this happening in a GUI application (be it made in Java or any other language) I generally get angry and immediately classify it in the category of "non professional" software.

So I inferred on some way to solve this: that is really the responsibility of a LayoutManager to make sure that whenever you resize a Container, all resized Components keep good-looking.

First I have extended the interface HeightGrowPolicy above:
interface HeightGrowPolicy
{
public boolean canGrowHeight(Component component);

/**
* Computes the maximum amount of extra height that a {@link Component} can
* use.
* @param component the component to test
* @param extraHeight the amount of available extra height
* @return the maximum amount of extra height that {@code component} can use
* without exceeding {@code extraHeight}
*/
public int computeExtraHeight(Component component, int extraHeight);
}
to give a chance to let DesignGridLayout know what amount of extra height a given Component will accept to keep its good look. This amount can be anything between 0 and extraHeight.

Here is the implementation for JScrollPane components:
class JScrollPaneHeightGrowPolicy implements HeightGrowPolicy
{
public boolean canGrowHeight(Component component)
{
return true;
}

public int computeExtraHeight(Component component, int extraHeight)
{
JScrollPane scroller = (JScrollPane) component;
int unit = scroller.getVerticalScrollBar().getUnitIncrement(+1);
if (unit <= 0)
{
return extraHeight;
}
else
{
// Return an integral number of units pixels
return (extraHeight / unit) * unit;
}
}
}

Simple isn't it? It makes use (behind the scenes) of the Scrollable interface that is most often implemented by components that are supposed to be used in JScrollPane (namely JList, JTable, JTextArea and JTree).

Provided that the preferred size of these components of your dialog is already good looking, then DesignGridLayout will ensure that they always stay this way. If you want to have proper preferred size for JTable, JList or JTextArea, you can use specific API of these components in your own code as in the following snippet:
JTextArea area = new JTextArea();
JTable table = new JTable();
JList list = new JList();

// area has preferred height to show exactly 3 lines of text
area.setRows(3);

// table has preferred height to show exactly 4 rows
int height = 4 * table.getRowHeight();
table.setPreferredScrollableViewportSize(new Dimension(PREF_WIDTH, height));

// list has preferred height to show exactly 2 items (ie 2 rows)
list.setVisibleRowCount(2);

You can try this on the Java Web Start enabled example.

Not bad!

But that's not the end of the story yet. If you play a bit with the example above, you'll see that between two actual resize of e.g. the first JTable component, the spacing between that row and the next is increasing, which is in contradiction with DesignGridLayout philosophy which promises to use the "ideal" inter-components spacing (according to the LAF/platform in use).

So I have made a second attempt, that you can experiment through this JWS example.

In this attempt, inter-components spacing is always preserved. However, to my viewpoint, resizing does not have a very good behavior (in real-time I mean):
  • first of all, the user does not "feel" that something is going on during the first pixels of his resizing action: the layout does not change at all! The user may just stop here and think that resize does not work!
  • second, a weird behavior is observed when the exact number of extra pixels is obtained during resize: all components below the first JTable seem to "hop" to a new position several dozens of pixels away from their previous one!
Hence I believe I will stick with the first solution and completely remove the second one. I could make it an option for the library users to choose, but I would not feel satisfied of giving such a possibility to create layouts with such a bad feel during resize.

What do you think?

If you want to look at DesignGridLayout code which snippets have been used in this post, you should check out the latest trunk from subversion.

Have fun!

Tuesday, September 30, 2008

Flash news: RTL support for DesignGridLayout

Tonight, I have just committed into Subversion the support of right-to-left orientation for DesignGridLayout. as a fix for issue #9.

Implementation was easier than foreseen, except for the test part itself (see below).

First of all, here are 2 screenshots with the same layout, but one uses LTR, the other uses RTL.





DesignGridLayout automatically discovers the orientation to be used for the container it is in charge of laying out, and simply inverts x coordinates if container is RTL-based.

Determining component orientation is based on the Component#getComponentOrientation() method which returns a ComponentOrientation instance. This instance determines the orientation of the text, it is not limited to horizontal LTR (eg English) and RTL (eg Arabic) languages, but also defines languages that are written vertically (all from top to bottom) and which "lines" (should we say "columns"?) are written from right to left (eg Chinese, Japanese) or left to right (eg Mongolian).

However, as far as I know, Swing components LAFs do not support components for vertical languages (I have never seen a vertical JLabel or JTextField), hence for these languages, we have to revert to the "usual" horizontal LTR orientation. For this, DesignGridLayout needs a simple trick to determine text orientation:
ComponentOrientation orientation = parent.getComponentOrientation();
boolean rtl = orientation.isHorizontal() && !orientation.isLeftToRight();

Just using orientation.isLeftToRight() is not sufficient because it would render eg Japanese horizontally from right to left, which I doubt any Japanese person can read (just imagine reading English from right to left!).

As mentioned above, implementing this enhancement was quite easy, but writing test cases for it was much more difficult than I expected.

My original idea for tests was just to set the default Locale to one of English, Arabic, Japanese or Mongolian (in order to cover the four possible situations defined in ComponentOrientation). But it turned out that just creating a new Locale through new Locale("JA") will work only if you have this Locale installed with your JRE, else a new Locale will be instantiated but it will be unusable; in particular, ComponentOrientation.getOrientation(Locale locale) will not return the expected value.

Since my JRE does not include Arabic, Hebrew, Japanese, Chinese and Mongolian, I had to find another way for testing. I chose to directly create a ComponentOrientation instance, but this is not possible since it has no public constructor and it is declared final! The only way to directly use ComponentOrientation is to use one of the 2 provided static instances LEFT_TO_RIGHT and RIGHT_TO_LEFT, which is what I did. But it prevented me from testing the new DesignGridLayout RTL support with vertical orientations.

Hence my call to DesignGridLayout users in Japan or China (for vertical right to left) and Mongolia (for vertical left to right) for testing the latest DesignGridLayout (in subversion trunk) with their respective Locale and send me a screenshot of their results. Thanks in advance!

That'll be all for today.

Thursday, September 25, 2008

DesignGridLayout news

Two months ago, I had announced my participation to the OSS project DesignGridLayout.

As a brief reminder, DesignGridLayout is a LayoutManager for Java Swing GUI, which main advantages are:
  • Good looking dialogs (alignment, spacing, sizing, visual balance): this is taken over by DesignGridLayout itself without any special hint from the developer
  • Reduced learning curve for developers, thanks to the fluent API which is, at the same time, simple, effective, compile-safe (no cryptic strings to express the layout) and IDE code-completion friendly
  • No graphics designer needed: the API is the graphical designer
  • Readability and maintainability: you can literally "visualize" the layout by browsing the code that sets it up; inserting a new row of components is done by inserting a new line of code in your layout setup code...
During the past 2 months, after struggling with the switch from CVS to SVN, I have finally finished taking the project over.

In the past few days, I have checked in the latest source code into SVN, and updated the web site.

Several things have changed in this project (as compared with previous 0.1.1 version):
  • License: the original GPL has been changed to ASL 2.0, much more open
  • Build: now the project uses maven 2 for the build and the site generation
  • Package: the old "zappini.designgridlayout" has been changed to a more standard "net.java.dev.designgridlayout"
  • Source code: it has been refactored to improve the API and ease future evolutions
  • Issues: all known bugs have been fixed
  • API: it has been improved on several points (more on this below) such as its narrowing (in order to prevent calls that have no effect, hence potentially pollute source code using DesignGridLayout), as well as the implementation of a few enhancements
  • Javadoc: has been completely rewritten and completed for all public API, along with examples in the package description
Improvements on the API consist essentially in using different interfaces for the different "rows" created by DesignGridLayout:
  • IRow
  • IGridRow extends IRow
  • INonGridRow extends IRow
These interfaces define the exact methods available to each kind of row. Once a row has been created, you cannot change its type (e.g. from Grid to Center) as you could before, which was useless, required more calls and led to some flaws in the API (e.g. what should happen if you set the row type to Grid, then back to Center; and why would you do that?).

Moreover, in DesignGridLayout, methods that create rows have been specialized to determine upfront which kind of row is to be created:
  • DesignGridLayout#row() creates a grid row (IGridRow)
  • DesignGridLayout#centerRow() creates a non grid row, with centered components (INonGridRow)
  • DesignGridLayout#leftRow() creates a non grid row, with left-aligned components (INonGridRow)
  • DesignGridLayout#rightRow() creates a non grid row, with right-aligned components (INonGridRow)
  • DesignGridLayout#emptyRow(int height) creates an empty row, with no component at all (used for introducing vertical spacing between rows)
Other changes in the API are:
  • Removal of IGridRow#label(String) to keep only IGridRow#label(JLabel): this was motivated by the fact a LayoutManager should not create components by itself (arguable opinion, I admit); in addition, this reduces one's options for GUI i18n (one option is to use Component#getName()) to set its text, which is impossible here, since the end-developer code can not get hold of the created JLabel)
  • Row.EMPTY "component" is replaced by IGridRow#empty() methods
  • New INonGridRow#fill() added to allow extreme components to take all remaining space in the row. This is particularly useful to split groups of rows with a label and a separator (as in Karsten Lentzsch FormLayout)
  • New IRow#addMulti() method to add several components in only one grid column, which is useful when you have components that should always "stick together" (eg a JSpinner and a JLabel indicating a unit of measure)
With all these changes, all constants and enum have been removed from the previous version because they serve no purpose now.

You can find the current snapshot (named "0.9-SNAPSHOT") of this version there.

So a further question is "when will the official 0.9 version be released?". That should be short now, we should expect an official release by mid October, including uploaded artifacts to some maven repository (for developers using maven).

What's the roadmap for 1.0?

There are a couple of enhancements requests submitted here.

The main enhancement planned for 1.0 will be the support for variable height rows that would get extra height during resize; this is particularly useful for rows that include components such as JList, JTable, or more generally any component wrapped in a JScrollPane.

Another interesting feature I would like in 1.0 release is components spanning several rows. This is a particularly useful feature and I know several users have been expecting it. The difficult part here will be to define the right API for that, in order to keep this feature easy to use, easy to visualize and safe (reducing potential errors at runtime by catching them at compile-time).

Finally, 1.0 release may include some attempts at right-to-left languages support. This will depend on several factors.

In any case, if you are a DesignGridLayout user or consider it for your next Swing GUI, please take your chance and participate in the discussions on the 3 issues above, so that DesignGridLayout can keep its spirit while bringing important features common in daily GUI design work.

Of course you can also suggest other enhancements and report bugs if you find any.

You can find more details on DesignGridLayout here.

Enjoy GUI design with DesignGridLayout!