How to disable JSlider progress bar from thumb?
I am using JSlider to show the progress in my application so that it updates as a certain process is calculated.
I want the user to be able to drag their thumb back to a mark in the past, but when that happens, I want the progress bar to stay at its current position.
// A dismal attempt at drawing the situation
Progress: ---------
Thumb: |
// User drags backwards. What I have:
Progress: ---
Thumb: |
// What I want instead:
Progress: ---------
Thumb: |
Can anyone suggest any guidance on how to do this? Thank you in advance for your help!
source to share
I understood that. I extended the JSlider to track progress separately from the current thumb position, and then override the MetalSliderUI paintTrack method to draw the track at the desired position.
Here's a solution divided into important parts.
New progress bar:
public class ProgressSlider extends JSlider {
protected int progress;
private static final String uiClassID = "ProgressSliderUI";
public ProgressSlider() {
progress = 0;
putClientProperty("JSlider.isFilled", Boolean.TRUE);
updateUI();
}
public void updateUI() {
setUI(new ProgressSliderUI(this));
}
public String getUIClassID() {
return uiClassID;
}
public int getProgress() {
return this.progress;
}
public void setProgress(int value) {
if (value < this.getMinimum()) {
this.progress = this.getMinimum();
}
else if (value > this.getMaximum()) {
this.progress = this.getMaximum();
}
else {
this.progress = value;
}
}
}
New interface: Note that only 2 lines have been added to the paintTrack () method in the UI class, right after the comment saying this.
public class ProgressSliderUI extends MetalSliderUI {
public ProgressSliderUI() {
super();
}
public ProgressSliderUI(JSlider b) {
}
@Override
public void paintTrack(Graphics g) {
Color trackColor = !slider.isEnabled() ? MetalLookAndFeel
.getControlShadow() : Color.blue;
boolean leftToRight = true;
g.translate(trackRect.x, trackRect.y);
int trackLeft = 0;
int trackTop = 0;
int trackRight = 0;
int trackBottom = 0;
// Draw the track
if (slider.getOrientation() == JSlider.HORIZONTAL) {
trackBottom = (trackRect.height - 1) - getThumbOverhang();
trackTop = trackBottom - (getTrackWidth() - 1);
trackRight = trackRect.width - 1;
}
else {
if (leftToRight) {
trackLeft = (trackRect.width - getThumbOverhang())
- getTrackWidth();
trackRight = (trackRect.width - getThumbOverhang()) - 1;
}
else {
trackLeft = getThumbOverhang();
trackRight = getThumbOverhang() + getTrackWidth() - 1;
}
trackBottom = trackRect.height - 1;
}
if (slider.isEnabled()) {
g.setColor(MetalLookAndFeel.getControlDarkShadow());
g.drawRect(trackLeft, trackTop, (trackRight - trackLeft) - 1,
(trackBottom - trackTop) - 1);
g.setColor(MetalLookAndFeel.getControlHighlight());
g.drawLine(trackLeft + 1, trackBottom, trackRight, trackBottom);
g.drawLine(trackRight, trackTop + 1, trackRight, trackBottom);
g.setColor(MetalLookAndFeel.getControlShadow());
g.drawLine(trackLeft + 1, trackTop + 1,
trackRight - 2, trackTop + 1);
g.drawLine(trackLeft + 1, trackTop + 1, trackLeft + 1,
trackBottom - 2);
}
else {
g.setColor(MetalLookAndFeel.getControlShadow());
g.drawRect(trackLeft, trackTop, (trackRight - trackLeft) - 1,
(trackBottom - trackTop) - 1);
}
// Draw the fill
if (filledSlider) {
int middleOfThumb = 0;
int fillTop = 0;
int fillLeft = 0;
int fillBottom = 0;
int fillRight = 0;
if (slider.getOrientation() == JSlider.HORIZONTAL) {
middleOfThumb = thumbRect.x + (thumbRect.width / 2);
middleOfThumb -= trackRect.x; // To compensate for the
// g.translate()
fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
fillBottom = !slider.isEnabled() ? trackBottom - 1
: trackBottom - 2;
if (!drawInverted()) {
fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
// THIS IS THE CHANGE OF NOTE:
// Fills the progress with the value from the custom slider
fillRight = xPositionForValue(((ProgressSlider) slider)
.getProgress());
fillRight -= trackRect.x;
}
else {
fillLeft = middleOfThumb;
fillRight = !slider.isEnabled() ? trackRight - 1
: trackRight - 2;
}
}
else {
middleOfThumb = thumbRect.y + (thumbRect.height / 2);
middleOfThumb -= trackRect.y; // To compensate for the
// g.translate()
fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
fillRight = !slider.isEnabled() ? trackRight - 1
: trackRight - 2;
if (!drawInverted()) {
fillTop = middleOfThumb;
fillBottom = !slider.isEnabled() ? trackBottom - 1
: trackBottom - 2;
}
else {
fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
fillBottom = middleOfThumb;
}
}
if (slider.isEnabled()) {
g.setColor(slider.getBackground());
g.drawLine(fillLeft, fillTop, fillRight, fillTop);
g.drawLine(fillLeft, fillTop, fillLeft, fillBottom);
g.setColor(trackColor);
g.fillRect(fillLeft + 1, fillTop + 1, fillRight
- fillLeft, fillBottom - fillTop);
}
else {
g.setColor(MetalLookAndFeel.getControlShadow());
g.fillRect(fillLeft, fillTop, fillRight - fillLeft,
trackBottom - trackTop);
}
}
g.translate(-trackRect.x, -trackRect.y);
}
}
And for the driver to check it:
public class ProgressExample extends JFrame
{
public ProgressExample()
{
super("Progress Example");
ProgressSlider mSlider = new ProgressSlider();
mSlider.setMinimum(0);
mSlider.setMaximum(100);
mSlider.setValue(20);
mSlider.setProgress(50);
getContentPane().setLayout(new FlowLayout());
getContentPane().add(slider);
getContentPane().add(mSlider);
}
public static void main(String args[])
{
ProgressExample f = new ProgressExample();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.setSize(300, 80);
f.show();
}
}
source to share
Ah, I understand what you mean. Unfortunately, there is no way to do this.
Blue stripe is not actually part of JSlider, it is part of Metal LAF. This is how Metal LAF decided to paint the component. If you try it with another LAF, you won't get the bar at all.
You will need to connect JProgressBar and JSlider to get what you want.
Here is the starting point. You will need to tweak it to make it look correct.
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.event.ChangeListener;
public class ProgressSlider extends javax.swing.JPanel
{
private JProgressBar progressBar;
private JSlider slider;
public ProgressSlider() {
this(0, 100);
}
public ProgressSlider(int min, int max) {
setLayout(new GridBagLayout());
GridBagConstraints gbc;
gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
slider = new JSlider(min, max, min);
progressBar = new JProgressBar(min, max);
add(slider, gbc);
add(progressBar, gbc);
}
public void setValue(int n) {
boolean adjSlider = slider.getValue() == progressBar.getValue();
progressBar.setValue(n);
if (adjSlider)
slider.setValue(n);
}
public int getValue() {
return progressBar.getValue();
}
public int getSliderValue() {
return slider.getValue();
}
public void syncSlider() {
slider.setValue(progressBar.getValue());
}
public void addChangeListener(ChangeListener l) {
slider.addChangeListener(l);
}
public void removeChangeListener(ChangeListener l) {
slider.removeChangeListener(l);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame jf = new JFrame();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final ProgressSlider ps = new ProgressSlider();
final JTextField tf = new JTextField(4);
final JButton jb = new JButton(new AbstractAction("Set") {
public void actionPerformed(ActionEvent e) {
ps.setValue(Integer.parseInt(tf.getText()));
}});
JPanel jp = new JPanel();
jp.add(tf);
jp.add(jb);
jf.getContentPane().add(ps, BorderLayout.CENTER);
jf.getContentPane().add(jp, BorderLayout.SOUTH);
jf.pack();
jf.setVisible(true);
}
});
}
}
source to share
Well, I don't understand how this is possible. You will need to customize the slider user interface and change the model, because you don't need to store two pieces of information about the model, "track value" and "thumb value".
If you want a big hack, then you draw two sliders on top of each other. The bottom slider will be for the track value, and the top one for the high value.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.metal.*;
public class SliderTest extends JFrame
{
public SliderTest()
{
JSlider bottom = createSlider();
bottom.setUI( new MySliderUI1() );
JSlider top = createSlider();
top.setUI( new MySliderUI2() );
top.setOpaque(false);
JPanel panel = new JPanel();
panel.setLayout( new OverlapLayout() );
panel.add( bottom );
panel.add( top );
getContentPane().add( panel );
}
private JSlider createSlider()
{
JSlider slider = new JSlider(2, 50);
slider.createStandardLabels(10, 5);
slider.setMajorTickSpacing(10);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setValue(20);
return slider;
}
class MySliderUI1 extends MetalSliderUI
{
public void paintThumb(Graphics g) {}
}
class MySliderUI2 extends MetalSliderUI
{
public void paintTrack(Graphics g) {}
}
public static void main(String[] args)
{
JFrame frame = new SliderTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
}
You will also need Overlap Layout . How you keep them in sync is up to you. The solution has many disadvantages, but it may give you some ideas.
source to share