Posted by: Morten Nobel-Jørgensen | January 18, 2009

Changing preferred size of a html JLabel


In general Swing is build to be a very dynamic GUI library, that let the developer create UI that supports rescaling (so the components of the layout will refit into new dimensions).
In general the layout is created using minimum-, preferred- and maximum dimensions of the components along with layout managers.
Most of the cases this seems like a good approach, however, when the preferred size does not fit your layout, it can be hard to find a valid dimension.
This is a common problem when the component can wrap, such as a JLabel containing html.

Example: The following code snippet creates a frame with one line of text

JFrame frame = new JFrame();
JLabel descriptionLabel = new JLabel("<html>Yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi</html>");
frame.getContentPane().add(descriptionLabel);
frame.pack();
frame.setVisible(true);

jlabel-problem

If you for some reason, think that it would look better to have a smaller preferred width of the frame, you does not have a lot of choices. Swing does not give you any advice on what the height of a component is, using a certain fixed width. You may think that layout managers may solve this this issue, but also layout managers depends on the minimum-, preferred-, and maximum dimensions of a components.
The usual way I have solved the problem, is either insert <br> tags into the html code and force the formatting to be fixed, or hard-coding the height.
Both workarounds have significant disadvantages such as breaking the ability to rescale the component.

A better solution

I’ll show a solution that works at least for the a plain JLabel with html.
As I mentioned, the real problem is, that you can’t get the height of the component when the width of the component is fixed.
The layout of the components is actually not done by the JLabel object itself. Instead the layout is done by the JLabel UI delegate object from the current look and feel.

Instead of figuring out the layout before rendering, another approach is to paint the component and determine the size of what is rendered. Of cause we shouldn’t render the component to a real surface (such as screen or BufferedImage). Basically we only need to determine what what is rendered and where it is rendered, from that we can know the height of the object.

This should give us a method that can determine the height of a component with a fixed width.

JLabel descriptionLabel = new JLabel("<html>Yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi yadi</html>");
int height = getComponentHeight(descriptionLabel, 200); // this method
descriptionLabel.setPreferredSize(new Dimension(200, height));
JFrame frame = new JFrame();
frame.getContentPane().add(descriptionLabel);
frame.pack();
frame.setVisible(true);

The getComponentHeight method first set the size of the component to 200 width and Integer.MAX_VALUE height. Then component is painted to a Graphics proxy object, that instead of delegating the painting calls, it just measures the minimum and maximum height for each painting call.
When this paint method is done, the height of the component equals maximum minus minimum. And we get the following frame:

jlabel-solution

The sourcecode for getComponentHeight:
@Deprecated: See update 20-09-01 below

deprecated_solution.java

Conclusion

The graphics proxy approach gives us a way to change the preferred size of a JLabel with html. However this is a hack, and will not work in all situations.
The hack can be used to other components than JLabel, but it requires that the component does only renders the content and does not stretch itself the the current size (so no borders) and is not opaque (opaque components will paint the background, and the GraphicsProxy will think that the height is Integer.MAX_VALUE).
Besides the getComponentHeight is not fully implemented, so some components may a throw UnsupportedOperationException. (Maybe a Graphics2D proxy is required in some cases). Hopefully you grasp the idea and can easily implement the rest yourself.
Finally the getComponent should use the target Graphics object whenever possible, if the Graphics object is not available, a offscreen image is used for creating a Graphics object – but the related font metrics may be different that the targets font metrics.

Update 20-01-09

After I have posted the same question on forums.sun.com, Maxideon showed me a much cleaner implementation, that let you use Swings HTML rendere to calculate the alternative preferred size.

private static final JLabel resizer = new JLabel();

	/**Returns the preferred size to set a component at in order to render
	 * an html string.  You can specify the size of one dimension.*/
	public static java.awt.Dimension getPreferredSize(String html,
													  boolean width, int prefSize) {

		resizer.setText(html);

		View view = (View) resizer.getClientProperty(
				javax.swing.plaf.basic.BasicHTML.propertyKey);

		view.setSize(width?prefSize:0,width?0:prefSize);

		float w = view.getPreferredSpan(View.X_AXIS);
		float h = view.getPreferredSpan(View.Y_AXIS);

		return new java.awt.Dimension((int) Math.ceil(w),
				(int) Math.ceil(h));
	}
About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

Follow

Get every new post delivered to your Inbox.

Join 68 other followers

%d bloggers like this: