JProgressBar in JTable not updating
Asked Answered
S

3

2

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)
  }
}
Spasm answered 7/5, 2012 at 18:11 Comment(0)
P
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 https://mcmap.net/q/903428/-jprogressbar-in-jtable-not-updating
*/
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();
            }
        });
    }
}
Powerboat answered 8/5, 2012 at 1:21 Comment(9)
This doesn't make any difference; the progress bars are still frozen.Spasm
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?Powerboat
I've updated my example. The override keyword is like the @Override annotation in Java except it's mandatory to avoid ambiguity.Spasm
But not override def getValueAt? Your timer's action handler updates column 0, while your setValueAt() only fires on column 1.Powerboat
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.Spasm
Could you put your code somewhere so I can compare it to my Scala version?Spasm
Well, since you helped me learn Scala; I even converted to AbstractTableModel. See also Initial Threads.Powerboat
At least in Java 1.8.0_31 on Ubuntu trusty, the bars are frozen. Also tried different look&feels with same result: pic-upload.de/view-27921327/TestProgressBar_050.png.htmlGaleiform
No, that's just what setIndeterminate(true) looks like; try setIndeterminate(false) and incrementing the value with each tick.Powerboat
A
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

Aphrodite answered 7/5, 2012 at 18:38 Comment(2)
So is there a way around the problem?Spasm
+1 for concept; it should work the same in Scala. I think it just needs something to drive it, for example.Powerboat
S
2

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)
  }
}
Spasm answered 9/5, 2012 at 15:48 Comment(3)
This should not be required if setValueAt() invokes fireTableCellUpdated(); I was hoping to see how you call EventQueue.invokeLater(new Runnable() {…}).Powerboat
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: gist.github.com/2658997Spasm
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.Powerboat

© 2022 - 2024 — McMap. All rights reserved.