Create a range slider
By Ernie Yu | May 19, 2009
The upcoming version of LimeWire adds advanced filtering features to help you refine search results and locate files. These features display controls to select from several file attributes, such as category, file extension, and file size. In particular, the file size control uses a dual-thumb slider to allow you to select a range of sizes to display.
In order to create this range slider, we need a new Swing component because the existing JSlider component only displays a single thumb to adjust the slider value. (Interestingly, even though JSlider only allows you to update a single value, its data model actually provides support for two values that define an internal range within the minimum and maximum bounds. )
The internal range is defined by the slider value and an “extent”, which is the length of the inner range that begins at the slider value. In DefaultBoundedRangeModel, the default “extent” is zero and not normally used for anything. We add it to the slider value to define the upper value of the selected range.
public void setUpperValue(int value) {
// Compute new extent.
int lowerValue = getValue();
// Set extent to set upper value.int
newExtent = Math.min(Math.max(0, value - lowerValue),
getMaximum() - lowerValue);
setExtent(newExtent);
}
Our new dual-thumb slider component takes advantage of the built-in definition for DefaultBoundedRangeModel, and is implemented with just two classes:
-> RangeSlider extends JSlider to install a UI delegate and provide value change methods, and
-> RangeSliderUI extends BasicSliderUI to paint the two thumbs and handle user events.
RangeSlider provides methods to set the lower and upper values in the selected range. We define the existing slider value as the lower value, and the upper value as the lower value plus the extent. To access these values, we override some methods, and add a couple of our own.
-> We add setUpperValue(int) to calculate the extent based on the current lower value. We apply limits to ensure that the upper value is bounded by the lower value and the maximum.
-> We override setValue(int) to calculate the extent to ensure that the upper value does not change. We apply limits to ensure that the lower value stays bounded by the minimum and the upper value.
-> We override updateUI() to install a custom UI delegate to render the two thumbs.
RangeSliderUI provides a customized rendering of the two thumbs and handles user events to update their values. This is somewhat involved so we will only highlight the primary aspects of this code. (Here’s the source: Range Slider Demo code.)
The main tasks are: (1) calculating the thumb sizes and locations, (2) painting the thumbs, and (3) handling mouse and keyboard input events on the thumbs.
Step 1: thumb sizes and locations
To calculate the thumb sizes and locations, we need to override several UI delegate methods.
-> We override installUI(JComponent) to initialize a new upperThumbRect attribute, which we use to store the size and position of the upper thumb.
-> We override calculateThumbSize() to update the size of the upper thumb, which is defined to be the same as the lower thumb.
-> We override calculateThumbLocation() to update the location of the upper thumb, which is based on the upper value stored in the slider component. The thumb is always positioned to be centered over its value on the track. The logic is essentially copied from the superclass method and applied to upperThumbRect.
-> We override getThumbSize() to return the dimensions of the thumb. In LimeWire, we use custom images for our thumbs, although any shape will do.
Step 2: paint
To paint the slider component, we need to override the default implementations of various paint methods in the UI delegate, and add a few of our own.
-> We override paintThumb(Graphics) to do nothing. This is called by the superclass paint() to draw the single thumb, and we want to replace this with our own logic for two thumbs.
-> We override paint(Graphics, JComponent) to draw the two thumbs as needed. We define an upperThumbSelected indicator to keep track of which thumb was last selected, so we can draw the unselected thumb first, and draw the selected one on top of it.
-> We add paintLowerThumb(Graphics) and paintLowerThumb(Graphics). The two methods are similar - they draw a thumb in the appropriate rectangular region. (The sample code uses a colored shape for the thumb.)
-> We override paintTrack(Graphics) to draw the slider track, and highlight the selected range.
Step 3: handle input
To handle mouse drag events on the thumbs, we define a RangeTrackListener inner class that extends the TrackListener class in BasicSliderUI. We also override createTrackListener(JSlider) to return a new instance of the class. In RangeTrackListener, we implement several methods.
-> We override mousePressed(MouseEvent) to detect which thumb is being pressed.
-> We override mouseDragged(MouseEvent) to adjust the appropriate slider value as the thumb is dragged.
-> We override mouseReleased(MouseEvent) to reset indicators.
-> We add moveLowerThumb() and moveUpperThumb() to update the slider values and repaint the thumbs.
Finally, to handle keyboard events on the thumbs, such as the arrow keys, we override a pair of methods used to move the slider by increments.
-> We override scrollByBlock(int) to move the selected thumb by a block increment. This is called when the Page Up or Page Down key is pressed.
-> We override scrollByUnit(int) to move the selected thumb by a unit increment. This is called when one of the arrow keys is pressed.
Summary
The dual-thumb slider component allows you to select a range by adjusting its lower and upper bounds. And by implementing our own UI delegate, we can adjust the appearance of the component to suit our needs. (This can be very useful when working with visual designers who are creating a unique vision.)
Check out the Range Slider Demo code which contains a complete example of RangeSlider and RangeSliderUI, minus the custom images we use for the thumbs in LimeWire.


Comments and Trackbacks
Java desktop links of the week, May 25 | Jonathan Giles Says:
May 24th, 2009 at 5:47 pm |
Permalink
[…] Limewire blog continues to post interesting articles. This time they post an article detailing how they developed a range slider in Swing which supports setting both a lower and… . Similar components exist in Jide etc, but this looks to be an excellent component for people […]
FP Says:
December 11th, 2009 at 3:24 am |
Permalink
Very good job! This is really what I was looking for!! Simple and well-structured. What license do these files have?
Ernie Yu Says:
December 11th, 2009 at 12:34 pm |
Permalink
FP,
I’m glad you found this article useful. The demo code was posted without a license, so you are free to use it as you please. As a courtesy, you may include an attribution in your work to let others know where you found the source code. And if your situation permits, we would love to hear about your project and how you made use of our example.
FP Says:
December 18th, 2009 at 10:51 am |
Permalink
I posted a comment some days ago but I think it has gone lost somehow.
However, what I’m developing is a plot editor and I employ your slider is to make fine-grain adjustments to the displayed portion of the graph (minX-maxX and minY-maxY).
I also used G library ) in my code for managing java drawing complexity and, as soon as I finish the plot editor I will release the code on that site as an example, and I’ll also probably send it to you so that you could give some feedback about it!