Painting the Slider Icon of Jslider

Painting the slider icon of JSlider

There are three ways:

  1. change Java Look and Feel, Preferred of Ways
  2. OverRide XxxSliderUI, but that would be Look and Feel sensitive, and not easy way
  3. learning The Synth Look and Feel

Example using Synth

Synth slider

SynthSliderTest.java

import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.synth.*;

public class SynthSliderTest {

private JFrame f = new JFrame();

public SynthSliderTest() {
try {
SynthLookAndFeel laf = new SynthLookAndFeel();
laf.load(SynthSliderTest.class.getResourceAsStream("yourPathTo/demo.xml"), SynthSliderTest.class);
UIManager.setLookAndFeel(laf);
} catch (Exception e) {
e.printStackTrace();
}
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}

public JComponent makeUI() {
JSlider slider = new JSlider(0, 100);
JPanel p = new JPanel();
p.add(slider);
return p;
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
SynthSliderTest synthSliderTest = new SynthSliderTest();
}
});
}
}

demo.xml file

<?xml version="1.0" encoding="UTF-8"?>

<synth>
<style id="backingStyle">
<opaque value="TRUE"/>
<font name="Dialog" size="12"/>
<state>
<color value="WHITE" type="BACKGROUND"/>
<color value="BLACK" type="FOREGROUND"/>
</state>
</style>
<bind style="backingStyle" type="region" key=".*"/>

<style id="SliderTrackStyle">
<opaque value="TRUE"/>
<state>
<color type="BACKGROUND" value="ORANGE"/>
</state>
</style>
<bind style="SliderTrackStyle" type="region" key="SliderTrack" />

<style id="SliderThumbStyle">
<opaque value="TRUE"/>
<state>
<color type="BACKGROUND" value="RED"/>
</state>
<state value="PRESSED">
<color type="BACKGROUND" value="GREEN"/>
</state>
<!-- state value="MOUSE_OVER">
<color type="BACKGROUND" value="BLUE"/>
</state -->
</style>
<bind style="SliderThumbStyle" type="region" key="SliderThumb" />
</synth>

Jslider grow and shrink icon

Problems:

  • Don't give your class Graphics or Graphics2D fields, as doing this is almost guaranteed to give your gui faulty graphics due to an unstable Graphics object or have it throw a NullPointerException for trying to use a null Graphics object.
  • You shouldn't be trying to draw the Icon directly by calling paintIcon(...). Let Java itself do this. Instead simply call repaint() on the JLabel that holds the icon after changing your icon's width -- that's it!

This is how I tested it:

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

@SuppressWarnings("serial")
public class ResizeIcon extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final int MAX_ICON_WIDTH = 400;
private int iconWidth = MAX_ICON_WIDTH / 2;
private CarIcon carIcon = new CarIcon(iconWidth);
private JLabel carLabel = new JLabel(carIcon);
private JSlider slider = new JSlider(0, MAX_ICON_WIDTH, iconWidth);

public ResizeIcon() {
slider.setMajorTickSpacing(50);
slider.setMinorTickSpacing(10);
slider.setPaintLabels(true);
slider.setPaintTicks(true);
slider.setPaintTrack(true);
slider.setSnapToTicks(true);
slider.addChangeListener(new SliderListener());

setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
setLayout(new BorderLayout());
add(slider, BorderLayout.PAGE_START);
add(carLabel, BorderLayout.CENTER);
}

@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}

private class SliderListener implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
int value = slider.getValue();
carIcon.setWidth(value);
carLabel.repaint();
}
}

private static void createAndShowGui() {
ResizeIcon mainPanel = new ResizeIcon();

JFrame frame = new JFrame("Resize Icon");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}

class CarIcon implements Icon {
public CarIcon(int aWidth) {
width = aWidth;
}

public void setWidth(int aWidth) {
this.width = aWidth;
}

public int getIconWidth() {
return width;
}

public int getIconHeight() {
return width / 2;
}

public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2 = (Graphics2D) g;
Rectangle2D.Double body = new Rectangle2D.Double(x, y + width / 6, width - 1, width / 6);
Ellipse2D.Double frontTire = new Ellipse2D.Double(x + width / 6, y + width / 3, width / 6,
width / 6);
Ellipse2D.Double rearTire = new Ellipse2D.Double(x + width * 2 / 3, y + width / 3,
width / 6, width / 6);

// The bottom of the front windshield
Point2D.Double r1 = new Point2D.Double(x + width / 6, y + width / 6);
// The front of the roof
Point2D.Double r2 = new Point2D.Double(x + width / 3, y);
// The rear of the roof
Point2D.Double r3 = new Point2D.Double(x + width * 2 / 3, y);
// The bottom of the rear windshield
Point2D.Double r4 = new Point2D.Double(x + width * 5 / 6, y + width / 6);

Line2D.Double frontWindshield = new Line2D.Double(r1, r2);
Line2D.Double roofTop = new Line2D.Double(r2, r3);
Line2D.Double rearWindshield = new Line2D.Double(r3, r4);

g2.fill(frontTire);
g2.fill(rearTire);
g2.setColor(Color.red);
g2.fill(body);
g2.draw(frontWindshield);
g2.draw(roofTop);
g2.draw(rearWindshield);
}

private int width;
}

Change thumb and color of JSlider?

As was already mentioned in the comments there is no way for you to change the appearance of the slider without extending an existing implementation of SliderUI. Here is an example implementation of how one could achieve the visuals from your demo picture.

Note that hard coding the sizes and colours isn't the best approach and for a real implementation should be handled by setting and using values available by the UIManager.

class Scratch {
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
JPanel content = new JPanel(new BorderLayout());
content.setPreferredSize(new Dimension(300, 100));
JSlider slider = new JSlider() {
@Override
public void updateUI() {
setUI(new CustomSliderUI(this));
}
};
content.add(slider);

JFrame frame = new JFrame();
frame.setContentPane(content);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}

private static class CustomSliderUI extends BasicSliderUI {

private static final int TRACK_HEIGHT = 8;
private static final int TRACK_WIDTH = 8;
private static final int TRACK_ARC = 5;
private static final Dimension THUMB_SIZE = new Dimension(20, 20);
private final RoundRectangle2D.Float trackShape = new RoundRectangle2D.Float();

public CustomSliderUI(final JSlider b) {
super(b);
}

@Override
protected void calculateTrackRect() {
super.calculateTrackRect();
if (isHorizontal()) {
trackRect.y = trackRect.y + (trackRect.height - TRACK_HEIGHT) / 2;
trackRect.height = TRACK_HEIGHT;
} else {
trackRect.x = trackRect.x + (trackRect.width - TRACK_WIDTH) / 2;
trackRect.width = TRACK_WIDTH;
}
trackShape.setRoundRect(trackRect.x, trackRect.y, trackRect.width, trackRect.height, TRACK_ARC, TRACK_ARC);
}

@Override
protected void calculateThumbLocation() {
super.calculateThumbLocation();
if (isHorizontal()) {
thumbRect.y = trackRect.y + (trackRect.height - thumbRect.height) / 2;
} else {
thumbRect.x = trackRect.x + (trackRect.width - thumbRect.width) / 2;
}
}

@Override
protected Dimension getThumbSize() {
return THUMB_SIZE;
}

private boolean isHorizontal() {
return slider.getOrientation() == JSlider.HORIZONTAL;
}

@Override
public void paint(final Graphics g, final JComponent c) {
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
super.paint(g, c);
}

@Override
public void paintTrack(final Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Shape clip = g2.getClip();

boolean horizontal = isHorizontal();
boolean inverted = slider.getInverted();

// Paint shadow.
g2.setColor(new Color(170, 170 ,170));
g2.fill(trackShape);

// Paint track background.
g2.setColor(new Color(200, 200 ,200));
g2.setClip(trackShape);
trackShape.y += 1;
g2.fill(trackShape);
trackShape.y = trackRect.y;

g2.setClip(clip);

// Paint selected track.
if (horizontal) {
boolean ltr = slider.getComponentOrientation().isLeftToRight();
if (ltr) inverted = !inverted;
int thumbPos = thumbRect.x + thumbRect.width / 2;
if (inverted) {
g2.clipRect(0, 0, thumbPos, slider.getHeight());
} else {
g2.clipRect(thumbPos, 0, slider.getWidth() - thumbPos, slider.getHeight());
}

} else {
int thumbPos = thumbRect.y + thumbRect.height / 2;
if (inverted) {
g2.clipRect(0, 0, slider.getHeight(), thumbPos);
} else {
g2.clipRect(0, thumbPos, slider.getWidth(), slider.getHeight() - thumbPos);
}
}
g2.setColor(Color.ORANGE);
g2.fill(trackShape);
g2.setClip(clip);
}

@Override
public void paintThumb(final Graphics g) {
g.setColor(new Color(246, 146, 36));
g.fillOval(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height);
}

@Override
public void paintFocus(final Graphics g) {}
}
}

Result:

Custom slider

JSlider value only showing upon moving slider

Although it is not entirely clear what the last problem was, here the answer from the comments, in order to give this question the chance to be moved out of the "unanswered" list:

You can probably just create your label3 as

label3 = new JLabel("Rain ammount: 0mm");

Initially, the slider value is 0. So the text on the label will have to be "Rain ammount: 0mm". When you change the slider (so that its value is no longer 0), then the label will be updated anyhow.

how to change Jslider value and pass to paintComponent?

I'm not sure what it is you how to gain, but JFrame doesn't have a paintComponent method, so it will never be called.

What I would do, instead is

  • Load the source image as soon as you can and store it in variable (masterImage for example)
  • Use a JLabel to show the image
  • Use a javax.swing.Timer with a short delay (so 125 milliseconds) set so it won't repeat. Each time the JSlider's stateChanged event is raised, I would restart this timer.

When the timer finally triggers, I would update the "master" image's brightness and apply teh result to "image" JLabel's as it's icon.

The reason for the timer is the fact that the operation of applying the brightness can not only take time, but can increase the amount of memory the application consumes. You will want to reduce this to the absolute minimum if you can

Is there a way to flip a JSlider so that the slider arrow points upwards?

You can override the thumbnail used in UIManager How to hide the knob of jSlider?

UIManager.getLookAndFeelDefaults().put(
"Slider.horizontalThumbIcon",
new Icon('your_icon')
);

However, this would be done to all sliders in your program.



Related Topics



Leave a reply



Submit