X

JSlider Appearance Improvements

Guest Author

by John Zukowsi

The JSlider component is a popular component for selecting a value from
a numerical range. While some people might think of a JScrollBar for
numerical input, that really serves the purpose of scrolling around a
viewport, not data input. By default, the input range of a JSlider is0 to 100, with an initial value of 50. You can change any of the three
defaults, the direction of the scrollbar, and whether tick marks should
appear and what to show next to them. You'll look at all of these
possible features.

First off is the basic JSlider, just creating it and adding it to a
screen, preserving all the defaults. Nothing fancy here, but builds the
basics for what to build on for the remaining examples. The SliderSample
program actually has four sliders; two horizontal and two vertical.

import javax.swing.\*;
import java.awt.\*;
public class SliderSample {
public static void main(final String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Sample Sliders");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JSlider js1 = new JSlider();
JSlider js2 = new JSlider();
js2.setInverted(true);
JSlider js3 = new JSlider(JSlider.VERTICAL);
js3.setPaintLabels(true);
JSlider js4 = new JSlider(JSlider.VERTICAL);
js4.setInverted(true);
frame.add(js1, BorderLayout.NORTH);
frame.add(js2, BorderLayout.SOUTH);
frame.add(js3, BorderLayout.WEST);
frame.add(js4, BorderLayout.EAST);
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}

Compiling and running the program shows a screen with four sliders on
it. While you cannot tell from the screen, the ones in the EAST andSOUTH regions of the BorderLayout are actually inverted. So, for the
horizontal sliders, 0 is on the left for the top one and 100 is on the
left for the inverted bottom one. For the vertical sliders, 0 is on
the bottom for the left one and on the top for the inverted right one.

By default, there is no indicator on the slider for what its value is.
Instead, you would just ask the slider its value at some later time, or
attach a listener to the control to be told the value upon movement.
Ignoring the import lines, here is a listener that reports the value
when the user stops dragging the slider knob. The getValueIsAdjusting()
method reports true while the user still has the knob selected with the
mouse.

 public class SliderChangeListener implements ChangeListener {
public void stateChanged(ChangeEvent changeEvent) {
Object source = changeEvent.getSource();
JSlider theJSlider = (JSlider)source;
if (!theJSlider.getValueIsAdjusting()) {
System.out.println ("Slider changed: " + theJSlider.getValue());
}
}
}

Not showing a value on the slider makes it somewhat useless, though you
can certainly work with it where the exact value doesn't matter and the
user can see results based upon relative position. To help in making the
slider more useful, you can show tick marks with labels. There are two
classes of tick marks, major and minor ones. Major and minor are just
classifications for the length and positioning of the lines drawn for
the tick mark, where major tick marks are longer than minor ones. To
enable tick marks, you have to call setPaintTicks(true), and, say how
far apart the tick marks should appear. By default, the spacing is
zero, resulting in no tick marks drawn, even when paint ticks is true.
Adding the appropriate lines to the earlier program will show tick
marks on the top and left sliders, with major ticks every 10 and
minor ones at the midpoint between them.

  
js1.setPaintTicks(true);
js1.setMajorTickSpacing(10);
js1.setMinorTickSpacing(5);
js3.setPaintTicks(true);
js3.setMajorTickSpacing(10);
js3.setMinorTickSpacing(5);

Showing tick marks certainly helps, but showing labels makes things
most useful. Adding setPaintLabels(true) calls will show labels at
the major tick marks. By default, the labels are their respective
cardinal numbers, so with a major tick mark at value 0, the associated
label is string "0," and so on for "10,", "20,", all the way to "100."

Instead of showing the cardinal numbers, you can provide a label table:setLabelTable(Dictionary labels). In the table, you would map integer
values to different components to show as the label. In generic-speak,
that would be Dictionary<Integer, JComponent>, though the method
isn't explicitly defined to require the key-value pair to be such.
Typically, the mapping is Integer object to JLabel. As the Swing
component set predates the Collections framework, the method takes
a Dictionary, not a Map, so remember to use a Hashtable for the label
table. Here's one label table setup that shows text roughly every
eleven positions. At the ends are colored diamond icons, instead of
text, to help show that that are labels not text strings that can go
at each position.

   Hashtable<Integer, JComponent> table =
new Hashtable<Integer, JComponent>();
table.put(new Integer(0), new JLabel(
new DiamondIcon(Color.RED)));
table.put(new Integer(11), new JLabel("Eleven"));
table.put(new Integer(22), new JLabel("Twenty-Two"));
table.put(new Integer(33), new JLabel("Thirty-Three"));
table.put(new Integer(44), new JLabel("Fourty-Four"));
table.put(new Integer(55), new JLabel("Fifty-Five"));
table.put(new Integer(66), new JLabel("Sixty-Six"));
table.put(new Integer(77), new JLabel("Seventy-Seven"));
table.put(new Integer(88), new JLabel("Eighty-Eight"));
table.put(new Integer(100), new JLabel(
new DiamondIcon(Color.BLACK)));
js4.setLabelTable(table);
js4.setPaintLabels(true);

The definition for the DiamondIcon will be shown at end with the
full class definition.

Notice that the slider in the eastern area of the screen was used for
the label table. Had the southern slider been used instead, the label
text would overlap. Keep that in mind when you work with slider labels.

One last thing you can do to affect the display. You can hide the
track that the knob slides on. Just call the setPaintTrack() method,
with a setting of false. Here, the bottom slider has its track hidden.

There isn't that much more you can do to customize the look of theJSlider, but as you've seen there is certainly quite a bit you can do.
From adding tick marks and labels to hiding the track, you've seen the
most significant features. If you want to get really fancy, consider
creating a custom UI for the component and show it as a dial instead of
a line. Sounds like an idea for a future tip.

Here's the final and complete source used to generate the samples. You
will need to work backwards to remove code sections to return back to
the first screen shot.

import javax.swing.\*;
import javax.swing.event.\*;
import java.awt.\*;
import java.util.Hashtable;
public class SliderSample {
public static void main(final String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Sample Sliders");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ChangeListener listener = new SliderChangeListener();
JSlider js1 = new JSlider();
js1.setPaintLabels(true);
js1.setPaintTicks(true);
js1.setMajorTickSpacing(10);
js1.setMinorTickSpacing(5);
js1.addChangeListener(listener);
JSlider js2 = new JSlider();
js2.setInverted(true);

js2.setPaintTrack(false);
js2.addChangeListener(listener);
JSlider js3 = new JSlider(JSlider.VERTICAL);
js3.setPaintLabels(true);
js3.setPaintTicks(true);
js3.setMajorTickSpacing(10);
js3.setMinorTickSpacing(5);
js3.addChangeListener(listener);
JSlider js4 = new JSlider(JSlider.VERTICAL);
js4.setInverted(true);
Hashtable<Integer, JComponent> table =
new Hashtable<Integer, JComponent>();
table.put(new Integer(0), new JLabel(
new DiamondIcon(Color.RED)));
table.put(new Integer(11), new JLabel("Eleven"));
table.put(new Integer(22), new JLabel("Twenty-Two"));
table.put(new Integer(33), new JLabel("Thirty-Three"));
table.put(new Integer(44), new JLabel("Fourty-Four"));
table.put(new Integer(55), new JLabel("Fifty-Five"));
table.put(new Integer(66), new JLabel("Sixty-Six"));
table.put(new Integer(77), new JLabel("Seventy-Seven"));
table.put(new Integer(88), new JLabel("Eighty-Eight"));
table.put(new Integer(100), new JLabel(
new DiamondIcon(Color.BLACK)));
js4.setLabelTable(table);
js4.setPaintLabels(true);
js4.addChangeListener(listener);
frame.add(js1, BorderLayout.NORTH);
frame.add(js2, BorderLayout.SOUTH);
frame.add(js3, BorderLayout.WEST);
frame.add(js4, BorderLayout.EAST);
frame.setSize(400, 300);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
public static class SliderChangeListener implements ChangeListener {
public void stateChanged(ChangeEvent changeEvent) {
Object source = changeEvent.getSource();
JSlider theJSlider = (JSlider)source;
if (!theJSlider.getValueIsAdjusting()) {
System.out.println ("Slider changed: " + theJSlider.getValue());
}
}
}
public static class DiamondIcon implements Icon {
private Color color;
private boolean selected;
private int width;
private int height;
private Polygon poly;
private static final int DEFAULT_WIDTH = 10;
private static final int DEFAULT_HEIGHT = 10;
public DiamondIcon(Color color) {
this(color, true, DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public DiamondIcon(Color color, boolean selected) {
this(color, selected, DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public DiamondIcon(Color color, boolean selected,
int width, int height) {
this.color = color;
this.selected = selected;
this.width = width;
this.height = height;
initPolygon();
}
private void initPolygon() {
poly = new Polygon();
int halfWidth = width / 2;
int halfHeight = height / 2;
poly.addPoint(0, halfHeight);
poly.addPoint(halfWidth, 0);
poly.addPoint(width, halfHeight);
poly.addPoint(halfWidth, height);
}
public int getIconHeight() {
return height;
}
public int getIconWidth() {
return width;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(color);
g.translate(x, y);
if (selected) {
g.fillPolygon(poly);
} else {
g.drawPolygon(poly);
}
g.translate(-x, -y);
}
}
}

Join the discussion

Comments ( 3 )
  • M Kneebone Friday, April 4, 2008

    You can adjust the behaviour of JSliders too. One thing that always bothered me with JSliders is how once setSnapToTicks(true) is passed to a JSlider, it only snaps once the drag is complete, while the mouse button is held down the slider "thumb" will freely move instead of snapping to ticks. I wrote an article a few months back about how this can be fixed for any Look and Feel without having to change application code, save for one line of initialisation. See here:

    http://www.javaspecialists.eu/archive/Issue148.html


  • Casper Monday, April 7, 2008

    Good point. I've often used the JSlider as well to supplement and extend various other controls:

    http://www.brunata.dk/ptd/3/backup/analyse.gif


  • S.K.Dogra Monday, April 7, 2008

    Very useful indeed! Thanks for your efforts.


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha