抱歉,有点长,但有点涉及......
SwingWorker在我的应用程序中完全按预期工作,除了一个棘手的问题,我正在努力解决,如果块进入进程()合并,因为API明确指出是完全可能和正常的。
问题出现了,例如,当我有一个JDialog开始时说“任务正在发生,请稍候”:所以一个块发布在doInBackground()
,然后到达process()
并设置一个JDialog。
当doInBackground中的冗长任务完成时,我“再发布”2个命令:一个说“将JDialog的消息更改为”等待GUI更新“”,另一个说“用结果填充JTable” “送你”。
关于这一点的一点是,如果你发送一个JTable大量的新数据来替换它的TableModel的向量,Swing实际上可以花费不可忽视的时间来更新自己...因此我想告诉用户:“冗长的任务已经完成,但我们现在正在等待Swing更新GUI”。
奇怪的是,如果这两个指令以2个合并的块的形式到达,我发现JDialog只能部分更新:setTitle(“blab”)导致JDialog的标题被更改......但所有其他更改JDialog被搁置......直到JTable的主GUI更新完成。
如果我设计的东西使得在发布块之间doInBackground稍有延迟,那么JDialog会更新。显然,对于合并的块,我使用循环逐个遍历它们,所以我想在每个循环结束时放置一个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中一个接一个地执行...我已经尝试过像在运行一个短的定时器结束“块处理循环”,并尝试使用Thread.yield()......本质上我试图强制GUI全面更新JDialog及其所有组件......继续更新JTable ......
任何想法都赞赏。
答案 0 :(得分:2)
在JDialog上设置值时,Swing正在安排重绘事件。当代码运行构建模型时,这些事件仍在等待EDT线程空闲。完成工作后,线程处于空闲状态,延迟事件就会发生。
所以,试试这个:
不是直接执行if ( value == UPDATE_TABLE_IN_GUI )
块中的代码,而是将其放在方法中。在Runnable
中对该调用进行换行,并使用SwingUtilities.invokeLater()
来安排执行。
这将允许EDT在构建表之前处理排队事件
<强>更新强>
EDT的队列为Runnables
。对Swing组件队列Runnables
的更改以供稍后执行。这通常是件好事。设置标签的文本,前景和背景时,您实际上并不想等待每个标签之间的重新绘制。
EDT不会继续下一个Runnable
,直到它完成当前的process()
。 process()方法是从其中一个Runnables调用的。因此,让EDT运行其他更新的唯一方法是从{{1}}返回。 SwingUtilities.invokeLater()是最简单的方法。
对于JDialog标题,一些LAF将其委托给本机窗口管理器(X或MS Windows)。很可能该标题不是由美国东部时间绘制的。
答案 1 :(得分:2)
破解它! - paintImmediately()可以做到这一点:
m_pane.setOptions(null);
m_pane.setMessage("Populating...");
m_jDialog.setTitle("Table being populated...");
Rectangle rect = m_jDialog.getContentPane().getBounds();
((JComponent)m_jDialog.getContentPane()).paintImmediately( rect );
的后强> 的
对于任何磕磕绊绊的人并担心下面的语无伦次的评论,我认为可以安全地忽略这个评论是公平的:首先,我认为没有证据表明paintImmediately设计为在EDT之外执行,其次是死锁在并发意义上,只发生在两个线程之间共享的可变对象:因此,在EDT中这些块的循环迭代中,这在我看来是错误的。对上述代码的另一项更改
awt.Dialog.show()的Java API:“允许从事件调度线程显示模态对话框,因为工具包将确保在调用此方法的事件被阻止时运行另一个事件泵”。这意味着如果DISPLAY_WAIT_FOR_TASK是传递给process()的最后一个块,我们就可以了:另一个事件泵在m_jDialog.setVisible(true)之后运行,并且这个新的事件泵处理对process()的下一个调用。
相反,如果一个块与DISPLAY_WAIT_FOR_TASK合并(即如果另一个在同一个process()调用中跟随它),则代码将在setVisible(true)处阻塞,并且循环将继续处理下一个块只有当JOptionPane被用户“处理”或以编程方式“处理”时。
为了防止这种情况,并在此setVisible()命令之后立即继续运行,必须让单个命令m_jDialog.setVisible(true)在其自己的(非EDT)线程中运行(NB JOptionPane是设计为在EDT或非EDT中运行。
显然,JOptionPane的这个特殊线程可以在现场创建,也可以从可用的线程池,ExecutorService等中获取。
答案 2 :(得分:1)
如果你的意思是我一次设置
TableModel
的整个向量,确实是。
这可能是问题的核心。 JTable
在renderers中使用flyweight pattern。通过限制对可见行的更新,最小化在process()
内递增更新模型的成本; publish()
通常是限速步骤,simple examples通常会使用sleep()
来模拟延迟。
从TableModel
派生的DefaultTableModel
很方便,但它在内部使用(同步)java.util.Vector
。 AbstractTableModel
是一种允许在所选数据结构中拥有更多自由度的替代方案。