Posted on Aug 20, 2008

Skinning a Slider with Nimbus

So time for a example of how to skin a Swing JSlider using the Nimbus Look and Feel and some simple painting code. So this is what we get with the standard Nimbus look slider on a dark grey background.

Slider Default Look

All of the Nimbus skin comes from a set of properties in the UIManager defaults table. The keys we will be changing for this example are:

  • “Slider.thumbWidth”
  • “Slider.thumbHeight”
  • “Slider:SliderThumb.backgroundPainter”
  • “Slider:SliderTrack.backgroundPainter”

You can customize the look for a Component either globally for all instances of the component or locally for a single component instance. To change all sliders globally you can set these properties using UIManager.put(key,value) but in this example I will just set them locally for a single slider. To set UI defaults localy for a single component instance you need to create a UIDefaults map, insert you properties and then set it as a client property on the component:

                JSlider slider = new JSlider(0, 100, 50);
                UIDefaults sliderDefaults = new UIDefaults();
                ....
                sliderDefaults.put(<<key>>,<<value>>)
                ....
                slider.putClientProperty("Nimbus.Overrides",sliderDefaults);
                slider.putClientProperty("Nimbus.Overrides.InheritDefaults",false);

The “Nimbus.Overrides.InheritDefaults” key states if the values in “Nimbus.Overrides” should be merged with the defaults(false) or replace them(true). So next some examples for what properties we should set to skin the slider:

                sliderDefaults.put("Slider.thumbWidth", 20);
                sliderDefaults.put("Slider.thumbHeight", 20);
                sliderDefaults.put("Slider:SliderThumb.backgroundPainter", new Painter() {
                    public void paint(Graphics2D g, JComponent c, int w, int h) {
                        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                        g.setStroke(new BasicStroke(2f));
                        g.setColor(Color.RED);
                        g.fillOval(1, 1, w-3, h-3);
                        g.setColor(Color.WHITE);
                        g.drawOval(1, 1, w-3, h-3);
                    }
                });
                sliderDefaults.put("Slider:SliderTrack.backgroundPainter", new Painter() {
                    public void paint(Graphics2D g, JComponent c, int w, int h) {
                        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                        g.setStroke(new BasicStroke(2f));
                        g.setColor(Color.GRAY);
                        g.fillRoundRect(0, 6, w-1, 8, 8, 8);
                        g.setColor(Color.WHITE);
                        g.drawRoundRect(0, 6, w-1, 8, 8, 8);
                    }
                });	

The lets pop that code into a sample app and run it and see what it looks like.

Slider Demo

The original Nimbus slider on the bottom and the skinned one on top, both running in the same application. Here is the source so you can try it your self. Let me know if this was useful and what other examples you would like to see.

Java Icon
SliderSkinDemo.java

46 Comments

  • Patrick says:

    Nice demo–one question: Nimbus is supposed to be vector-based, which I assume means scalable–when using a custom painter what does one need to take care of to make sure scaling is handled correctly? I assume the hard-coded offsets need to be multiplied by some factor available in context? Or am I misunderstanding what you mean by vector-based in the presentations?

    Thanks
    Patrick

  • Eric Burke says:

    Yes, this is very useful. The various “Overrides” properties and client defaults are not explained well elsewhere, so it is extremely helpful to see concise working examples like this!

    I’ve struggled to figure out how to make JButtons smaller, specifically by removing some of the extra internal padding/margins. So I’d be very interested in examples that delve into borders, insets, padding, margins, etc. For instance, I wanted to create a tiny square button next to a date field that brings up a calendar popup.

    I also really hope bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6723524 is fixed before the final release, as it can make Nimbus JTables look really awful.

  • Tom says:

    could you provide a demo to show how easily the blue style of nimbus can be changed to another color style, because
    that would be really interesting.
    I was wondering where I could find a list of synth properties and the Nimbus values for it.

    And BTW, good work!

  • Jacek says:

    Instead of these hard-coded strings like “”Nimbus.Overrides.InheritDefaults” could you just provide let’s say hardcoded static String constants or even better a nice enum with a toString() implementation for each value?

  • Jasper Potts says:

    Patrik: There is three advantages of Nimbus being vector graphics based: #1 is for when you draw a large button say 200×200 it still looks good, if it was a stretched image it would look poor. #2 When you Nimbus components in a scenegraph like in JavaFX and scale and rotate them they always look crisp. The same if you print them on a high dpi printer. #3 High DPI, this was always a aim of Nimbus though we did not get to finish work on it, hopefully for Java 7. This is the case where the hard coded sizes need to be multiplied by a scaling constant. Once we get this finished we will provide examples of how to write painters that support high DPI.

    Eric: I will try and blog an answer to how to change the padding around components like button. Have you tried , it should net be hard, one property to change. Have you tried mini components? http://www.jasperpotts.com/blog/2007/12/nimbus-large-small-mini-components/
    The JTable bug I have not had a chance to look at, just scanned it and I think its a known issue that we can fix which is we can not give nimbus styling to Renderers that do not extend the DefaultRenderers. So I am not sure there is a better answer than the workaround you have. I will look into it when I have time in more details but not sure when that will be as I am flat out working on Java FX at the moment.

    Tom: I will do a blog on this but basically all you need to do is write a quick prog that lists and prints all the contents of the map from UIManager.getLookAndFeelDefaults().

    Jacek: Unfortunately that is something that we could not do at the moment because of politics. Adding that would be a API change which would require JCP approval and could not be done in a update release on a X.0 release.

  • mbien says:

    >Jacek: Unfortunately that is something that we could not do at the moment because of politics. Adding that would be a API change >which would require JCP approval and could not be done in a update release on a X.0 release.
    … but sun could still provide the enums as java files which can be included in our apps. This won’t break the backwards compatibility.

    (in worst case put it into the JavaFX package)

    It seems like it is possible to introduce a API without the JCP just look into the draggable applet feature. … just write experimental technology above it and do not generate javadocs ;)

  • GeekyCoder says:

    Jasper Potts,
    Nimbus is awesome. I wonder if it is possible to remove the inset spacing for component. For example, after setMargin to all zero to JButton with imageIcon, there still a lot of padding around. Is there a way to remove those space ? Because when one switch from the metal L&F to Nimbus, suddenly images are truncated due to the space.

  • hello says:

    hello, is it possible to use nimbus on the mac?

    is it allowed to fetch the new swing classes that contain nimbus and deploy them with your application so that they can also run on the mac?

  • hello says:

    i just tried the SliderSkinDemo.java which is attached to this blogpost in java6u10 rc2 but the skinning does not seem to work. the two sliders look exactly the same.

  • Mats says:

    Yeah, skinning this way does not work in java6u10 rc2 (build 32). It works only if you do laf.getDefaults().put, but then you skin ALL components and not just a specific component.

  • lqd says:

    While it’s true that this particular piece of code does not skin the component in the released version of java6u10, it definitely works, you can skin an individual component. The part that (now) seems to be missing here is the state.

    Change “Slider:SliderThumb.backgroundPainter” into “Slider:SliderThumb[Focused].backgroundPainter” and ”Slider:SliderTrack.backgroundPainter” into “Slider:SliderTrack[Enabled].backgroundPainter” et voilà.

    I don’t know if it’s possible to specify more than one state in a single client property, i didn’t find how.

    Jasper, btw do you have any update on when/if we’ll be able to either test the nimbus designer tool and/or have it opensourced ?

  • Kevin says:

    If I’ve created a new set of colors for Nimbus by creating a UIDefaults, is there a way to set this on a global scale instead of on each instance of the control like you did in the example? My first thought would be to extend the Nimbus look and feel class and override the getDefaults() method. Thoughts?

    Thanks!

  • Jasper Potts says:

    Kevin: You can set them on UIManager with the static call UIManager.put(“nimbusBase”, Color.RED); that should be all you need. Its best to do it before you set the look and feel to Nimbus so it is there for Nimbus to use from the beginning.

  • Great Nimbus info in your site… and Nimbus is great. I try to change the default JButton background color following your example above, but no success, have you some hints? Thanks!

    • Jasper Potts says:

      Luca: if you respond with the code you tried I can see whats wrong. If you just want simple changing of the color for a JButton try setBackground() on it, that will color theme the button.

  • I used this code (where button is an instance of JButton):

    UIDefaults buttonDefaults = new UIDefaults();
    buttonDefaults.put(“Button.background”, Color.RED);
    button.putClientProperty(“Nimbus.Overrides”,buttonDefaults);
    button.putClientProperty(“Nimbus.Overrides.InheritDefaults”,false);

    if I use the setBackground() I need to use setContentAreaFilled too, and I loose the beautiful Nimbus background effect! :)

  • Renato says:

    It did not work. I use the code in netBeas and it´s not different

    • Jasper Potts says:

      Renato: Make sure you are setting the defaults before you set the look and feel. Depending on the project type you create in NB then it way be setting the Look and Feel for you before your code runs.

  • Eric Burke says:

    I suspect many of the problems people mention in the comments are caused by this bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6752660

    You must set Nimbus.Overrides.InheritDefaults first, not second.

  • Jeff Martin says:

    I’d like to second GeekyCoder’s question – is there any way to reduce the insets on JButton and JTextField? If I make a button or textfield that is 22pts tall, it only gets rendered at 16pts tall. The compressed rendering effects a number of my existing layouts. I was hoping that changing JComponent.sizeVariant to large would help, but that doesn’t seem to do anything.

  • Steve Ray says:

    What is the status of the icon editor that was used to develop Nimbus. I would like to start taking advantage of the L&F but have lots of icons to convert from bitmap to vector. Or are there other editors available that can do the job?

    • Jasper Potts says:

      I am not 100% sure on status for Nimbus designer mainly depends on if I can find the time to get legal approval for releasing I will try. The question you need to ask is what is the advantage of vector icons? They can scale but they tend to look over complicated when small or under detailed when large. The one advantage for Nimbus its self was color theming so if you change the base color for Nimbus then the icons follow that as well. There is also the high dpi argument which I think is valid up to a point when you get to where you want more detail because if a icon has grown from 32 to 64 pixels for example there is a lot more room for detail and a simple icon may look poor. This is why Apple still uses multiple sizes of bitmap icons and scales between them. They have from 16×16 to 512×512 and each one is custom tweaked by a designer.

      If you want to do vector Icons then you might be best of using a graphics tool that can save as SVG and then one of the Java SVG libraries. You will probably want to cache as a image for any given size for performance reasons.

  • LeOneL says:

    I have tried the demo above, but it seems that it doesn’t work on me. I am using the jdk update 15 from Sun on a Windows 7 OS. I also tried to change the default icons of the nimbus l&f but it didn’t work. I used the keys OptionPane.informationIcon, OptionPane.questionIcon, OptionPane.warningIcon and OptionPane.errorIcon for that. For example, to change the information icon, is use:
    UIManager.put( “OptionPane.informationIcon”, new ImageIcon(…) );

    Also, I tried changing the PopMenu background color but it didn’t change. I wonder why it happens that way. I tried different keys like MenuItem.background, Menu.background PopupMenu.background but still, it is the same.

    Also, is there a global way to change the size of all the components? What I do now is to iterate all of the components of a container, cast it to JComponent, and call the method putClientProperty, and if that component is a Container, i recurse.

    • Jasper Potts says:

      If components are not picking up keys then the chances are the cache is getting in the way. The two workarounds are to make sure you set the keys early enough ie before the look and feel is set. and the other is to use updateUI() to force a component to refresh its styling. Hope that helps :-)

  • LeOneL says:

    where’s my reply? i recently left a reply when I last visit this page. It contains important question that I ask. Where it is?

  • Peter L says:

    Is the Painter interface no longer in the com.sun.java.swing package?

    • Jasper Potts says:

      Peter: For which version of Java? In Java 7 it should have moved to javax.swing.Painter and there should be a empty interface in the old location that extends the new one for backwards compatibility.

  • John M says:

    Is it possible to color theme all the content of a dialog differently than that of the main application frame? I am trying to apply new values for the core nimbus colors (nimbusBase, control, text, etc) to a UIDefaults map that I supply to the contents of the dialog by settings the Nimbus.Overrides client property. It does not seem to work.

    Here is the basics of the code I am using on JDK 1.6.0 Update 17:

    // Set some wacky colors for the frame’s theme
    UIDefaults defaults = UIManager.getDefaults();
    defaults.put(“nimbusBase”, new Color(0, 255, 0));
    defaults.put(“nimbusBlueGrey”, new Color(200, 0, 0));
    defaults.put(“control”, Color.black);
    defaults.put(“text”, Color.black);

    // Omitted: Create components and add them to the frame. Pack and show the frame.

    UIDefaults dialogTheme = new UIDefaults();
    dialogTheme.put(“nimbusBase”, new Color(61, 0, 0));
    dialogTheme.put(“nimbusBlueGrey”, new Color(0, 0, 200));
    dialogTheme.put(“control”, Color.white);
    dialogTheme.put(“text”, Color.pink);

    JButton dialogButton = new JButton(“Hello”);
    dialogButton.putClientProperty(“Nimbus.Overrides.InheritDefaults”, Boolean.TRUE);
    dialogButton.putClientProperty(“Nimbus.Overrides”, properties);

    JPanel dialogPanel = new JPanel();
    dialogPanel.putClientProperty(“Nimbus.Overrides.InheritDefaults”, Boolean.TRUE);
    dialogPanel.putClientProperty(“Nimbus.Overrides”, properties);
    dialogPanel.add(dialogButton);

    // Omitted: Create dialog, add dialogPanel, pack and show dialog

    Thanks!

  • John M says:

    My apologies for the cut and paste error. A correction from the above code: the ‘properties’ argument to the putClientProperty methods should have been ‘dialogTheme’.

  • Jasper Potts says:

    John: I think what you are doing is right, there are two things to check, the order of the two lines:

    ….putClientProperty(”Nimbus.Overrides.InheritDefaults”, Boolean.TRUE);
    ..,.putClientProperty(”Nimbus.Overrides”, properties);

    Effects things and I can’t remember the correct order off the top of my head. The other thing is you may need to call updateUI() on the component after setting them to force Nimbus to clear any cached values. Hope that helps :)

  • Andrew U says:

    Hi all,

    I ran into a few problems with the example as published. The first being a class cast exception on line 49 where:

    if (((String)entry.getKey()).startsWith(“Slider”)) {

    }

    … it appears to have run to a point, but then died …

    Since I am trying to use this example to get information on how to customize the TabbedPane.TabbedPaneTabArea, I commented out this if statement so that I could see all of the entries ->

    I then switched the order of the two …putClientProperty(… ( lines 88 and 89 ) statements.

    All worked well after that.

    I hope that this helps someone else running across this. Thank you for the example, I hope to figure out how to customize the tabs’ tab areas now!

    ~a~

  • To get the demo to run I ended up changing this

    if (((String)entry.getKey()).startsWith(“Slider”)){

    to this

    if (entry.getKey() instanceof String && ((String)entry.getKey()).startsWith(“Slider”)){

    and it works.

  • I mean, it runs. On OSX the sliders are normal JSliders :(

  • Got same error at line 43…

    Easiest and clearest way is to change from this:
    if (((String)entry.getKey()).startsWith(“Slider”)){

    To this:
    if (entry.getKey().toString().startsWith(“Slider”)){

    Also I had the problem of the project not running right on jdk1.6.0_19.

    The thumbs changed size but the colors didn’t change.

    I switched this:
    slider.putClientProperty(“Nimbus.Overrides”, sliderDefaults);
    slider.putClientProperty(“Nimbus.Overrides.InheritDefaults”, false);

    To this:
    slider.putClientProperty(“Nimbus.Overrides.InheritDefaults”, false);
    slider.putClientProperty(“Nimbus.Overrides”, sliderDefaults);

    Like mentioned above.

    Everything works right now…

  • John M if you visit here did you ever figure out a solution?

    It seems like what is happening is when you are using the “Nimbus.Overrides.InheritDefaults”, Boolean.TRUE and then just trying to override what you want, it is not obeying.

    If you change that setting to Boolean.FALSE then you get no UI because it is assuming you are going to provide the whole implementation(or a least that is what I believe is happening).

    Well that is partially true. In the sample posted above if you change it to Boolean.TRUE it will obey this part of the code:

    sliderDefaults.put(“Slider.thumbWidth”, 20);
    sliderDefaults.put(“Slider.thumbHeight”, 20);

    The thumb will change size, actually the whole thing changes size. I guess the thumb size automagically enforces a different size on the track.

    Personally what I was trying to do was just override the track part. I just changed it so it would draw no graphics, well attempted to. :(

  • Came up with a solution!!!

    Man, been working on this for awhile now. But heh, if you are like me that is what drives you to continue programming. Weird sense of accomplishment.

    Just for anyone else that may want to accomplish removing the background track.

    sliderDefaults.put(“Slider.thumbWidth”, 20);
    sliderDefaults.put(“Slider.thumbHeight”, 20);

    sliderDefaults.put(“Slider:SliderTrack[Enabled].backgroundPainter”, new Painter() {
    @Override
    public void paint(Graphics2D g, JComponent c, int w, int h) {

    }
    });

    sliderDefaults.put(“Slider:SliderTrack[Disabled].backgroundPainter”, new Painter() {
    @Override
    public void paint(Graphics2D g, JComponent c, int w, int h) {

    }
    });

    JSlider slider = new JSlider(0, 100, 50);
    slider.setPaintTrack(false);

    slider.putClientProperty(“Nimbus.Overrides.InheritDefaults”, true);
    slider.putClientProperty(“Nimbus.Overrides”, sliderDefaults);

    The key is to have paint override the state of the track(enabled/disabled states).

    If you are never going to have the track disabled then you can just override “Slider:SliderTrack[Enabled].backgroundPainter”.

    I hope this will be useful for someone who was trying to figure out a way to remove the track like you can do with metal L&F just by setting JSlider.paintTrack = false.

  • [...] Update: 10.7.2010 Skinning a Slider with Nimbus [...]

  • kawaly says:

    Hello,great post. Infos are pretty usefull and saved me a lot time which I have spend on something else instead of searching :) Thanks and waiting for more posts like this one.

  • Neville Harrison says:

    I have a problem skinning a JSpinner. Along the lines of the JSlider demo mentioned above (which worked well, thanks), I’d like to increase the size of the buttons on a JSpinner. I cannot, however, get much control over this component. Here’s my code, where I’m trying to use a circle instead of the triangle for the nextButton arrow. The result is the same whether running under jdk1.6_21 or jdk

    public class SpinnerSkinDemo {

    public static void main(String[] args) {
    // enable anti-aliasing
    System.setProperty(“awt.useSystemAAFontSettings”, “on”);
    System.setProperty(“swing.aatext”, “true”);
    SwingUtilities.invokeLater(new Runnable() {
    public void run() {
    UIManager.put(“text”, new Color(195, 53, 52));
    for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
    if (“Nimbus”.equals(laf.getName())){
    try {
    UIManager.setLookAndFeel(laf.getClassName());
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    JFrame frame = new JFrame(“Slider Skining Demo”);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.getContentPane().setLayout(new BorderLayout());
    JPanel panel = new JPanel(new GridLayout(0,1,20,20));
    panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
    panel.setBackground(Color.darkGray);

    UIDefaults spinnerDefaults = new UIDefaults();
    spinnerDefaults.put(“Spinner.contentMargins”, new Insets(10, 10, 10, 10)); // Works OK
    spinnerDefaults.put(“Spinner.font”, new Font(Font.SANS_SERIF, Font.BOLD, 30));// Works OK

    //This painter has no effect.
    spinnerDefaults.put(“Spinner:\”Spinner.nextButton\”[Focused+MouseOver].foregroundPainter”, new Painter() {
    public void paint(Graphics2D g, JComponent c, int w, int h) {
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g.setStroke(new BasicStroke(2f));
    g.setColor(Color.RED);
    g.fillOval(1, 1, w-3, h-3);
    g.setColor(Color.WHITE);
    g.drawOval(1, 1, w-3, h-3);
    }
    });

    JSpinner spinner = new JSpinner();
    panel.add(spinner);
    spinner.putClientProperty(“Nimbus.Overrides.InheritDefaults”,false);
    spinner.putClientProperty(“Nimbus.Overrides”,spinnerDefaults);
    spinner.updateUI();
    // Add a normal themed slider for comparison
    JSpinner normalSpinner = new JSpinner();
    panel.add(normalSpinner);

    frame.getContentPane().add(panel, BorderLayout.CENTER);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    }
    });
    }
    }

  • Neville Harrison says:

    Sorry, previous post missed “The result is the same whether running under jdk1.6_21 or jdk1.7.0″

  • Hanks says:

    Hi, I saw the example It works only once, But i need to change all the sliders in a project with the same appearance can you help me

    Hanks

  • Kobi says:

    Hi there’
    i’m trying to override the “nimbusBase” color for specific instance of JButton/JTabbedPane with no luck.
    only the specific attributes of the component e.g. “Button.background”, are working.
    any idea?

    UIDefaults dialogTheme = new UIDefaults();
    // dialogTheme.put(“nimbusBase”, Color.orange);
    // dialogTheme.put(“nimbusBlueGrey”, Color.blue);
    dialogTheme.put(“Button.background”, Color.yellow);
    JButton dialogButton = new JButton(“North”);
    dialogButton.putClientProperty(“Nimbus.Overrides.InheritDefaults”, true);
    dialogButton.putClientProperty(“Nimbus.Overrides”, dialogTheme);

  • iTry says:

    Dont mean to post nearly a year later but this is the best explanation of this topic I could find. However I found some changes. Mainly that this technique does not give the desired result anymore.

    for instance,
    sliderDefaults.put(“Slider:SliderThumb.backgroundPainter”,…
    no longer changes the thumb. It appears the thumb must be changed for all the states that it may have in order to get the desired effect.
    to change the individual cases
    sliderDefaults.put(“Slider:SliderThumb[Pressed].backgroundPainter”,
    works wonderfully but only changes for the pressed state. as stated in the original article there is a system printout of all the available enums.
    Some other explanation of changing all cases in 1 shot would be appreciated but I founf this useful as I personally want to change the look per state like a button.

    great article thanks

  • Paul says:

    Hi, I’m trying to modify design of JComboBox. It is ok if I write e.g. comboDefaults.put(“ComboBox[Enabled+MouseOver].backgroundPainter”, new MyDefaultComboBoxPainter()) but there is a problem if I want to design textField … by Nimbus defaults it is ComboBox:”ComboBox.textField”[Enabled].backgroundPainter. The problem are quotations. It is non-functional to write comboDefaults.put(“ComboBox:ComboBox.textField[Enabled].backgroundPainter”, new MyDownComboBoxPainter()) nor comboDefaults.put(“ComboBox:’ComboBox.textField’[Enabled].backgroundPainter”, new MyDownComboBoxPainter()) nor comboDefaults.put(“ComboBox:\”ComboBox.textField\”[Enabled].backgroundPainter”, new MyDownComboBoxPainter()).

    Haven’t you experience with it?

  • [...] Jasper Potts has a nice blog post about how you can skin the slider using Nimbus Look and Feel: Skinning a slider with Nimbus. [...]

  • [...] tinkering with a small tutorial so I can learn more about UIManager and making custom Look and Feels. the tutorial has a JSlider [...]