Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

An example of using threading with SwingBuilder to do lengthy operations in the background so GUI event handling remains responsive. This sample code demonstrates many of the issues that may occur including replacing the content of a "please wait" message with something else, and safely changing "realized" components only on the EDT (Event Dispatch Thread). This sample happens to use tableLayout, which can get pretty slow but that is a good stresser for the demonstration.

Eventually there will be more explanation of what's going on here, but hopefully this will be helpful.

Code Block
// @author Jim White <mailto:jim@pagesmiths.com>

import groovy.swing.SwingBuilder

import java.awt.BorderLayout
import javax.swing.JFrame
import java.awt.GridLayout
import java.awt.Color

SwingBuilder.build {
    myFrame = frame(title: 'SwingThreading', pack: true, defaultCloseOperation: JFrame.EXIT_ON_CLOSE) {
        borderLayout()
        def lazyPanelsParent
        scrollPane(constraints: BorderLayout.CENTER, preferredSize: [500, 300]) {
            lazyPanelsParent = panel(layout: new GridLayout(0, 1, 5, 5)) {
            }
        }
        panel(constraints: BorderLayout.NORTH) {
            button(text: 'Build One', actionPerformed: {
                println 'Pressed'
                buildInTheBackground(lazyPanelsParent)
            })
        }
    }
    myFrame.show()
}

/**
 * Create a Swing panel in the background using SwingBuilder.
 * This can be called from any thread.
 */
def buildInTheBackground(parentPanel) {
    // If we are not already on the EDT, static SwingBuilder.build(Closure) will do that for us.
    // In the case of an event handler like the actionPerformed for the button, then naturally
    // we're on the EDT already and the building will continue immediately.
    SwingBuilder.build {
        def statusMessage = label(border: lineBorder(color:Color.RED, thickness:4))

        // Notice that the parameter to setStatus() is declared as String.
        // That way if code uses a GString it gets rendered at the time (and on the
        // thread) of the call.  If we don't do that then it might get deferred,
        // by which time the results that the GString will produce may change and
        // could be in a race (results varying with execution order and maybe invalid).
        // Also the context here is that the caller is the work environment and that
        // operations on the EDT are to be quick and determinate for display only.

        def setStatus = { String msg -> println msg; statusMessage.setText(msg) }

        // We can change the shown Swing tree here because we're on the EDT.

        setStatus('Building...')

        // Put the message at the top of the list if we use index = 0.
        // Use plain parentPanel.add(statusMessage) if you want the end of the list.
        parentPanel.add(statusMessage, 0)

        // Component.validate() must be called after any changes that affect layout
        // on realized (shown) AWT/Swing trees.
        parentPanel.validate()

        doOutside {
            // Now we're off on our own thread.

            // To change the display, be sure to run that code on the EDT.
            edt { setStatus('Working outside') }

            // That will wait, which isn't really necessary here.
            // For more parallelism, use doLater.
            // doLater { setStatus('Working outside') }

            // Here we do our lengthy work.
            sleep(3000)
            def powerData = [* 0..5].collect {power -> [* 0..10].collect { Math.pow(it, power) } }

            // SwingBuilding may take a while too.
            def newPanel = panel(minimumSize: [250, 250], border: lineBorder(color:Color.BLUE, thickness:2)) {
                edt { setStatus("Building new panel") }

                sleep(3000)
                tableLayout {
                    powerData.each {row -> tr { row.each {cell -> td { textField(cell.toString()) } } } }
                }
            }

            edt { setStatus("New panel ready") }

            // Back to the EDT for fiddling with the Swing tree.

            doLater {
                setStatus("Adding new panel")

                // We're gonna remove the widget used for the new panel's progress indication.
                // An alternate (and simpler) way to get this effect is to put the status
                // in a panel that doesn't get removed.  But this method is shown because
                // it is fairly general in that the status indicator can have a utility
                // layout that gets removed when done and so doesn't impact the customized
                // structure desired for the application.

                // We may not be the zeroth child (beginning of list) anymore.
                // (Or end of list either, if that's where we started.)
                def idx = (parentPanel.components as List).indexOf(statusMessage)
                parentPanel.remove(idx)
                parentPanel.add(newPanel, idx)
                parentPanel.validate()

// Cute groovyism, but we don't need the extra overhead.
//                parentPanel.with {
//                    //  We may not be the zeroth child anymore.
//                    def idx = (components as List).indexOf(statusMessage)
//                    remove(idx)
//                    add(newPanel, idx)
//                    validate()
//                }

//            myFrame.pack()

                setStatus("Built")
            }
        }
    }
}

SwingThreading.groovy