Как изменить цвет панели JProgressbar в ячейке таблицы, используя внешний вид Nimbus

Моя проблема в том, что у меня есть таблица, в которой один из столбцов имеет ProgressBars в ячейках таблицы, я хотел бы динамически изменять цвет полосы ProgressBar динамически на основе номера строки и столбца, однако я не могу этого добиться. Также есть ограничения Nimbus. Я должен переопределить настройки пользовательского интерфейса Nimbus по умолчанию для каждого компонента. Итак, если я хочу динамически изменить цвет полосы ячейки, как я могу добиться этого, не меняя цвет текста ячейки?

 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);
        }
    }         
}

Выше приведен мой фрагмент кода, который я использую TableCellRenderer.


person mbasol    schedule 22.01.2013    source источник


Ответы (2)


Что-то такое? ;)

Другой JProgressBar в ячейках JTable

Проблема заключается в том, что чрезвычайно сложно переопределить цвета Nimbus по умолчанию для конкретного экземпляра компонента (см. swing-nimbus-lf-primary-color-per-component-instance">связанный с этим вопрос).

Большинство художников Nimbus определяют около 50 различных цветов, полученных из одного или двух основных цветов (nimbusBlueGrey и знаменитого nimbusOrange). Лучшим способом было бы переопределить их в свойстве UIDefaults, найденном в свойстве Nimbus.Override компонента, который вы хотите изменить, но это не то, что они сделали (я хотел бы открыть ошибку по этому поводу;), нет серьезно!).

Я пытался добиться того же самого и, наконец, смог... (закройте глаза) скопировать и вставить класс javax.swing.plaf.nimbus.ProgressBarPainter в свой собственный код! Почему? Потому что этот класс является частным для пакета и не может быть переопределен (что было бы немного чище...). Как бы я ненавидел это делать, это сработало...

Вот как его изменить (я не буду публиковать весь код, так как он слишком большой и не очень интересный):

  1. Начните с добавления следующих методов после конструктора ProgressBarPainter(). (в основном это копипаст того, что вы можете найти, следуя методу decodeColor() до AbstractRegionPainter, затем NimbusLookAndFeel, затем NimbusDefaults.getDerivedColor() и, наконец, 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. Скопируйте и вставьте код, статически генерирующий 50 цветов, в метод, который вы будете использовать для их динамического генерирования из нужного цвета:

От :

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);
}

Обратите внимание, что мы передаем нужный цвет в качестве параметра и используем его вместо nimbusOrange, который, по-видимому, является основным цветом для индикаторов выполнения. Мы пытаемся придерживаться способа получения цветов Nimbus.

И измените конструктор ProgressBarPainter(), чтобы включить основной цвет и сгенерировать их:

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
}

Вы найдете, как инициализировать поле ctx в источнике NimbusDefaults. Однако обратите внимание, что перечисление AbstractRegionPainter.PaintContext.CacheMode не видно из измененного класса, поэтому вы не сможете использовать все причудливые функции. К счастью, для AbstractRegionPainter.PaintContext есть более простой конструктор, который его не использует. (В моем случае мне не нужны были все разные состояния, поэтому я использовал значения по умолчанию, но не стесняйтесь добавлять любые другие параметры, чтобы справиться с ними).

И, наконец, Грааль! ;) (Я меняю цвет в соответствии со значением, которое должно быть в процентах: green, если более 75%, orange, если более 50%, и red в противном случае).

@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;
}

Теперь давайте немного «очистим» это (настолько, насколько мы можем назвать это «чистой» работой):

  • удалите назначения для private Color colorXX, которые используют nimbusOrange, так как они будут сгенерированы вызовом initColors().
  • сохраните назначения для тех, которые используют nimbusBlueGrey, так как мы их не меняем, и не включайте их в initColors(). Это оставляет вас с initColors(), генерирующим 26 цветов (от 17 до 28, 30, от 33 до 44 и 50).
  • если, как и у меня, у вас есть только несколько цветов для представления всех ваших индикаторов выполнения, предварительно сгенерируйте все ProgressBarPainter, которые вы когда-либо будете использовать, и назначьте правильный в getTableCellRendererComponent().
  • (или в моем случае создайте три private static JProgressBar red, orange, green с соответствующими переопределенными цветными рисунками и верните их вместо this в getTableCellRendererComponent())
person Matthieu    schedule 28.06.2013

С другой стороны, в итоге я расширил JPanel и закрасил индикатор выполнения нужным мне цветом:

Настраиваемый индикатор выполнения с цветами

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
    }
}

Результат выглядит достаточно хорошо для меня, и я нахожу его намного чище (и эффективнее), чем "взлом Nimbus"!

person Matthieu    schedule 28.06.2013
comment
может и нехорошо, требовалось как можно глубже изучить это, но сделать нужные вещи, +1 за старание :-) - person mKorbel; 28.06.2013
comment
Да, я устал искать, как настроить Nimbus, поэтому я быстро сделал это. Если вы видите некоторые очевидные ошибки или улучшения, не стесняйтесь редактировать! Я в Swing всего месяц :) - person Matthieu; 28.06.2013