I have a problem that I can't solve using JTable with editor and renderer containing JProgressBar inside. I have a JButton that is use to start a thread that increment the progress bar value. The problem is when I click on the JTable's cell, the progress bar is not refreshing itself anymore. I tried to add a ChangeListener to the progress bar that terminate edits, but then other cells are not editable as well.
Here is a SSCCE:
public class TableTest {
final static MyObjectTableModel model = new MyObjectTableModel();
final static JTable table = new JTable(model);
private static Map<Integer, Future> mapSubmittedReadProgress = new HashMap<Integer, Future>();
final static StartProgressActionListener progressActionListener = new StartProgressActionListener();
final static CloseActionListener closeActionListener = new CloseActionListener();
final static ProgressChangeListener progressChangeListener = new ProgressChangeListener();
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new TableTest().createGUI();
}
});
}
public static class MyObjectTableModel extends AbstractTableModel {
private LinkedList<MyObject> myList;
public MyObjectTableModel() {
super();
myList = new LinkedList<MyObject>();
}
public MyObjectTableModel(SortedSet<MyObject> myObjects) {
super();
this.myList = new LinkedList<MyObject>(myObjects);
}
public void addRow(MyObject myObject) {
myList.add(myObject);
fireTableRowsInserted(myList.size() - 1, myList.size() - 1);
}
public void removeRow(int row) {
myList.remove(row);
fireTableRowsDeleted(row, row);
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
myList.set(rowIndex, (MyObject) aValue);
fireTableCellUpdated(rowIndex, 0);
}
@Override
public int getRowCount() {
return myList.size();
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 0:
return MyObject.class;
default:
throw new IllegalArgumentException("invalid column: " + columnIndex);
}
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
switch (columnIndex) {
case 0:
return myList.get(rowIndex);
default:
throw new IllegalArgumentException("invalid column: " + columnIndex);
}
}
public MyObject getMyObjectAt(int row) {
return myList.get(row);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
public int getIndexOf(MyObject myObject) {
return myList.indexOf(myObject);
}
}
private static void createGUI() {
JFrame f = new JFrame("TableTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
table.getModel().addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setPreferredRowHeights();
}
});
}
});
for (int i = 0; i < 16; i++) {
MyObject myObject = new MyObject();
myObject.setText1("" + i);
model.addRow(myObject);
}
table.setOpaque(false);
table.setShowGrid(false);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setDefaultRenderer(MyObject.class, new MyTableCellRenderer());
table.setDefaultEditor(MyObject.class, new MyTableCellEditor());
table.setFillsViewportHeight(true);
f.add(new JScrollPane(table));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static void setPreferredRowHeights() {
for (int row = 0; row < table.getRowCount(); row++) {
setPreferredRowHeight(row);
}
}
private static void setPreferredRowHeight(int row) {
int prefHeight = getPreferredRowHeight(row);
table.setRowHeight(row, prefHeight);
}
public static int getPreferredRowHeight(int row) {
int pref = 0;
for (int column = 0; column < table.getColumnCount(); column++) {
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component comp = table.prepareRenderer(renderer, row, column);
pref = Math.max(pref, comp.getPreferredSize().height);
}
return pref > 0 ? pref : table.getRowHeight();
}
private static class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor {
private MyObjectPanel myObjectPanel = new MyObjectPanel();
private transient List<CellEditorListener> listeners;
public MyTableCellEditor() {
myObjectPanel.addStartProgressActionListener(progressActionListener);
myObjectPanel.addCloseActionListener(closeActionListener);
// myObjectPanel.addProgressChangeListener(progressChangeListener);
listeners = new ArrayList<>();
}
@Override
public boolean isCellEditable(EventObject e) {
return true;
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
MyObject myObject = (MyObject) value;
myObjectPanel.setMyObject(myObject);
return myObjectPanel;
}
@Override
public Object getCellEditorValue() {
MyObject myObject = myObjectPanel.getMyObject();
return myObject;
}
@Override
public void addCellEditorListener(CellEditorListener l) {
listeners.add(l);
}
@Override
public void removeCellEditorListener(CellEditorListener l) {
listeners.remove(l);
}
@Override
protected void fireEditingStopped() {
ChangeEvent ce = new ChangeEvent(this);
for (int i = listeners.size() - 1; i >= 0; i--) {
((CellEditorListener) listeners.get(i)).editingStopped(ce);
}
}
}
private static class MyTableCellRenderer implements TableCellRenderer {
private MyObjectPanel myObjectPanel = new MyObjectPanel();
public MyTableCellRenderer() {
myObjectPanel.addStartProgressActionListener(progressActionListener);
myObjectPanel.addCloseActionListener(closeActionListener);
// myObjectPanel.addProgressChangeListener(progressChangeListener);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
MyObject myObject = (MyObject) value;
myObjectPanel.setMyObject(myObject);
return myObjectPanel;
}
}
private static class ProgressChangeListener implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
if (table.isEditing()) {
table.getCellEditor().stopCellEditing();
}
}
}
private static class CloseActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (table.isEditing()) {
table.getCellEditor().stopCellEditing();
}
model.removeRow(table.getSelectedRow());
}
}
private static class StartProgressActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (table.isEditing()) {
table.getCellEditor().stopCellEditing();
}
final ExecutorService executor = Executors.newFixedThreadPool(1);
int row = table.getSelectedRow();
MyObject myObject = (MyObject) table.getValueAt(row, 0);
myObject.setStartEnable(false);
myObject.setText1Enable(false);
myObject.setText2Enable(false);
Runnable progressRunnable = new ProgressRunnable(table.getSelectedRow(), myObject);
final Future<?> submit = executor.submit(progressRunnable);
mapSubmittedReadProgress.put(table.getSelectedRow(), submit);
}
}
private static class ProgressRunnable implements Runnable {
private ExecutorService executor;
private long beT;
private int dur = 30; // s
private int progress = 0;
private int row;
private MyObject myObject;
public ProgressRunnable(int row) {
}
private ProgressRunnable(int selectedRow, MyObject myObject) {
this.row = selectedRow;
this.myObject = myObject;
beT = System.currentTimeMillis();
}
@Override
public void run() {
boolean abort = false;
int i = 0;
while (i <= dur && !abort) {
final long curT = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
abort = true;
executor.shutdown();
}
if (Thread.currentThread().isInterrupted()) {
abort = true;
executor.shutdown();
}
progress = (int) Math.round(100 * ((double) (curT - beT) / 1000) / dur);
myObject.setProgress(progress);
table.setValueAt(myObject, row, 0);
i++;
}
}
}
// My object
static class MyObject {
private String text1;
private String text2;
private int progress;
private boolean startEnable = true;
private boolean abortEnable = true;
private boolean text1Enable = true;
private boolean text2Enable = true;
private boolean closeEnable = true;
public String getText1() {
return text1;
}
public void setText1(String text1) {
this.text1 = text1;
}
public String getText2() {
return text2;
}
public void setText2(String text2) {
this.text2 = text2;
}
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
this.progress = progress;
}
public boolean isStartEnable() {
return startEnable;
}
public void setStartEnable(boolean startEnable) {
this.startEnable = startEnable;
}
public boolean isAbortEnable() {
return abortEnable;
}
public void setAbortEnable(boolean abortEnable) {
this.abortEnable = abortEnable;
}
public boolean isText1Enable() {
return text1Enable;
}
public void setText1Enable(boolean text1Enable) {
this.text1Enable = text1Enable;
}
public boolean isText2Enable() {
return text2Enable;
}
public void setText2Enable(boolean text2Enable) {
this.text2Enable = text2Enable;
}
public boolean isCloseEnable() {
return closeEnable;
}
public void setCloseEnable(boolean closeEnable) {
this.closeEnable = closeEnable;
}
}
// MyObjectPanel
static class MyObjectPanel extends javax.swing.JPanel {
/**
* Creates new form MyObjectPanel
*/
public MyObjectPanel() {
initComponents();
}
/**
* This method is called from within the constructor to initialize the
* form. WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jTextField1 = new javax.swing.JTextField();
jTextField2 = new javax.swing.JTextField();
jProgressBar1 = new javax.swing.JProgressBar();
btnStart = new javax.swing.JButton();
btnStop = new javax.swing.JButton();
btnClose = new javax.swing.JButton();
btnStart.setText("Start");
btnStop.setText("Stop");
btnClose.setText("Close");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jTextField1)
.addComponent(jTextField2)
.addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(btnStart)
.addGap(18, 18, 18)
.addComponent(btnStop)
.addGap(18, 18, 18)
.addComponent(btnClose)
.addGap(0, 199, Short.MAX_VALUE)))
.addContainerGap()));
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(btnStart)
.addComponent(btnStop)
.addComponent(btnClose))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));
}// </editor-fold>
// Variables declaration - do not modify
private javax.swing.JButton btnClose;
private javax.swing.JButton btnStart;
private javax.swing.JButton btnStop;
private javax.swing.JProgressBar jProgressBar1;
private javax.swing.JTextField jTextField1;
private javax.swing.JTextField jTextField2;
// End of variables declaration
void setMyObject(MyObject myObject) {
jTextField1.setText(myObject.getText1());
jTextField2.setText(myObject.getText2());
jProgressBar1.setValue(myObject.getProgress());
btnStart.setEnabled(myObject.isStartEnable());
btnClose.setEnabled(myObject.isCloseEnable());
btnStop.setEnabled(myObject.isAbortEnable());
jTextField1.setEnabled(myObject.isText1Enable());
jTextField2.setEnabled(myObject.isText2Enable());
}
MyObject getMyObject() {
MyObject myObject = new MyObject();
myObject.setText1(jTextField1.getText());
myObject.setText2(jTextField2.getText());
myObject.setProgress(jProgressBar1.getValue());
myObject.setStartEnable(btnStart.isEnabled());
myObject.setCloseEnable(btnClose.isEnabled());
myObject.setAbortEnable(btnStop.isEnabled());
myObject.setText1Enable(jTextField1.isEnabled());
myObject.setText2Enable(jTextField2.isEnabled());
return myObject;
}
void addStartProgressActionListener(ActionListener progressActionListener) {
btnStart.addActionListener(progressActionListener);
}
void addCloseActionListener(ActionListener closeActionListener) {
btnClose.addActionListener(closeActionListener);
}
void addProgressChangeListener(ChangeListener changeListener) {
jProgressBar1.addChangeListener(changeListener);
}
}
}
Thanks for any help.
There's a few things to change to make it work.
First, your
MyObjectPanel
class needs to have aMyObject
variable and return that instead of a new one each timegetMyObject
is called. This variable is set whensetMyObject
is called onMyObjectPanel
.Second, your
MyObject
class needs aint row
variable to be able to know which row it represents. Make a getter too for this variable. Set this variable in thefor
loop increateGUI
.Last, in the
while
of yourProgressRunnable
inrun
, you need to stop editing the cell if that's the one your progress is running. To do that, you check withJTable.getEditingRow
, if the tableisEditing
that this row is the same asmyObject.getRow
that the progressmyObject
variable. Something like this :Also note that I changed the
setValue
there forfireTableRowsUpdated
instead.This should make it work as you want.
You can remove your
TableModelListener
increateGUI
and only callsetPreferredRowHeights
once beforepack
.