Note that there are some explanatory texts on larger screens.

plurals
  1. POSwingWorker process() GUI update difficulty with coalesced chunks
    primarykey
    data
    text
    <p><strong>Sorry, bit long, but it is a bit involved...</strong></p> <p>SwingWorker works entirely as expected in my app, except for one knotty problem which I'm struggling to solve, in the event that chunks arrive in process() coalesced, as the API clearly states is perfectly possible and normal.</p> <p>The problem comes, for example, when I have a JDialog which starts by saying "task happening, please wait": so a chunk is published in <code>doInBackground()</code> which then arrives in <code>process()</code> and sets up a JDialog.</p> <p>When the lengthy task in doInBackground has finished I "publish" 2 more commands: one says "change the message of the JDialog to "waiting for GUI to update"", and the other says "populate the JTable with the results I'm sending you".</p> <p>The point about this is that, if you are sending a JTable a large amount of new data to replace its TableModel's vector, Swing can actually take a non-negligible time to udpate itself... for that reason I want to tell the user: "the lengthy task has finished, but we're now waiting for Swing to update the GUI".</p> <p>What is strange is that if these two instructions arrive as 2 coalesced chunks I find that the JDialog is only capable of being partially updated: setTitle( "blab" ) results in the JDialog's title being changed... but all other changes to the JDialog are put on hold ... until the main GUI update of the JTable has finished.</p> <p>If I engineer things so that there is a slight delay in doInBackground between publishing the chunks the JDialog updates OK. Obviously, with coalesced chunks I am using a loop to go through them one by one, so I thought of putting a Timer at the end of each loop. This had no effect.</p> <p>I've also tried innumerable permutations of "validate" and "paint" and "repaint" on the JDialog.</p> <p>The question therefore is: how get I get the GUI to update itself within process() between iterations dealing with coalesced chunks.</p> <p>NB I also tried something else: republishing chunks if they are multiple. The trouble with this is that, given the asynchronous nature of things, it can result in chunks being published in the wrong order, as back in doInBackground, inevitably, things are continuing to be published. Plus, this kind of solution is just inelegant.</p> <p><strong>later... as requested, here is an SSCCE:</strong></p> <pre><code>import javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.util.*; class Coalescence extends SwingWorker&lt;Object, Object&gt; { 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 &lt; 20000; i++ ){ Object[] row = new Object[ 20 ]; for( int j = 0; j &lt; 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&lt;Object&gt; chunks){ p( "no chunks " + chunks.size() ); // "CHUNK PROCESSING LOOP" for( int i = 0, n_chunks = chunks.size(); i &lt; 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 &lt; 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 &lt; 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 &gt; 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 ); } } </code></pre> <p>... the program consists of 5 stages: 1) set up the GUI 2) put up a message saying "wait for the task to complete" 3) the "lengthy" non-EDT task 4) a change to the message so that it now says "wait for GUI to update the table" 5) updating of the table in GUI (followed by disposal of the JDialog/JOptionPane).</p> <p>The thing I don't understand is why, if you comment out the Thread.sleep() line in doInBackground above, the JDialog behaves oddly: the title is then updated, but the text of the JOptionPane does not change, and the "Cancel" button is not removed.</p> <p>It can be seen that the difference is that without the Thread.sleep() line, the two chunks arrive coalesced, and are performed one after another in the EDT... I have tried things like running a short Timer at the end of the "chunk processing loop", and experimenting with Thread.yield()... essentially I am trying to force the GUI to update the JDialog and all its components comprehensively ... BEFORE moving on to update the JTable...</p> <p>Any thoughts appreciated.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload