我正在编写一个类来跟踪线程并在 JTable 中显示状态/进度。我想到的是一个 JTable,其中包含所有必要的状态/按钮/等。被布置在一列中,每行一个线程。我使用单元格编辑器来获取表格中的可点击按钮,但我无法解决的问题是所选单元格中的项目不会更新,除非我点击另一个单元格。有没有办法让选定的单元格仍然更新?下面的代码演示了这个问题。单击行中的开始按钮将启动线程,但是在选择该行时,行中的进度不会更新。
import javax.swing.*;
import javax.swing.table.*;
import java.util.Random;
import java.lang.Thread;
import java.lang.Math;
import java.beans.*;
import java.util.concurrent.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
/*
* Program tracks some threads' progress, updating status in
* cells in a JTable.
*
* Inner classes are MyDefTM, ThreadOB and ThreadCell which are
* the table model, the object representing the thread's data and
* the renderer/editor "stamp", respectively.
*/
public class ThreadManager {
public JFrame jFrame;
public JTable jTable;
public MyDefTM tm;
public JScrollPane jsp;
public ThreadManager() {
tm = new MyDefTM();
tm.addColumn("Threads");
jFrame = new JFrame("Thread List");
jTable = new JTable();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void createAndShowGUI() {
/*
* JTable in JScrollPane in JFrame.
*/
jTable.setModel(tm);
jTable.setRowHeight(60);
jsp = new JScrollPane(jTable);
jFrame.getContentPane().add(jsp);
jFrame.pack();
jTable.setDefaultRenderer(Object.class, new ThreadCell());
jTable.setDefaultEditor(Object.class, new ThreadCell());
jTable.setShowHorizontalLines(true);
/*
* Add some test threads.
*/
for (int ii = 0; ii < 5; ii++) {
ThreadOb to = new ThreadOb(ii, jTable);
Vector v = new Vector();
v.add(to);
tm.addRow(v);
}
jFrame.setSize(640, 480);
jFrame.setVisible(true);
return;
}
public static void main(String[] args) {
ThreadManager threadManager = new ThreadManager();
threadManager.createAndShowGUI();
}
/*
* Use DefaultTableModel but make every cell editable.
*/
public class MyDefTM extends DefaultTableModel {
public boolean isCellEditable(int row, int column) {
return true;
}
}
/*
* Represents a thread as stored in the table. Stores
* an ID for the thread, its progress and the result.
*/
public class ThreadOb {
public int threadID;
public int threadProgress;
public JTable jTable;
public SwingWorker workerThread;
public String threadResult;
public ThreadOb(int id, JTable t) {
jTable = t;
threadID = id;
threadProgress = 0;
}
public void buttonAction() {
/*
* Perform a task that takes just a little while to finish.
*/
workerThread = new SwingWorker<String,Object>() {
@Override
public String doInBackground() throws InterruptedException {
int prog = 0;
Random rand = new Random(42);
while (prog < 100) {
prog += Math.abs(rand.nextInt() % 5);
setProgress(prog);
Thread.sleep(1000);
setProgress(Math.min(prog, 100));
}
return "42";
}
};
workerThread.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
if (e.getPropertyName() == "state" &&
e.getNewValue() == "DONE") {
try {
threadResult = (String) workerThread.get();
} catch (Exception ignore) { }
}
if (e.getPropertyName() == "progress") {
threadProgress = ( (Integer) e.getNewValue()).intValue();
/*
* Couple the model and view together. The table cells will
* not update without this line.
*/
((MyDefTM) jTable.getModel()).fireTableDataChanged();
}
}
});
workerThread.execute();
}
}
/*
* Represents the graphical "stamp" for the renderer and editor.
*/
public class ThreadCell extends AbstractCellEditor
implements TableCellRenderer, TableCellEditor {
private JLabel threadIDLabel;
private JLabel threadProgressLabel;
private JPanel threadPane;
private JButton but;
private JPanel statuspane;
private JProgressBar jpb;
private JPanel pane;
private Border offBorder;
private Border onBorder;
private ThreadOb val;
/*
* Establish the layout of the cells in the JTable.
* The selected cell has a red border.
*/
public ThreadCell() {
val = null;
threadIDLabel = new JLabel();
threadProgressLabel = new JLabel();
threadPane = new JPanel();
threadPane.setLayout(new BoxLayout(threadPane, BoxLayout.X_AXIS));
threadPane.add(threadIDLabel);
threadPane.add(threadProgressLabel);
statuspane = new JPanel();
statuspane.setLayout(new BoxLayout(statuspane, BoxLayout.X_AXIS));
statuspane.add(threadPane);
statuspane.add(Box.createHorizontalGlue());
but = new JButton("Start");
statuspane.add(but);
jpb = new JProgressBar(0, 100);
jpb.setStringPainted(true);
pane = new JPanel();
pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
pane.add(statuspane);
pane.add(jpb);
offBorder = BorderFactory.createEmptyBorder(2,2,2,2);
onBorder = BorderFactory.createLineBorder(java.awt.Color.RED, 2);
pane.setBorder(offBorder);
but.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
val.buttonAction();
/*
* Uncomment to deselect the cell after clicking.
*/
//fireEditingStopped();
}
});
}
/*
* Populate the cell with the correct values.
*/
public void update(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
if (value == null) {
return;
}
val = (ThreadOb) value;
threadIDLabel.setText("ID: " + ((ThreadOb) value).threadID + " ");
threadProgressLabel.setText("Progress: " +
((ThreadOb) value).threadProgress + "%");
jpb.setValue(((ThreadOb) value).threadProgress);
if (hasFocus) {
pane.setBorder(onBorder);
} else {
pane.setBorder(offBorder);
}
}
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
update(table, value, isSelected, hasFocus, row, column);
return pane;
}
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row,
int column) {
update(table, value, isSelected, true, row, column);
return pane;
}
public Object getCellEditorValue() {
return val;
}
}
}