对不起,有点长,但它是一个有点麻烦...SwingWorker的过程()与聚结块GUI更新困难
SwingWorker的工作完全按预期在我的应用程序,除了一个棘手的问题,我”因为API清楚说明的是完全可能的和正常的,所以如果大块在过程()中合并,则努力解决。
问题出现了,例如,当我有一个JDialog,它以“任务正在发生,请稍候”开头:因此一个块发布在doInBackground()
中,然后到达process()
并设置一个JDialog。
当doInBackground中冗长的任务完成后,我“发布”2个更多的命令:一个说“将JDialog的消息更改为”等待GUI更新“,另一个说”使用结果填充JTable我送你“。
关于这一点,如果你发送一个JTable大量的新数据来替换它的TableModel的向量,Swing实际上可能需要一个不可忽视的时间来自我修改......因为这个原因,我想告诉用户:“冗长的任务已经完成,但我们现在正在等待Swing更新GUI”。
奇怪的是,如果这两条指令以2个合并块的形式到达,我发现JDialog只能被部分更新:setTitle(“blab”)导致JDialog的标题被更改......但所有其他直到JTable的主GUI更新完成后,对JDialog的更改才会暂停。
如果我设计了一些东西,使doInBackground在发布块之间稍有延迟,那么JDialog会更新OK。显然,通过合并的块,我使用一个循环来一个接一个地循环,所以我想在每个循环的末尾放一个Timer。这没有效果。
我也尝试了JDialog上“验证”和“绘制”和“重绘”的无数组合。
因此,问题是:如何让我在处理合并块的迭代之间的process()内更新自己的GUI。
NB我也试过其他的东西:如果它们是多个的话,重新发布块。这样做的麻烦在于,考虑到事物的异步性,它可能导致块以错误的顺序发布,就像在doInBackground中一样,不可避免的是,事情正在不断发布。另外,这种解决方案只是不雅观。
后... 的要求,这里是一个SSCCE:
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.util.*;
class Coalescence extends SwingWorker<Object, Object> {
int DISPLAY_WAIT_FOR_TASK = 0; int DISPLAY_WAIT_FOR_GUI_UPDATE = 1; int UPDATE_TABLE_IN_GUI = 2; int SET_UP_GUI = 3;
private Object[][] m_dataTable;
private JTable m_table;
private JFrame m_frame;
private JOptionPane m_pane;
private JDialog m_jDialog;
private FontMetrics m_fontMetrics;
private Dimension m_intercellSpacing;
@Override
protected Object doInBackground() throws Exception {
publish(SET_UP_GUI);
publish(DISPLAY_WAIT_FOR_TASK);
Random rand = new Random();
String s = "String for display, one two three four five six seven eight";
m_dataTable = new Object[ 20000 ][];
for(int i = 0; i < 20000; i++){
Object[] row = new Object[ 20 ];
for(int j = 0; j < 20; j++){
// random length string - so column width computation has something to do...
int endIndex = rand.nextInt(40);
row[ j ] = s.substring(0, endIndex);
}
m_dataTable[ i ] = row;
// slow the "lengthy" non-EDT task artificially for sake of SSCCE
if(i % 10 == 0)
Thread.sleep(1L);
}
publish(DISPLAY_WAIT_FOR_GUI_UPDATE);
// *** LINE TO COMMENT OUT ***
Thread.sleep(100L);
publish(UPDATE_TABLE_IN_GUI);
return null;
}
protected void process(java.util.List<Object> chunks){
p("no chunks " + chunks.size());
// "CHUNK PROCESSING LOOP"
for(int i = 0, n_chunks = chunks.size(); i < n_chunks; i++){
int value = (Integer)chunks.get(i);
p("processing chunk " + value);
if(value == SET_UP_GUI){
m_frame = new JFrame();
m_frame.setPreferredSize(new Dimension(800, 400));
m_frame.setVisible(true);
JScrollPane jsp = new JScrollPane();
jsp.setBounds(10, 10, 600, 300);
m_frame.getContentPane().setLayout(null);
m_frame.getContentPane().add(jsp);
m_table = new JTable();
jsp.setViewportView(m_table);
m_frame.pack();
m_fontMetrics = m_table.getFontMetrics(m_table.getFont());
m_intercellSpacing = m_table.getIntercellSpacing();
}
else if(value == DISPLAY_WAIT_FOR_TASK){
m_pane = new JOptionPane("Waiting for results...");
Object[] options = { "Cancel" };
m_pane.setOptions(options);
// without these 2 sQLCommand, just pressing Return will not cause the "Cancel" button to fire
m_pane.setInitialValue("Cancel");
m_pane.selectInitialValue();
m_jDialog = m_pane.createDialog(m_frame, "Processing");
m_jDialog.setVisible(true);
}
else if (value == DISPLAY_WAIT_FOR_GUI_UPDATE){
// this if clause changes the wording of the JDialog/JOptionPane (and gets rid of its "Cancel" option button)
// because at this point we are waiting for the GUI (Swing) to update the display
m_pane.setOptions(null);
m_pane.setMessage("Populating...");
m_jDialog.setTitle("Table being populated...");
}
else if (value == UPDATE_TABLE_IN_GUI){
Object[] headings = { "one", "two", "three", "four", "five", "six", "one", "two", "three", "four", "five", "six",
"one", "two", "three", "four", "five", "six", "19", "20" };
m_table.setModel(new javax.swing.table.DefaultTableModel(m_dataTable, headings));
// lengthy task which can only be done in the EDT: here, computing the preferred width for columns by examining
// the width (using FontMetrics) of each String in each cell...
for(int colIndex = 0, n_cols = 20; i < n_cols; i++){
int prefWidth = 0;
javax.swing.table.TableColumn column = m_table.getColumnModel().getColumn(colIndex);
int modelColIndex = m_table.convertColumnIndexToModel(colIndex);
for(int rowIndex = 0, n_rows = m_table.getRowCount(); rowIndex < n_rows; rowIndex++){
Object cellObject = m_table.getModel().getValueAt(rowIndex, modelColIndex);
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)m_table.getCellRenderer(rowIndex, colIndex);
int margins = 0;
if(renderer instanceof Container){
Insets insets = renderer.getInsets();
margins = insets.left + insets.right ;
}
Component comp = renderer.getTableCellRendererComponent(m_table, cellObject, true, false, rowIndex, colIndex);
if(comp instanceof JLabel){
String cellString = ((JLabel)comp).getText();
int width = SwingUtilities.computeStringWidth(m_fontMetrics, cellString) + margins;
// if we have discovered a String which is wider than the previously set widest width String... change prefWidth
if(width > prefWidth){
prefWidth = width;
}
}
}
prefWidth += m_intercellSpacing.width;
column.setPreferredWidth(prefWidth);
// slow things in EDT down a bit (artificially) for the sake of this SSCCE...
try {
Thread.sleep(20L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
m_jDialog.dispose();
}
}
}
public static void main(String[] a_args){
Coalescence c = new Coalescence();
c.execute();
try {
c.get();
} catch (Exception e) {
e.printStackTrace();
}
}
static void p(String s){
System.out.println(s);
}
}
...程序包括5个阶段:1)建立GUI 2)提出了一个消息,说“等待任务完成“3)”冗长的“非EDT任务4)对消息进行更改,使其现在说”等待GUI更新表格“5)更新GUI中的表格(随后处理的JDialog/JOptionPane)。
我不明白的是为什么,如果你注释掉上面的doInBackground中的Thread.sleep()行,那么JDialog的行为会很奇怪:标题随即更新,但JOptionPane的文本不会改变,并且“取消”按钮不会被删除。
可以看出,不同之处在于,如果没有Thread.sleep()行,两个数据块会合并,并在EDT中一个接一个地执行......我已经尝试过在“块处理循环”结尾处运行一个短的Timer,并尝试使用Thread.yield()...实质上我试图强制GUI全面更新JDialog及其所有组件。在开始更新JTable之前...
任何想法都会被赞赏。
然后提供了一个[SSCCE](http://sscce.org/),其表现出你描述的问题。 – trashgod
@trashgod公平点...将对此工作。我使用的是Jython而不是Java,但会尝试使沙漏起来 –
有一个最小的例子[这里](http://stackoverflow.com/questions/4637215/can-a-progress-bar-be-used-in- a-class-outside-main/4637725#4637725)和[这里](https://sites.google.com/site/drjohnbmatthews/randomdata)。这听起来像你可能一次更新整个模型,而不是逐步更新。 – trashgod