1

Why don't the progress bars in the following code update and how would I get them to?

import java.awt.event.{ActionListener, ActionEvent}
import javax.swing.table.{TableCellRenderer, AbstractTableModel}
import javax.swing.{Timer, JTable, JProgressBar, JFrame}

object TestProgressBar {
  val frame = new JFrame()
  val model = new TestModel()
  val table = new JTable(model)

  class TestModel extends AbstractTableModel {
    def getColumnCount = 4

    def getRowCount = 4

    def getValueAt(y: Int, x: Int) = "%d,%d".format(x, y)

    override def setValueAt(value: Any, row: Int, col: Int) {
      if (col == 0) {
        model.fireTableCellUpdated(row, col);
      }
    }
  }

  class TestCellRenderer extends TableCellRenderer with ActionListener {
    val progressBar = new JProgressBar()
    val timer = new Timer(100, this)

    progressBar.setIndeterminate(true)
    timer.start()

    override def getTableCellRendererComponent(tbl: JTable, value: AnyRef,
                                               isSelected: Boolean,
                                               hasFocus: Boolean,
                                               row: Int, column: Int) = {
      progressBar
    }

    override def actionPerformed(e: ActionEvent) {
      val model = table.getModel
      for (row <- 0 to model.getRowCount) {
        model.setValueAt(0, row, 0)
      }
    }
  }

  def main(args: Array[String]) {
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)

    table.getColumnModel.getColumn(0).setCellRenderer(new TestCellRenderer())

    frame.add(table)

    frame.pack()
    frame.setVisible(true)
  }
}
Simon Morgan
  • 2,018
  • 5
  • 23
  • 36

3 Answers3

3

As @Robin notes, the renderer in only evoked when the model is updated. You need to periodically change the value in the desired row(s). This example uses javax.swing.Timer.

private class TestCellRenderer implements TableCellRenderer, ActionListener {

    JProgressBar bar = new JProgressBar();

    Timer timer = new Timer(100, this);

    public TestCellRenderer() {
        bar.setIndeterminate(true);
        timer.start();
    }

    @Override
    public Component getTableCellRendererComponent(JTable table,
        Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        return bar;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        TableModel model = table.getModel();
        for (int row = 0; row < model.getRowCount(); row++) {
            table.getModel().setValueAt(0, row, 0);
        }
    }
}

You may also want to implement setValueAt(), as the AbstractTableModel implementation is empty:

@Override
public void setValueAt(Object aValue, int row, int col) {
    if (col == 1) {
        // update your internal model and notify listeners
        this.fireTableCellUpdated(row, col);
    }
}

Addendum: For reference, here's a corresponding Java implementation to complement the Scala.

enter image description here

Code:

import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.Timer;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;

/**
* @see http://stackoverflow.com/a/10491290/230513
*/
public class TestProgressBar extends JPanel {

    private JTable table = new JTable(new TestModel());
    public TestProgressBar() {
        table.getColumnModel().getColumn(0).setCellRenderer(new TestCellRenderer());
        table.setPreferredScrollableViewportSize(new Dimension(320, 120));
        this.add(new JScrollPane(table));
    }

    private class TestModel extends AbstractTableModel {

        @Override
        public int getRowCount() {
            return 4;
        }

        @Override
        public int getColumnCount() {
            return 4;
        }

        @Override
        public Object getValueAt(int row, int col) {
            return String.valueOf(row) + ", " + String.valueOf(col);
        }

        @Override
        public void setValueAt(Object aValue, int row, int col) {
            // update internal model and notify listeners
            fireTableCellUpdated(row, col);
        }
    }

    private class TestCellRenderer implements TableCellRenderer, ActionListener {

        JProgressBar bar = new JProgressBar();
        Timer timer = new Timer(100, this);

        public TestCellRenderer() {
            bar.setIndeterminate(true);
            timer.start();
        }

        @Override
        public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            return bar;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            TableModel model = table.getModel();
            for (int row = 0; row < model.getRowCount(); row++) {
                table.getModel().setValueAt(0, row, 0);
            }
        }
    }

    private void display() {
        JFrame f = new JFrame("TestProgressBar");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

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

            @Override
            public void run() {
                new TestProgressBar().display();
            }
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • 1
    This doesn't make any difference; the progress bars are still frozen. – Simon Morgan May 08 '12 at 15:40
  • It works in Java; you may want to update the Scala example in your question to show your `setValueAt()` implementation. What's the difference between `override def` and `def`? – trashgod May 08 '12 at 19:26
  • I've updated my example. The `override` keyword is like the `@Override` annotation in Java except it's mandatory to avoid ambiguity. – Simon Morgan May 08 '12 at 22:47
  • But not `override def getValueAt`? Your timer's action handler updates column 0, while your `setValueAt()` only fires on column 1. – trashgod May 09 '12 at 01:28
  • getValue must be an implementation rather than being overridden. I've updated my example, the code I was running did have the correct column check (and I even tried it without any at all) and the progress bars still aren't updating. – Simon Morgan May 09 '12 at 12:33
  • Could you put your code somewhere so I can compare it to my Scala version? – Simon Morgan May 09 '12 at 12:43
  • Well, since you helped me learn Scala; I even converted to `AbstractTableModel`. See also [Initial Threads](http://download.oracle.com/javase/tutorial/uiswing/concurrency/initial.html). – trashgod May 09 '12 at 15:42
  • At least in Java 1.8.0_31 on Ubuntu trusty, the bars are frozen. Also tried different look&feels with same result: http://www.pic-upload.de/view-27921327/TestProgressBar_050.png.html – Daniel Alder Aug 06 '15 at 13:49
  • No, that's just what `setIndeterminate(true)` looks like; try `setIndeterminate(false)` and incrementing the value with each tick. – trashgod Aug 07 '15 at 03:03
2

Not sure about Scala, but in Swing the component returned by the TableRenderer is just used to create a 'stamp'. So the actual component is not contained in the JTable, only a stamp of it. Meaning that if you update the component afterwards, this is not reflected in the `JTable.

The main benefit is that you can reuse your component in the renderer. E.g. to render Strings, you would only need one JLabel instance on which you update the text and let the renderer return it.

The renderers section in the Swing table tutorial explains this in more detail

Robin
  • 36,233
  • 5
  • 47
  • 99
1

I seem to have solved the problem by overriding the isDisplayable and repaint methods of JProgressBar.

import javax.swing.table.{TableCellRenderer, AbstractTableModel}
import javax.swing.{JTable, JProgressBar, JFrame}

object TestProgressBar {
  val frame = new JFrame()
  val model = new TestModel()
  val table = new JTable(model)

  class TestModel extends AbstractTableModel {
    def getColumnCount = 4

    def getRowCount = 4

    def getValueAt(y: Int, x: Int) = "%d,%d".format(x, y)
  }

  class TestCellRenderer extends TableCellRenderer {
    val progressBar = new JProgressBar() {
      override def isDisplayable = true
      override def repaint() { table.repaint() }
    }

    progressBar.setIndeterminate(true)

    override def getTableCellRendererComponent(tbl: JTable, value: AnyRef,
                                               isSelected: Boolean,
                                               hasFocus: Boolean,
                                               row: Int, column: Int) = {
      progressBar
    }
  }

  def main(args: Array[String]) {
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)

    table.getColumnModel.getColumn(0).setCellRenderer(new TestCellRenderer())

    frame.add(table)

    frame.pack()
    frame.setVisible(true)
  }
}
Simon Morgan
  • 2,018
  • 5
  • 23
  • 36
  • This should not be required if `setValueAt()` invokes `fireTableCellUpdated()`; I was hoping to see how you call `EventQueue.invokeLater(new Runnable() {…})`. – trashgod May 09 '12 at 21:03
  • Unless I'm missing something this seems to be the optimal solution because it does away with the need to have another thread running. I call `invokeLater` like this: https://gist.github.com/2658997 – Simon Morgan May 11 '12 at 11:02
  • The limitation of this approach is that it unnecessarily couples the model and view. The `Timer` is just for demonstration purposes; in practice, the availability of new data would drive the progress state change. +1 for the link. – trashgod May 11 '12 at 14:26