Mac Java tip: How to customize Aqua sliders

Technologies: Java 5+, Mac OS X 10.5+

Java's Swing JSlider creates a sliding widget for selecting a number within a fixed range. The sliding "thumb" icon, the track it slides on, tick marks, and labels may be turned on and off, but Swing has no other methods to affect the appearance of a JSlider. That's left up to the current look and feel. This article shows how to use Apple's "Mac OS X" look and feel for Java on a Mac to create smaller JSliders and switch between two thumb icon styles. The article also shows work-arounds for controlling tick and label sizes and colors.

Introduction

Swing's familiar JSlider component has a movable "thumb" that slides along a track with ticks and labels:

The anatomy of a JSlider
 
Figure 1. A JSlider has a sliding thumb, a track for
it to slide on, tick marks, and labels.

The Swing API has generic methods to control the slider's basic appearance, but it leaves the details to the current look and feel. In principal, this enables Java applications to use sliders generically without being concerned by specifics unique to a Mac, Windows, or Linux.

Unfortunately, there are some downsides to Swing's generic approach:

  • Some attributes provided by Swing do not act entirely as expected, such as changing the size of a slider.
  • Some attributes an application might legitimately need to change are not available from Swing, such as setting the tick color.
  • Some attributes that can be changed by native Mac applications are not available from Swing, such as selecting between a Mac's "regular", "small", and "mini" variants of a slider.

Some of these have work-arounds, but the work-around differs from one look and feel to the next. The focus in this article, of course, is work-arounds for the Mac and the "Mac OS X" look and feel for Java.

Controlling slider size

Setting a slider's preferred size works well to change its length, but not its breadth. For the Mac, and many other look and feels, the slider thumb is a fixed-size icon and the track thickness and tick lengths have built-in fixed sizes. Reducing a slider's breadth crops the slider instead of making it smaller.

JSlider slider = new JSlider( 0, 100 );
slider.setPreferredSize( new Dimension( 150, 25 ) );
JSliders are cropped when shrunk in breadth
 
Figure 2. Setting the preferred size works well to change a slider's length,
but it crops the slider's breadth.

A proper reduction in a slider's size should shrink the thumb and track. Swing has no such controls.

Native Mac applications, however, do have slider size control. Apple provides three standard sizes: regular, small, and mini. The smaller sizes are intended for use in tight layouts, such as those for inspector windows and control panels. (See the Apple Human Interface Guidelines.)

All three of the Mac-specific slider sizes are available to Java applications that use Apple's "Mac OS X" look and feel (the default for Java applications run on a Mac). To select a size, set a slider's "JComponent.sizeVariant" client property to "regular", "small", or "mini". The property must be set before the slider has been realized on a visible window. (See Apple's New Control Styles available with J2SE 5.0 on Mac OS X 10.5.)

JSlider slider = new JSlider( 0, 100 );
slider.putClientProperty( "JComponent.sizeVariant", "small" );
Mac sliders come in regular, small, and mini sizes
 
Figure 3. On a Mac, setting a slider's "JComponent.sizeVariant" client property
switches between regular, small, and mini variants.

The smaller sliders have a smaller thumb and thinner track. The tick marks and default labels, however, remain the same size. To make them smaller too, applications need to create ticks and labels themselves (see below).

Setting slider labels

Swing automatically creates slider labels at major ticks. Each label is a JLabel component stored in a hash table indexed by the slider's numeric value. To override these default labels, create your own hash table and pass it to the slider:

JSlider slider = new JSlider( 0, 100 );
 
Hashtable<Integer,JLabel> labels = new Hashtable<Integer,JLabel>( );
labels.put( new Integer(   0 ), new JLabel( "Cold" ) );
labels.put( new Integer(  50 ), new JLabel( "Just right" ) );
labels.put( new Integer( 100 ), new JLabel( "Hot" ) );
 
slider.setLabelTable( labels );
Labels in the slider's label tabel can be anything
 
Figure 4. Setting labels in a slider's label table
enables labels to be anything.

Setting slider label font sizes

Setting the font size of slider labels is trickier than you'd think.

Setting the label font

Calling setFont( ) on a JSlider will not change the label font size! A slider is not a container and the labels are not its children, so slider labels do not inherit slider properties like the font size, colors, etc. Each label is a separate component with its own properties.

JSlider slider = new JSlider( 0, 100 );
slider.setFont( smallerFont );            // Does not work!
                                          // Labels do not inherit the font
                                          // size from a slider

Calling setFont( ) on each JLabel in the hash table is the right approach, but there are gotchas. At construction time, the slider does not have a look and feel UI defined yet. That occurs when the slider or a parent of the slider is added to a frame, dialog, or window. When that UI gets set, it initializes the slider to the look and feel's defaults and resets all of the slider's labels to those defaults. So, if you set a slider label's font size during construction, that size will be reset to the defaults as soon as the slider is added to a parent window.

JFrame parentFrame = new JFrame( );
JSlider slider = new JSlider( 0, 100 );
 
Hashtable<Integer,JLabel> labels = new Hashtable<Integer,JLabel>( );
JLabel label = new JLabel( "0" ); 
label.setFont( smallerFont ); // Does not work! Reset below. labels.put( new Integer( 0 ), label ); ...
slider.setLabelTable( labels ); parentFrame.add( slider ); // Sets slider and label UIs and
// resets label font sizes

If you delay setting the slider label hash table until after the slider's UI has been assigned, that doesn't work either. As soon as you assign the labels, the slider's UI resets the UI for each of the labels, which resets their font size.

JFrame parentFrame = new JFrame( );
JSlider slider = new JSlider( 0, 100 );
parentFrame.add( slider );                // Sets slider UI
 
Hashtable<Integer,JLabel> labels = new Hashtable<Integer,JLabel>( );
JLabel label = new JLabel( "0" ); 
label.setFont( smallerFont ); // Does not work! Reset below. labels.put( new Integer( 0 ), label ); ...
slider.setLabelTable( labels ); // Sets label UIs and
// resets label font sizes

To set label font sizes, first set up the label hash table and add it to the slider. Then, after the slider has been added to a parent window and had its UI assigned, go back and change the label sizes.

However, now there's another gotcha. Since a slider is not a container, it does not have a layout manager. Instead, it lays out the slider and labels on its own. Changing the label font size does not force the slider to update that layout to account for the larger or smaller label font size. Asking the slider or labels to revalidate does not work. Instead, you must force the label's size to update to a new preferred size based on the new font size.

JFrame parentFrame = new JFrame( "Parent" );
JSlider slider = new JSlider( 0, 100 );
 
Hashtable<Integer,JLabel> labels = new Hashtable<Integer,JLabel>( );
JLabel label = new JLabel( "0" ); 
labels.put( new Integer( 0 ), label ); ...
slider.setLabelTable( labels ); parentFrame.add( slider ); // Sets slider and label UIs and // resets label font sizes label = labels.get( new Integer( 0 ) ); label.setFont( smallerFont ); // Updates the font size label.setSize( label.getPreferredSize() );// Updates the label size and // slider layout
Change label fonts to make labels any size
 
Figure 5. Setting the font on individual labels in the
slider's label hash table changes their size
after the slider and label UIs have been set.

Getting the label font

To make a larger or smaller font, you need access to the current font. During construction, before a UI has been assigned to a slider or label, there is no current font yet. The getFont( ) method returns null and that makes it hard to compute a smaller font size.

Fortunately, with the above code we don't need to compute a font size until after the slider's UI has been assigned. Calling getFont( ) then will get the current font from which to derive a larger or smaller font size.

Font font = slider.getFont( );
Font smallerFont = font.deriveFont( font.getSize2D( ) * 0.8f );
 
label = labels.get( new Integer( 0 ) );
label.setFont( smallerFont );
label.setSize( label.getPreferredSize( ) );

Using regular, small, and mini fonts

On a Mac there is a simpler way. To go along with the regular, small, and mini slider sizes, the Mac also supports smaller label sizes. Just set the "JComponent.sizeVariant" client property on each hash table label. This can be safely done during construction, without worrying about when the UIs are assigned.

JFrame parentFrame = new JFrame( "Parent" );
JSlider slider = new JSlider( 0, 100 );
 
Hashtable<Integer,JLabel> labels = new Hashtable<Integer,JLabel>( );
JLabel label = new JLabel( "0" );
label.putClientProperty( "JComponent.sizeVariant", "mini" );
labels.put( new Integer( 0 ), label ); ...
slider.setLabelTable( labels ); parentFrame.add( slider );
Mac labels come in three sizes too
 
Figure 6. On a Mac, setting a label's "JComponent.sizeVariant" client property
switches between regular, small, and mini variants.

Changing slider label colors

A slider's setForeground( ) method won't change label colors. Again, the problem is that the labels are independent components in the label hash table. They do not inherit properties from the slider.

JSlider slider = new JSlider( 0, 100 );
slider.setForeground( Color.blue );       // Does not work!
                                          // Labels do not inherit the
                                          // foreground color from a slider

To change label colors, call setForeground( ) on the individual labels added to the hash table. This can be safely done before the UI is assigned to the slider and labels.

JLabel label = new JLabel( "0" );
label.setForeground( Color.blue );
label.putClientProperty( "JComponent.sizeVariant", "mini" );
Set label sizes and colors
 
Figure 7. Setting attributes of individual labels in the
slider's label hash table changes their size and color.

Controlling slider tick size and color

Swing slider methods set major and minor tick spacing, but there are no methods to set the length or color of those ticks. There is no Mac-specific client property to set them either.

If you browse through the UI defaults for the "Mac OS X" look and feel, you'll find "Slider.tickColor" (see All UI defaults names for common Java look and feels on Windows, Mac OS X, and Linux). If you set this to a Color object, you will change the tick color... for all sliders in the application. Due to a quirk in the current implementation, you cannot use this UI default to change the tick color for an individual slider:

UIManager.put( "Slider.tickColor", Color.blue );  // Does not work as expected!
JSlider slider = new JSlider( 0, 100 );

Instead, abandon the automatic ticks created by JSlider by calling setPaintTicks( false ). Then create your own ticks with icons on labels in the label hash table:

JLabel label = new JLabel( "0" );
label.setIcon( majorTick );

You can use labels with text alone, text and an icon, or just an icon. Embed the tick bar directly in the image. For instance, start with these separate images of old Macs. Add a tick to the top of each one.

Individual icons used as tick labels
Figure 8. Adding a blue tick to the top of these Mac icons
creates tick-ready slider labels. The bottom stand-alone tick
is for intermediate ticks.

Then assign them as slider labels:

A slider with custom icons for ticks and labels
 
Figure 9. Creating custom tick labels gives full
control over tick and label appearance.

Controlling slider thumbs

Most look and feels, including the Mac's, distinguish between a generic slider and one with tick marks and labels. On a Mac, generic sliders have a round thumb, while those with tick marks and labels have an arrow-like thumb.

The "Mac OS X" look and feel automatically switches to the arrow thumb if tick marks and/or labels are enabled. You can force the switch by setting the "Slider.paintThumbArrowShape" client property. (See Java for Mac OS X v10.5 Release Notes.) Set it to true to draw the arrow shape, and false to draw the round thumb shape.

JSlider slider = new JSlider( 0, 100 );
slider.putClientProperty( "Slider.paintThumbArrowShape", Boolean.TRUE );
Sliders with round and arrow-shaped thumbs
 
Figure 10. Setting the "Slider.paintThumbArrowShape" client property changes between the
default round thumb and an arrow thumb, independent of tick and label visibility.

Summarizing what can and cannot be changed

Apple is known for their consistent look and feel in Mac OS X. To maintain that consistency, only some slider features can be controlled in a Java application:

  • Slider thumbs:
    • The thumb icon. You can use the "Slider.paintThumbArrowShape" client property to select between round and arrow-like icons, but you can't set the thumb to anything else.
    • The thumb color. The thumb's color always matches the user's look and feel color choice (Blue or Graphite). You can't change this at all.
    • The thumb size. Swing's setExtent( ) method on a JSlider does nothing on the Mac. You can use the "JComponent.sizeVariant" client property to select between regular, small, and mini sizes, but you cannot set the size arbitrarily.
  • Slider tracks:
    • The track color. The track is always dark gray with a recessed look. You can't change the color, but you can turn it off by calling setPaintTrack( false ), then draw your own track in the slider's background.
    • The track size. The track's thickness is always based upon the regular, small, or mini size variant in use. You can't change this, but you can turn off the track and draw your own if you need something different.
  • Slider ticks:
    • The major and minor tick length. Both tick lengths have fixed sizes, but you can call setPaintTicks( false ) to turn them off then make your own ticks with custom slider labels.
    • The tick color. Ticks are always dark gray, but you can turn them off and create your own tick labels to make them any color you like.
  • Slider labels:
    • The label color. Labels are always black, but you can create your own labels, or get the slider's default labels, then set their color individually.
    • The label justification. For horizontal sliders, they are always centered under their tick location. For vertical sliders, they are always left justified beside their ticks.
    • The side of the slider that's labeled. For horizontal sliders, ticks and labels are always along the bottom. For vertical sliders, they are always on the right.

If you really need to customize sliders further, consider creating your own slider UI by subclassing javax.swing.plaf.BasicSliderUI. Fill in your own paintThumb, paintTrack, paintTicks, and paintLabels methods.

You can also mix and match UIs from different look and feels if you have to. For instance, if you prefer the customizability of Sun's "Metal" slider, you can switch to that slider style on a per-slider basis by calling setUI on the slider:

JSlider slider = new JSlider( 0, 100 );
slider.setUI( new javax.swing.plaf.metal.MetalSliderUI( ) );

Be careful. This can produce a horrible mess of GUI components that look and act differently in the same application.

Further reading

Related articles at NadeauSoftware.com

Web articles and specifications

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

Nadeau software consulting
Nadeau software consulting