我正在编写一个类来跟踪线程并在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;
}
}
}
答案 0 :(得分:8)
有太多错误(抱歉),这有点吓人......
此e.getPropertyName() == "state"
不是String
比较完成的方式。您的if
声明永远不会是true
。使用类似"state".equals(e.getPropertyName())
的内容......
由于单元格仍处于编辑模式,因此UI未开始更新。
在您的单元格编辑器actionPerformed
方法中,添加stopCellEditing
语句,这将关闭单元格编辑器并允许单元格渲染器完成其工作
这不是真正使用JTable
更新了可能的示例
这只是一个可能的解决方案的例子。
JTable
假设显示数据的行和列。您目前设置它的方式,使用GridBagLayout
- 恕我直言
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;
import java.util.Random;
import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
public class ThreadMonitorExample {
public static void main(String[] args) {
new ThreadMonitorExample();
}
public ThreadMonitorExample() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
ThreadTableModel model = new ThreadTableModel();
model.add(new Task(0, model));
model.add(new Task(1, model));
model.add(new Task(2, model));
model.add(new Task(3, model));
model.add(new Task(4, model));
JTable table = new JTable(model);
TaskProgressRenderer progressRenderer = new TaskProgressRenderer();
TaskStatusRenderer statusRenderer = new TaskStatusRenderer();
table.getColumnModel().getColumn(1).setCellRenderer(progressRenderer);
table.getColumnModel().getColumn(2).setCellRenderer(statusRenderer);
table.getColumnModel().getColumn(2).setCellEditor(new TaskStatusEditor());
table.setRowHeight(
Math.max(getCellRendererHeight(table, 0, 1, progressRenderer),
getCellRendererHeight(table, 0, 2, statusRenderer)));
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected int getCellRendererHeight(JTable table, int row, int column, TableCellRenderer renderer) {
return renderer.getTableCellRendererComponent(table, table.getValueAt(row, column), true, true, row, column).getPreferredSize().height;
}
public class ThreadTableModel extends AbstractTableModel {
private String[] headers = {"ID", "Progress", "Action"};
private List<Task> tasks;
public ThreadTableModel() {
tasks = new ArrayList<>(25);
}
public void add(Task task) {
int row = getRowCount();
tasks.add(task);
fireTableRowsInserted(row, getRowCount() - 1);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 2 && !tasks.get(rowIndex).isRunning() && !tasks.get(rowIndex).isDone();
}
@Override
public int getRowCount() {
return tasks.size();
}
@Override
public int getColumnCount() {
return headers.length;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
Class clazz = Object.class;
switch (columnIndex) {
case 0:
clazz = String.class;
break;
case 1:
clazz = Integer.class;
break;
}
return clazz;
}
@Override
public String getColumnName(int column) {
return headers[column];
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Task task = tasks.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = task.getID();
break;
case 1:
value = task.getProgress();
break;
case 2:
value = task;
break;
}
return value;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
System.out.println("setValueAt " + rowIndex + "x" + columnIndex);
if (columnIndex == 2) {
Task task = tasks.get(rowIndex);
if (!task.isRunning() && !task.isDone()) {
task.execute();
fireTableCellUpdated(rowIndex, columnIndex);
}
}
}
public void updated(Task task) {
int row = tasks.indexOf(task);
System.out.println("Row updated " + row);
fireTableRowsUpdated(row, row);
}
}
public class TaskProgressRenderer extends JProgressBar implements TableCellRenderer {
public TaskProgressRenderer() {
setOpaque(false);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Integer) {
int progress = (int) value;
System.out.println("cellProgress = " + progress);
setValue(progress);
}
if (isSelected) {
setBackground(table.getSelectionBackground());
setOpaque(true);
} else {
setBackground(table.getBackground());
setOpaque(false);
}
return this;
}
}
public class TaskStatusEditor extends AbstractCellEditor implements TableCellEditor {
private JPanel editor;
public TaskStatusEditor() {
editor = new JPanel();
editor.add(new JButton("Start"));
}
@Override
public boolean isCellEditable(EventObject e) {
return true;
}
@Override
public Object getCellEditorValue() {
return null;
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
stopCellEditing();
}
});
return editor;
}
}
public class TaskStatusRenderer extends JPanel implements TableCellRenderer {
private JButton start;
private JLabel label;
public TaskStatusRenderer() {
setOpaque(false);
start = new JButton("Start");
label = new JLabel();
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
removeAll();
if (value instanceof Task) {
Task task = (Task) value;
if (task.isDone()) {
try {
label.setText(task.get());
} catch (Exception ex) {
label.setText(ex.getMessage());
}
add(label);
} else if (task.isRunning()) {
label.setText("Working");
add(label);
} else {
add(start);
}
}
if (isSelected) {
setBackground(table.getSelectionBackground());
setOpaque(true);
} else {
setBackground(table.getBackground());
setOpaque(false);
}
return this;
}
}
public class Task extends SwingWorker<String, Object> {
private int id;
private String threadResult;
private ThreadTableModel tableModel;
private boolean running;
public Task(int id, ThreadTableModel tableModel) {
this.tableModel = tableModel;
this.id = id;
addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
System.out.println(e.getPropertyName());
if ("state".equalsIgnoreCase(e.getPropertyName())
&& "DONE".equalsIgnoreCase(e.getNewValue().toString())) {
try {
threadResult = (String)get();
} catch (Exception ignore) {
ignore.printStackTrace();
}
}
if ("progress".equalsIgnoreCase(e.getPropertyName())) {
System.out.println("task.getProgress = " + getProgress());
Task.this.tableModel.updated(Task.this);
}
}
});
}
public boolean isRunning() {
return running;
}
public int getID() {
return id;
}
@Override
protected String doInBackground() throws Exception {
running = true;
setProgress(0);
int prog = 0;
Random rand = new Random(42);
while (prog < 100) {
prog += Math.abs(rand.nextInt() % 5);
Thread.sleep(250);
setProgress(Math.min(prog, 100));
}
return "42";
}
}
}