1

My problem is that I have table which one of the columns have ProgressBars within table cells, I would like dynamically change ProgressBar's Bar color dynamically based on row and column number, however I cannot achive it. Also there are limitations of Nimbus too. I have to override Nimbus UI Defaults per component based. So if I want to dynamically change a cell's bar color, how could I achieve it without changing cell text color?

 public class ProgressRenderer extends JProgressBar implements TableCellRenderer {

    private static final long serialVersionUID = 1L;

    public ProgressRenderer(int min, int max) {
        super(min, max);
        this.setStringPainted(true);
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        this.setValue((Integer) value);
        UIDefaults defaults = new UIDefaults();
        defaults.put("ProgressBar[Enabled].foregroundPainter", new MyPainter(Color.RED));
       defaults.put("ProgressBar[Enabled+Finished].foregroundPainter", new MyPainter(Color.RED));

              putClientProperty("Nimbus.Overrides.InheritDefaults", Boolean.TRUE);
     putClientProperty("Nimbus.Overrides", defaults);
        return this;
    }

    class MyPainter implements Painter<JProgressBar> {

        private final Color color;

        public MyPainter(Color c1) {
            this.color = c1;
        }
        @Override
        public void paint(Graphics2D gd, JProgressBar t, int width, int height) {
            gd.setColor(color);
            gd.fillRect(0, 0, width, height);
        }
    }         
}

Above is my code snippet, which I use TableCellRenderer.

mKorbel
  • 109,525
  • 20
  • 134
  • 319
mbasol
  • 105
  • 1
  • 2
  • 9

2 Answers2

2

Something like that? ;)

Different JProgressBar on JTable cells

The problem you have is that it is extremely painful to override Nimbus' default colors for a specific component instance (see a related question).

Most of Nimbus painters are defining around 50 different colors, derived from one or two basic ones (the nimbusBlueGrey and famous nimbusOrange). The best way would have been to override them in the UIDefaults found in Nimbus.Override property of the component you want to change, but it's not what they did (I'd like to open a bug on that ;), no seriously!).

I've been trying to achieve the exact same thing and was finally able to by ... (close your eyes) copy-pasting and hacking the javax.swing.plaf.nimbus.ProgressBarPainter class into my own code! Why? Because that class is private to the package and cannot be overriden (which would have been a bit cleaner...). As much as I hate to do that, it worked...

Here is how to modify it (I won't post the whole code as it's way too big and not that interesting):

  1. Start by adding the following methods after the ProgressBarPainter() constructor. (they are basically a copy-paste of what you can find following the decodeColor() method down to AbstractRegionPainter then NimbusLookAndFeel then NimbusDefaults.getDerivedColor() and finally DerivedColor.rederiveColor()):

    private float clamp(float v) {
        if (v < 0.0f)
            return 0.0f;
        if (v > 1.0f)
            return 1.0f;
        return v;
    }
    
    private int clamp(int v) {
        if (v < 0)
            return 0;
        if (v > 255)
            return 255;
        return v;
    }
    
        // Got from javax.swing.plaf.nimbus.DerivedColor.rederiveColor()
    private Color decodeColor(Color src, float hOffset, float sOffset, float bOffset, int aOffset) {
        float[] tmp = Color.RGBtoHSB(src.getRed(), src.getGreen(), src.getBlue(), null);
        tmp[0] = clamp(tmp[0] + hOffset);
        tmp[1] = clamp(tmp[1] + sOffset);
        tmp[2] = clamp(tmp[2] + bOffset);
        int alpha = clamp(src.getAlpha() + aOffset);
        return new Color((Color.HSBtoRGB(tmp[0], tmp[1], tmp[2]) & 0xFFFFFF) | (alpha << 24), true);
    }
    
  2. Copy-paste the code statically generating the 50 colors to a method you'll use to generate them dynamically, from the color you want:

From :

private Color color1 = decodeColor("nimbusBlueGrey", 0.0f, -0.04845735f, -0.17647058f, 0);
...
private Color color50 = decodeColor("nimbusOrange", 0.0014062226f, -0.77816474f, 0.12941176f, 0);

To:

private void initColors(Color foreground) {
    color1 = decodeColor("nimbusBlueGrey", 0.0f, -0.04845735f, -0.17647058f, 0);
    // ...
    color50 = decodeColor(foreground, 0.0014062226f, -0.77816474f, 0.12941176f, 0);
}

Note that we pass the color we want as a parameter and use it as a replacement of nimbusOrange, which seems to be the main color for progress bars. We try to stick to Nimbus' way of deriving colors.

And change the ProgressBarPainter() constructor to include the main color and generate them:

public ProgressBarPainter(int state, Color foreground) {
    super();
    this.state = state;
    this.ctx = new AbstractRegionPainter.PaintContext(new Insets(5, 5, 5, 5), new Dimension(29, 19), false);
    initColors(foreground); // Generates appropriate colors
}

You'll find how to initialize the ctx field in the source of NimbusDefaults. However, note that the enum AbstractRegionPainter.PaintContext.CacheMode is not visible from the tweaked class so you won't be able to use all the fancy features. Fortunately, there is a simpler constructor to AbstractRegionPainter.PaintContext that doesn't use it. (In my case, I didn't need all of the different states so I used default values, but feel free to add any other parameter to cope with them).

And finally, the Graal! ;) (I'm changing color according to the value that should be a percentage: green if more than 75%, orange if more than 50% and red otherwise).

@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    int v = ((Integer)value).intValue();
    Color c;
    if (val >= 75)
        c = Color.GREEN;
    else if (val >= 50)
        c = Color.ORANGE;
    else
        c = Color.RED;
    setValue(v);
    UIDefaults defaults = new UIDefaults();
    ProgressBarPainter painter = new ProgressBarPainter(ProgressBarPainter.FOREGROUND_ENABLED, c);
    defaults.put("ProgressBar[Enabled].foregroundPainter", painter);
    putClientProperty("Nimbus.Overrides", defaults);
    return this;
}

Now, let's "clean" that a little bit (as much as we can call that a "clean" work):

  • delete the assignments for the private Color colorXX that use nimbusOrange, as they will be generated by the call to initColors().
  • keep the assignments for these which use nimbusBlueGrey, as we're not changing them, and do not include them in the initColors(). That leaves you with an initColors() generating 26 colors (17 to 28, 30, 33 to 44 and 50).
  • if, like me, you only have a few colors to represent for all of you progress bars, pre-generate all ProgressBarPainters you'll ever use and assign the correct one in getTableCellRendererComponent()
  • (or in my case create three private static JProgressBar red, orange, green with appropriate overriden color painters and return them instead of this in getTableCellRendererComponent())
Community
  • 1
  • 1
Matthieu
  • 2,736
  • 4
  • 57
  • 87
2

Alternately, what I ended up doing was extending JPanel and painting myself a progress bar with the color I want:

Custom-drawn progress bar with colors

public class ProgressBarCellRenderer extends JPanel implements TableCellRenderer {

    int val = 0;

    private static Paint generatePaint(Color c, int height) {
        return new LinearGradientPaint(0.0f, 0.0f, 0.0f, (float)height, new float[]{0.0f, 0.5f, 1.0f}, new Color[]{c.darker(), c.brighter(), c.darker()}, CycleMethod.REFLECT);
    }

    private static Paint greenPaint = generatePaint(Color.GREEN);
    private static Paint orangePaint = generatePaint(Color.ORANGE);
    private static Paint redPaint = generatePaint(Color.RED);

    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D)g;
        int x = 1;
        int y = 1;
        int w = getWidth()-2;
        int h = getHeight()-2;
        g2d.setColor(Color.LIGHT_GRAY);
        g2d.fillRect(x, y, w, h);
        Paint backPaint;
        if (val >= 75)
            backPaint = greenPaint;
        else if (val >= 50)
            backPaint = orangePaint;
        else
            backPaint = redPaint;
        g2d.setPaint(backPaint);
        int wd = (int)Math.round(w * val / 100.0);
        g2d.fillRect(x, y, wd, h);
        g2d.draw3DRect(x, y, wd, h, true);
        // Draw some text here if you want
    }
}

Result looks good enough for me and I find it a lot cleaner (and efficient) than the "Nimbus hack"!

Community
  • 1
  • 1
Matthieu
  • 2,736
  • 4
  • 57
  • 87
  • maybe not good, required deepest look at it, but make required things, +1 for effort :-) – mKorbel Jun 28 '13 at 10:28
  • Yes, I got tired of searching about how to tweak Nimbus so I went for this quickly. If you see some obvious mistakes or improvements, feel free to edit! I'm into Swing for just a month now :) – Matthieu Jun 28 '13 at 11:10