« Iterating multiple soap responses with Yield Return | Main | Movable Type 3 Upgrade »

Groovy, Verbosity, and Swing

While I was on vacation in Hawaii Jonathan Simon lamented about how people still aren't getting it.. I think that the single biggest problem creating the misconception that swing is slow is that people don't understand or use threading on their GUIs. Two main problems exist. First any event you handle is inside the event dispatch thread which is also responsible for painting the GUI. Second, practically any manipulation of a JComponent must be done from within the event dispatch thread. Practically a paradox, but it can be dealt with.

Consider an action event that updates a label named jLabel with a string value from a long running method called takesTenSeconds. You may be tempted to do something like

public void actionPerformed(ActionEvent e) {
    jLabel.setText(takesTenSeconds());
}

but then you violate the first rule: no long running calls in the event thread. With anonymous methods it can be handled however...

public void actionPerformed(ActionEvent e) {
    (new Thread(new Runnable() {
        public void run() {
            jLabel.setText(takesTenSeconds());
        }
    }).start();
}

That's some progress. But we now violate the second rule: Don't use thread unsafe methods on JComponents or mess with models they use. In this case it will not be a problem, but when you start with JTrees, JTables, JLists, JComboBoxes, and the like you can create these lovely stack traces to system.out involving concurrent modification, null pointer, index out of bounds, and other stuff that looks bad to QA and clients. How do we fix this?

public void actionPerformed(ActionEvent e) {
    (new Thread(new Runnable() {
        public void run() {
            final String newText = takesTenSeconds();
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    jLabel.setText(newText);
                }
            });
        }
    }).start();
}

A code snippet that a random VB developer would assume is one line morphs to 3 times it size in characters and (depending on your codeing style) 10 lines in the code. Even with another utility method for the background call we have to make two runnable classes, whether anonymous, inner, or stand alone that's a lot of code bloat. What would be nice is a more concise syntax for a runnnable. Enter Groovy and closures. This can be made much smaller with the creation of a few simple utility closures...

invokeAndWait = { SwingUtilities.isEventDispatchThread() ? it() : SwingUtilitites.invokeAndWait(it); }
invokeLater = { SwingUtilities.invokeLater(it) }
invokeOutside = { SwingUtilities.isEventDispatchThread() ? Thread.start(it) : it() }

From this, the proper way becomes much shorter...

invokeOutside {
    text = takesTenSecondes()
    invokeLater { jLabel.text = text}
}

With some tweaking on the sample closures it could probably even be reduced to...

invokeLater { jLabel.text = invokeOutside { takesTenSeconds() } }

I think that the greatest problem with the swing APIs are not that they are too complex or too poorly explained. The biggest problem I see is that to do simple things the correct way, it can get quite verbose. Kind of like reliable file I/O, but you get far fewer urgent-1 bug in the GUI than in core I/O routines. Perhaps the best thing isn't to change the Swing APIs, but to add more syntactic sugar.

Comments (2)

Kevin:

You hit the nail right on the head with this! Good stuff. I am not using Groovy, but what Java needs is some way to wrap up all that extra work into a single inlined method, so to speak. That is, take any bit of work, and apply it as a new thread, then updtae the result on the event thread. This can be done I guess, but it should be built in to the language. As you said, the things that should normally be easy require a little tedium to make work right.

I would also like to add that a lot of developers don't fully understand the whole issue of when to provide a thick amount of code or not. For example, in a JTable, the getValueAt() needs to be as slim as possible. The setValueAt() is where you want to do any work. There is a little bit of a "bug" (so I think) in the API code of I think its DefaultTableModel...I forget. Basically, if you select one cell, then select another cell a long ways away, every cell in between gets updated and I assume this is done for the "just in case" factor. If you are in single cell selection mode, you should only be repainting the cell you just left and the cell you are selecting. NOT all the cells in between. Because of this, depending on the size of the table rows/columns, you can get hundreds if not thousands of getValueAt() calls just by moving your mouse around or scrolling the table.

Some people disagree with my thought on this, but for me, storing the entire table data as one type as much as possible so that the getValueAt() can be super slim makes the most sense. When you first set the data into the table, and every call to setValueAt() should do whatever work is necessary to convert the data, no matter what type (other than oddball types like images or video clips), should try to be converted to the one type that the getValueAt() can return. In some cases, you may have to add a row or column cell renderer to format the layout, but no matter what, you need a way to make getValueAt() super fast!

My point is, tables, and trees as well, can often be the culprit behind slow swing apps. Need to learn how to program them if you want a smooth app. There are plenty of IDE's and other apps out there written in Java that are smooth consistently. Eclipse is pretty good at least on Windows, and IDEA is pretty smooth on all platforms..so it can be done.

Bart:

I wrote a few jse.sf.net macros for that to use it with plain java

Post a comment


About

This page contains a single entry from the blog posted on May 17, 2004 12:05 PM.

The previous post in this blog was Iterating multiple soap responses with Yield Return.

The next post in this blog is Movable Type 3 Upgrade.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.33