I am having a slight problem with a GUI where the ProgressCellRenderer is not updating until I either click on it or highlight it or even resize the window. I have slimmed down the code to just use generic data and using sleeps to emulate a long running task.
What have I missed that is causing it to not update until I cause an update on the gui itself?
This is minimum working code that will run.
public class Test
{
public static void main(String[] args)
{
new Test();
}
public Test()
{
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex)
{
}
UpdatableTableModel model = new UpdatableTableModel();
JTable table = new JTable();
table.setModel(model);
table.getColumn("Status").setCellRenderer(new ProgressCellRender());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
StringFinderWorker worker = new StringFinderWorker(model);
worker.execute();
}
});
}
public class ProgressCellRender extends JProgressBar implements TableCellRenderer
{
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
int progress = 0;
if (value instanceof Float)
{
progress = Math.round(((Float) value) * 100f);
}
else if (value instanceof Integer)
{
progress = (int) value;
}
setValue(progress);
return this;
}
}
public class RowData
{
private String string;
private float status;
public RowData(String string)
{
this.string = string;
this.status = 0f;
}
public String getString()
{
return string;
}
public float getStatus()
{
return status;
}
public void setStatus(float status)
{
this.status = status;
}
}
public class UpdatableTableModel extends AbstractTableModel
{
private static final long serialVersionUID = 1L;
private List<RowData> rows;
private Map<String, RowData> mapLookup;
public UpdatableTableModel()
{
rows = new ArrayList<>(25);
mapLookup = new HashMap<>(25);
}
@Override
public int getRowCount()
{
return rows.size();
}
@Override
public int getColumnCount()
{
return 2;
}
@Override
public String getColumnName(int column)
{
String name = "??";
switch (column)
{
case 0:
name = "The String";
break;
case 1:
name = "Status";
break;
}
return name;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
RowData rowData = rows.get(rowIndex);
Object value = null;
switch (columnIndex)
{
case 0:
value = rowData.getString();
break;
case 1:
value = rowData.getStatus();
break;
}
return value;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
RowData rowData = rows.get(rowIndex);
switch (columnIndex)
{
case 3:
if (aValue instanceof Float)
{
rowData.setStatus((float) aValue);
}
break;
}
}
public void addString(String string)
{
RowData rowData = new RowData(string);
mapLookup.put(string, rowData);
rows.add(rowData);
fireTableRowsInserted(rows.size() - 1, rows.size() - 1);
}
protected void updateStatus(String string, int progress)
{
RowData rowData = mapLookup.get(string);
if (rowData != null)
{
int row = rows.indexOf(rowData);
float p = (float) progress / 100f;
setValueAt(p, row, 3);
fireTableCellUpdated(row, 3);
}
}
}
public class StringFinderWorker extends SwingWorker<List<String>, String>
{
private UpdatableTableModel model;
public StringFinderWorker(UpdatableTableModel model)
{
this.model = model;
}
@Override
protected void process(List<String> chunks)
{
for (String string : chunks)
{
model.addString(string);
}
}
protected ArrayList<String> getStrings()
{
ArrayList<String> strings = new ArrayList<String>();
for (int i = 0; i < 24; i++)
{
strings.add("test: " + i);
}
return strings;
}
@Override
protected List<String> doInBackground() throws Exception
{
List<String> strings = getStrings();
for (int i = 0; i < strings.size(); i++)
{
publish(strings.get(i));
}
return strings;
}
@Override
protected void done()
{
try
{
List<String> strings = get();
for (String string : strings)
{
new StringDownloaderWorker(model, string).execute();
}
}
catch (Exception exp)
{
exp.printStackTrace();
}
}
}
public class StringDownloaderWorker extends SwingWorker<String, String>
{
private String currentString;
private UpdatableTableModel model;
public StringDownloaderWorker(UpdatableTableModel model, String string)
{
this.currentString = string;
this.model = model;
addPropertyChangeListener(new PropertyChangeListener()
{
@Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getPropertyName().equals("progress"))
{
StringDownloaderWorker.this.model.updateStatus(currentString, (int) evt.getNewValue());
}
}
});
}
@Override
protected String doInBackground() throws Exception
{
for (int i = 0; i <= 100; i++)
{
setProgress(i);
try
{
Thread.sleep(1000);
}
catch (InterruptedException ie)
{
//
}
}
return currentString;
}
}
}
You're going to kick yourself when you see this...
The column indexes that you are using to register the fact that the cell has changed are wrong...
You are using setValueAt(p, row, 3);
, when the progress column is actually 1
, so you should actually be using setValueAt(p, row, 1);
This also means that the switch
statement in your setValueAt
method will need to change.
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
RowData rowData = rows.get(rowIndex);
switch (columnIndex) {
case 1:
if (aValue instanceof Float) {
rowData.setStatus((float) aValue);
fireTableCellUpdated(rowIndex, columnIndex);
}
break;
}
}
You will also note that I moved the fireTableCellUpdated
call to this method, as you never know when setValueAt
might be called.