Tuesday, July 21, 2009

Showing JTable Header Without Using JScrollPane

Most tutorials and examples about JTable put it within a JScrollPane. If you do not place the JTable inside a JScrollPane, the header is not automatically shown.

However, JTable does create it. To show it, simply call JTable.getTableHeader() and add it to the Container. Just as importantly, you can continue to control the columns (such as changing column width and switching columns) from the header.

For example, you can easily put the header NORTH of a BorderLayout:
setLayout(new BorderLayout());
JTable table = new JTable(data, headerLabels);
add(table.getTableHeader(), BorderLayout.NORTH);
add(table, BorderLayout.CENTER);
Note that you could conceivably put the header in the SOUTH position. However, you can only have one header showing at a time. If you add it in both the NORTH and then the SOUTH location, it will only show up in the SOUTH location.

Friday, July 17, 2009

Writing Custom Java Layout Managers

Why Write Custom Layout Managers?
A Swing application depends on layout managers to intelligently position a collection of GUI components in a window. Swing provides a number of general purpose layout managers for this purpose.
But sometimes it is hard to shoehorn a general purpose layout manager for a specific purpose and the result is often less than satisfactory. Third-party layout managers are available that try to address some of the perceived shortcomings of Swing's own offerings. In addition, GUI layout editors are available that visually assist inprogramming a GUI layout.
Nonetheless, it is often more efficient and more effective to write a custom layout manager designed for a specific layout requirement rather than struggle with the standard layout managers. And it's easier than one may imagine.
LayoutManager and LayoutManager2 API
Java's LayoutManager interface declares the five methods that have to be implemented in a valid layout manager:
  • addLayoutComponent, specifying name
  • removeLayoutComponent
  • layoutContainer
  • minimumLayoutSize
  • preferredLayoutSize
AddLayoutComponent and removeLayoutComponent are called to inform the LayoutManager about Components added to a Container using it. LayoutContainer implements the layout strategy, changing the x and y pixel position and the dimension of the Components as necessary. MinimumLayoutSize and preferredLayoutSize let the Container find out the minimum required and preferred layout size given the Components to be laid out in it.
LayoutManager2 extends LayoutManager with five more methods:
  • addLayoutComponent, specifying constraints object
  • getLayoutAlignmentX
  • getLayoutAlignmentY
  • invalidateLayout
  • maximumLayoutSize
The new addLayoutComponent allows you to pass an object specifying properties to constrain the Component in the layout. GetLayoutAlignmentX and getLayoutAlignmentY specifies the alignment of the Component within the Container. InvalidateLayout tells the LayoutManager to discard cached information about the layout and recalculate. MaximumLayoutSize specifies maximum dimension of the Container.

Example: Grid Layout with Positionable Components
Let's say we need to draw components on a grid, for example, but need to specify the positions of the components arbitrarily at any time. On the first take we'd probably choose Java's GridLayout as the layout manager - that is, until we discover that it will only position components in sequence. Afterwards, the programmer has no control over their positions. The alternative would be to use GridBagLayout, but that's way too complicated and difficult to control for such a simple task. The solution is to write our own LayoutManager.

We call our grid layout with positionable Components the PositionableGridLayout class. The minimumLayoutSize for this case should be the extent of the layout embodying the outermost component. That would be our preferredLayoutSize as well. In other words, minimumLayoutSize is the x grid position of the easternmost Component times the width of each grid and the y grid position of the southernmost Component times the height of each grid. LayoutContainer calculates the pixel location of each Component given its grid position, and relocates it with a call to the Component's setBounds method if necessary. Note that we did not implement addLayoutComponent and removeLayoutComponent because nothing special needs to be done when a Component is added to or removed from the Container.

An automatic LayoutManager - that is, one that would not allow the programmer to control the location and size of each Component - would normally be able to work alone. For a LayoutManager that allows the programmer to individually fix the location and size of the Components, however, an assisting class is required. Such is the case with Java's GridBagLayout, which requires GridBagConstraints to work. In our example we need to be able to position any Component at an arbitrary location at any time. Following the example of GridBagLayout, we implement an assisting class, the PositionableGridConstraints, which allows us to specify the grid position of an added component. The PositionableGridConstraints class is fairly simple to implement. It mainly stores away the grid position of a Component.

/**
* Constructs a PositionableGridConstraints.
*/
public PositionableGridConstraints( int gridx, int gridy, ... )
{

this.gridx = gridx;
this.gridy = gridy;
:
}

We need to assign the grid constraints to the Component, of course. Again, following the example set by Java's GridBagLayout, we implement a setConstraints method in PositionableGridLayout whose purpose is to associate the grid constraints to a Component in a hashtable using the Component as the key.

Example: Row Layout with Proportionally Sized Components
RowLayout lets the programmer specify the space a Component is allowed to occupy horizontally relative to the width of the entire Container. But if the component has a preferred size, its aspect ratio is preserved. RowLayout implements LayoutManager2 so that we can specify the proportion as a constraint.
Again, minimumLayoutSize and also maximumLayoutSize calls preferredLayoutSize, but the calculation of preferred size is slightly more involved. Its width is the width of the Container. Its height is the height of the tallest Component.
AddLayoutComponent and removeLayoutComponent now has a function. They are used to add and remove the proportionality constraints to a component.
Download
Source code for PositionableGridLayout, PositionableGridConstraints and RowLayout.
References
  1. Daniel Dee, Implementing a Grid Layout Manager With Positionable Components, Java Developer's Journal, 1997.
  2. Using Layout Managers, Java Tutorials, Sun.
  3. Patrick Niemeyer and Jonathan Knudsen, Learning Java, 3rd ed., O'Reilly (2005), Chapter 19.


Wednesday, July 8, 2009

JavaScript and Applet Communication

Sometimes it's necessary to make an applet talk to JavaScript in the html page that contains it and vice versa. LiveConnect is the technology that makes this possible. And the best part is that you don't even have to download anything, because the component that enables JavaScript to talk to applet is supported by Firefox, Internet Explorer and Safari, and the component that enables an applet to talk to JavaScript is plugin.jar, which comes with the JRE.

JavaScript to Java Applet

For JavaScript to call methods in a Java applet, the applet must be named in the applet tag and the mayscript attribute set to true. For example, if I have an applet called JSApplet, then the applet tag may look like this:
<applet code="com.avacoda.swing.JSApplet"
mayscript="true" name="jsApplet" width="200" height="100">
If JSApplet has a method called setMessage(), then the JavaScript can access this method like so:
jsApplet.setMessage("Hello");

Java Applet to JavaScript

To perform the converse operation, the Java applet has to import the JSObject class. The JSObject class is a wrapper around JavaScript objects.

For example, the following code gets the JavaScript window object in the page that the applet referred by this resides in. Then, it gets the document object of the window and finally the form object in the document.
JSObject window = JSObject.getWindow(this);
JSObject document = (JSObject) window.getMember("document");
JSObject form = (JSObject) document.getMember("form");

Download
Source Code

Run JSApplet
This sample code contains a JSApplet that, when initialized, calls the JavaScript setMessage() method, which in turn calls back to the JSApplet's setMessage() method. The JSApplet's setMessage() method repaints the screen with the message that is passed from itself to JavaScript and back to itself again.

Note: This sample code works on Firefox and Internet Explorer, but this "closed-loop" program appears to be incompatible with Safari, though it does not appear to fail. Nonetheless, an applet can still communicate with JavaScript and vice versa in Safari.

References
  1. LiveConnect Overview, Mozilla Developer Center.
  2. Talk to JavaScript with Java, NetSpade Web Developer Heaven, 2005.

Thursday, July 2, 2009

Automatically Size to Fit Image in a JLabel

Images inside a Java Swing component do not automatically resize when it's larger than the component it resides in, resulting in a clipped image.

Oftentimes, it is convenient to have an image automatically resize to fit inside a component while preserving the aspect ratio. Thumbnail display is one that comes to mind.

It is actually fairly easy to implement this capability by extending JLabel and overriding the paintComponent() method:

protected void paintComponent(Graphics g)
{
ImageIcon icon = (ImageIcon) getIcon();
int iconWidth = icon.getIconWidth();
int iconHeight = icon.getIconHeight();
double iconAspect = (double) iconHeight / iconWidth;

int w = getWidth();
int h = getHeight();
double canvasAspect = (double) h / w;

int x = 0, y = 0;

// Maintain aspect ratio.
if(iconAspect < canvasAspect)
{
// Drawing space is taller than image.
y = h;
h = (int) (w * iconAspect);
y = (y - h) / 2; // center it along vertical
}
else
{
// Drawing space is wider than image.
x = w;
w = (int) (h / iconAspect);
x = (x - w) / 2; // center it along horizontal
}

Image img = icon.getImage();
g.drawImage(img, x, y, w + x, h + y, 0, 0, iconWidth, iconHeight, null);
}


Download Source Code

ThumbnailLabel.java