抱歉,有点长,但是有点复杂......
SwingWorker 在我的应用程序中完全按照预期工作,除了一个我正在努力解决的棘手问题,如果 block 到达 process() 合并,因为 API 明确指出这是完全可能且正常的。
问题就来了,例如,当我有一个 JDialog 开始时说“任务正在发生,请等待”:因此在 doInBackground()
中发布了一个 block ,然后到达 process()
并设置一个 JDialog。
当 doInBackground 中的冗长任务完成后,我“发布”了另外 2 个命令:一个说“将 JDialog 的消息更改为“等待 GUI 更新””,另一个说“用我的结果填充 JTable正在发送给您”。
关于这一点的一点是,如果您向 JTable 发送大量新数据来替换其 TableModel 的 vector ,Swing 实际上需要花费不可忽略的时间来更新自身......因此我想告诉您用户:“漫长的任务已经完成,但我们现在正在等待 Swing 更新 GUI”。
奇怪的是,如果这两条指令作为 2 个合并 block 到达,我发现 JDialog 只能部分更新: setTitle( "blab") 导致 JDialog 的标题被更改......但所有其他更改JDialog 的更新被搁置...直到 JTable 的主 GUI 更新完成。
如果我设计一些东西,以便在发布 block 之间 doInBackground 有轻微的延迟,则 JDialog 更新正常。显然,对于合并的 block ,我使用循环来逐一遍历它们,因此我考虑在每个循环的末尾放置一个计时器。这没有效果。
我还在 JDialog 上尝试了无数种“验证”、“绘制”和“重新绘制”的排列。
因此,问题是:如何让 GUI 在处理合并 block 的迭代之间的 process() 内更新自身。
NB 我还尝试了其他方法:如果 block 是多个,则重新发布 block 。这样做的问题在于,考虑到事物的异步性质,它可能会导致 block 以错误的顺序发布,就像回到 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() 行,两个 block 会合并在一起,并在 EDT 中依次执行...我尝试过在“ block 处理循环”结束,并尝试 Thread.yield()...本质上,我试图强制 GUI 全面更新 JDialog 及其所有组件...然后再继续更新 JTable...
任何想法表示赞赏。
最佳答案
当您在 JDialog 上设置值时,Swing 正在安排重绘事件。当您的代码运行完构建模型时,这些事件仍在等待 EDT 线程空闲。一旦你的工作完成,线程就会空闲,延迟的事件就会开始执行。
所以,试试这个:
不要直接执行 if ( value == UPDATE_TABLE_IN_GUI )
block 中的代码,而是将其放入方法中。将对其的调用包装在 Runnable
中,并使用 SwingUtilities.invokeLater()
安排其执行。
这将允许 EDT 在构建表之前处理排队的事件
更新
EDT 有一个要执行的 Runnables
队列。对 Swing 组件队列 Runnables
进行更改以供稍后执行。这通常是一件好事。当您设置标签的文本、前景和背景时,您并不想等待它们之间的重新绘制。
EDT 在完成当前的操作之前不会继续执行下一个Runnable
。 process() 方法是从这些 Runnable 之一调用的。因此,让 EDT 运行其他更新的唯一方法是从 process()
返回。 SwingUtilities.invokeLater() 是最简单的方法。
对于 JDialog 标题,一些 LAF 将其委托(delegate)给 native 窗口管理器(X 或 MS Windows)。标题很可能不是由 EDT 绘制的。
关于java - SwingWorker process() GUI 更新困难与合并 block ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7999873/