I'm working with an applet where tables and list selections are changed (as in changing the row that is currently selected) by calling JavaScript functions which, in turn, call public methods off of the applet. To ensure that the UI gets redrawn correctly, I've been running the relevant code through the SwingUtilties.invokeAndWait() method. This way of doing things works in IE and Firefox on Windows and in Safari on Mac OS X, but I'm getting the following exception when I run the applet in Firefox, Camino, and SeaMonkey on the Mac.
java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread
at java.awt.EventQueue.invokeAndWait(EventQueue.java:834)
at javax.swing.SwingUtilities.invokeAndWait(SwingUtilities.java:1257)
at Client.Main.ShowNextDocument(Main.java:291)
at netscape.oji.JNIRunnable.run(Native Method)
at netscape.oji.LiveConnectProxy.run(LiveConnectProxy.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at jep.LiveConnect$DoProxy.run(LiveConnect.java:125)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:461)
at jep.MySimpleEventQueue.dispatchEvent(MySimpleEventQueue.java:59)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:176)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:110)
Since invokeAndWait() blocks the calling thread, this method cannot be called from the event dispatch thread, which is what seems to be happening here. Is this a side effect of how the JEP does things and if so, is there a way to use invokeAndWait()?
As a side note, SwingUtilities.invokeLater() is allowed in the event dispatch thread since it doesn't block the calling thread, but I need to do some further processing based on the newly selected row, so invokeAndWait() seems to be the simplest way to wait for the UI to update.
I can post a more complete example if neccessary. Any help would be appreciated.
/** Initializes the applet TableApplet */
public void init()
{
try
{
java.awt.EventQueue.invokeAndWait(new Runnable()
{
public void run()
{
initComponents();
}
});
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
// Called by JavaScript to select the next row in the table through the event dispatch thread.
// This method should not throw an exception when called from JavaScript.
public void SelectNextRow()
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectNextRow();
}
};
// Called by JavaScript to select the previous row in the table through the event dispatch thread.
// This method should not throw an exception when called from JavaScript.
public void SelectPreviousRow()
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectPreviousRow();
}
};
private void initComponents()
{
_scroll = new javax.swing.JScrollPane();
_table = new javax.swing.JTable();
jPanel1 = new javax.swing.JPanel();
_normalPrevious = new javax.swing.JButton();
_normalNext = new javax.swing.JButton();
_threadedPrevious = new javax.swing.JButton();
_threadedNext = new javax.swing.JButton();
// Next (threaded) button action.
// Attempts to select the next row in the table through the AWT event dispatch thread.
// This method should throw an exception.
private void _threadedNextActionPerformed(java.awt.event.ActionEvent evt)
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectNextRow();
}
};
// Previous (threaded) button action.
// Attempts to select the previous row in the table through the AWT event dispatch thread.
// This method should throw an exception.
private void _threadedPreviousActionPerformed(java.awt.event.ActionEvent evt)
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectPreviousRow();
}
};
// Next (normal) button action.
// Selects the next row in the document without doing anything fancy.
private void _normalNextActionPerformed(java.awt.event.ActionEvent evt)
{
InternalSelectNextRow();
}
// Previous (normal) button action.
// Selects the previous row in the document without doing anything fancy.
private void _normalPreviousActionPerformed(java.awt.event.ActionEvent evt)
{
InternalSelectPreviousRow();
}
}
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
This is the HTML for the web page that's using the above applet. Let me know if you need more information or want this code packaged differently. Thanks for your help.
I can reproduce the problem you report with the top two buttons in
your HTML page ("Previous Row" and "Next Row"), which call the
SelectNextRow() and SelectPreviousRow() functions in your applet.
This happens on Tiger (OS X 10.4.X) and Leopard (OS X 10.5.X), though
not on earlier versions of OS X (10.3.X or 10.2.8). I've tracked this
down to a design flaw in the way the Java Embedding Plugin handles
JavaScript-to-Java LiveConnect on Tiger and Leopard -- the JEP always
runs such calls on an event dispatch thread (preferrably the applet's
event dispatch thread).
This won't be easy to change, and I currently don't have much time to
spend on the Java Embedding Plugin. But I do hope to address this
problem at some point in the next few months.
Interestingly, I can also reproduce the same problem in Firefox on all
platforms (and in Safari) with your applet's "Previous (threaded)" and
"Next (threaded)" buttons. The problem seems to be that (on all JVMs)
"action listeners" always run their commands on an event dispatch
thread. Besides OS X (Tiger and Leopard), I tested on Windows XP and
Kubuntu Linux.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The "Previous (threaded)" and "Next (threaded)" buttons are supposed to throw exceptions on all platforms and browsers since, as you pointed out, button presses are run in the event dispatch thread. I included it to show the similarity between that case and the HTML buttons throwing exceptions in Firefox. I guess I should have made it clearer in the comments that this isn't a bug, just another case to compare and contrast against. Sorry for the confusion.
At any rate, thanks for taking a look at the issue. I understand that your work on this plugin is essentially a free service to us, so any time that you do put into it is always appreciated. Now that I have a better understanding of what's going on, I can start working on a workaround for what I'm trying to do.
Finally, did you want me to write up a bug report for this issue? Otherwise, I'm fine with leaving it in the forums.
Thanks again for your help.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I'm working with an applet where tables and list selections are changed (as in changing the row that is currently selected) by calling JavaScript functions which, in turn, call public methods off of the applet. To ensure that the UI gets redrawn correctly, I've been running the relevant code through the SwingUtilties.invokeAndWait() method. This way of doing things works in IE and Firefox on Windows and in Safari on Mac OS X, but I'm getting the following exception when I run the applet in Firefox, Camino, and SeaMonkey on the Mac.
java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread
at java.awt.EventQueue.invokeAndWait(EventQueue.java:834)
at javax.swing.SwingUtilities.invokeAndWait(SwingUtilities.java:1257)
at Client.Main.ShowNextDocument(Main.java:291)
at netscape.oji.JNIRunnable.run(Native Method)
at netscape.oji.LiveConnectProxy.run(LiveConnectProxy.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at jep.LiveConnect$DoProxy.run(LiveConnect.java:125)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:461)
at jep.MySimpleEventQueue.dispatchEvent(MySimpleEventQueue.java:59)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:176)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:110)
Since invokeAndWait() blocks the calling thread, this method cannot be called from the event dispatch thread, which is what seems to be happening here. Is this a side effect of how the JEP does things and if so, is there a way to use invokeAndWait()?
As a side note, SwingUtilities.invokeLater() is allowed in the event dispatch thread since it doesn't block the calling thread, but I need to do some further processing based on the newly selected row, so invokeAndWait() seems to be the simplest way to wait for the UI to update.
I can post a more complete example if neccessary. Any help would be appreciated.
Javadoc explanation of the invokeAndWait() method:
http://java.sun.com/javase/6/docs/api/javax/swing/SwingUtilities.html#invokeAndWait\(java.lang.Runnable)
Java code snippet:
public void SelectNextRow()
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectNextRow();
}
};
try
{
SwingUtilities.invokeAndWait(runnableCode);
}
catch(Exception ex)
{
System.out.println("Problem selecting next row");
ex.printStackTrace();
}
}
private void InternalSelectNextRow()
{
int row = _table.getSelectedRow();
if(row < _table.getRowCount() - 1)
{
row++;
}
else
{
row = 0;
}
_table.changeSelection(row, 0, false, false);
}
JavaScript code snippet:
function SelectNextRow()
{
testApplet.SelectNextRow();
}
> I can post a more complete example if neccessary.
Please do (please post the source code for a complete test applet).
Moreover (as you say) your code is running via a call from
JavaScript-to-Java LiveConnect. (This is also apparent from your
exception's stack trace.)
Does the exception still happen when the code is called from Java?
// This is the source code for the test applet.
package Main;
import javax.swing.SwingUtilities;
public class TableApplet extends javax.swing.JApplet
{
private javax.swing.JButton _normalNext;
private javax.swing.JButton _normalPrevious;
private javax.swing.JScrollPane _scroll;
private javax.swing.JTable _table;
private javax.swing.JButton _threadedNext;
private javax.swing.JButton _threadedPrevious;
private javax.swing.JPanel jPanel1;
/** Initializes the applet TableApplet */
public void init()
{
try
{
java.awt.EventQueue.invokeAndWait(new Runnable()
{
public void run()
{
initComponents();
}
});
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
// Called by JavaScript to select the next row in the table through the event dispatch thread.
// This method should not throw an exception when called from JavaScript.
public void SelectNextRow()
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectNextRow();
}
};
try
{
SwingUtilities.invokeAndWait(runnableCode);
}
catch(Exception ex)
{
System.out.println("Problem selecting next row");
ex.printStackTrace();
}
}
// Selects the next row in the table.
private void InternalSelectNextRow()
{
int row = _table.getSelectedRow();
if(row < _table.getRowCount() - 1)
{
row++;
}
else
{
row = 0;
}
_table.changeSelection(row, 0, false, false);
}
// Called by JavaScript to select the previous row in the table through the event dispatch thread.
// This method should not throw an exception when called from JavaScript.
public void SelectPreviousRow()
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectPreviousRow();
}
};
try
{
SwingUtilities.invokeAndWait(runnableCode);
}
catch(Exception ex)
{
System.out.println("Problem selecting previous row");
ex.printStackTrace();
}
}
// Selects the previous row in the table.
private void InternalSelectPreviousRow()
{
int row = _table.getSelectedRow();
if(row > 0)
{
row--;
}
else
{
row = _table.getRowCount() - 1;
}
_table.changeSelection(row, 0, false, false);
}
private void initComponents()
{
_scroll = new javax.swing.JScrollPane();
_table = new javax.swing.JTable();
jPanel1 = new javax.swing.JPanel();
_normalPrevious = new javax.swing.JButton();
_normalNext = new javax.swing.JButton();
_threadedPrevious = new javax.swing.JButton();
_threadedNext = new javax.swing.JButton();
_table.setModel(new javax.swing.table.DefaultTableModel(
new Object [][]
{
{"1", "a"},
{"2", "b"},
{"3", "c"},
{"4", "d"},
{"5", "e"},
{"6", "f"},
{"7", "g"},
{"8", "h"},
{"9", "i"},
{"10", "j"},
{"11", "k"},
{"12", "l"},
{"13", "m"},
{"14", "n"},
{"15", "o"},
{"16", "p"},
{"17", "q"},
{"18", "r"},
{"19", "s"},
{"20", "t"},
{"21", "u"},
{"22", "v"},
{"23", "w"},
{"24", "x"},
{"25", "y"},
{"26", "z"}
},
new String []
{
"Title 1", "Title 2"
}
));
_scroll.setViewportView(_table);
getContentPane().add(_scroll, java.awt.BorderLayout.WEST);
_normalPrevious.setText("Previous (normal)");
_normalPrevious.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
_normalPreviousActionPerformed(evt);
}
});
jPanel1.add(_normalPrevious);
_normalNext.setText("Next (normal)");
_normalNext.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
_normalNextActionPerformed(evt);
}
});
jPanel1.add(_normalNext);
_threadedPrevious.setText("Previous (threaded)");
_threadedPrevious.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
_threadedPreviousActionPerformed(evt);
}
});
jPanel1.add(_threadedPrevious);
_threadedNext.setText("Next (threaded)");
_threadedNext.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
_threadedNextActionPerformed(evt);
}
});
jPanel1.add(_threadedNext);
getContentPane().add(jPanel1, java.awt.BorderLayout.NORTH);
}
// Next (threaded) button action.
// Attempts to select the next row in the table through the AWT event dispatch thread.
// This method should throw an exception.
private void _threadedNextActionPerformed(java.awt.event.ActionEvent evt)
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectNextRow();
}
};
try
{
SwingUtilities.invokeAndWait(runnableCode);
}
catch(Exception ex)
{
System.out.println("Problem selecting next row");
ex.printStackTrace();
}
}
// Previous (threaded) button action.
// Attempts to select the previous row in the table through the AWT event dispatch thread.
// This method should throw an exception.
private void _threadedPreviousActionPerformed(java.awt.event.ActionEvent evt)
{
Runnable runnableCode = new Runnable()
{
public void run()
{
InternalSelectPreviousRow();
}
};
try
{
SwingUtilities.invokeAndWait(runnableCode);
}
catch(Exception ex)
{
System.out.println("Problem selecting previous row");
ex.printStackTrace();
}
}
// Next (normal) button action.
// Selects the next row in the document without doing anything fancy.
private void _normalNextActionPerformed(java.awt.event.ActionEvent evt)
{
InternalSelectNextRow();
}
// Previous (normal) button action.
// Selects the previous row in the document without doing anything fancy.
private void _normalPreviousActionPerformed(java.awt.event.ActionEvent evt)
{
InternalSelectPreviousRow();
}
}
This is the HTML for the web page that's using the above applet. Let me know if you need more information or want this code packaged differently. Thanks for your help.
<html>
<head>
<title>Table Test</title>
<script language="javascript" type="text/javascript">
function SelectNextRow()
{
testApplet.SelectNextRow();
}
function SelectPreviousRow()
{
testApplet.SelectPreviousRow();
}
</script>
</head>
<body>
<p>
<input type="button" value="Previous Row" onclick="SelectPreviousRow();" />
</p>
<p>
<input type="button" value="Next Row" onclick="SelectNextRow();" />
</p>
<p>
<applet code="Main.TableApplet.class" name="testApplet" archive="ThrowAway.jar" height="400px" width="600px"></applet>
</p>
</body>
</html>
Thanks for the very thorough testcase.
I can reproduce the problem you report with the top two buttons in
your HTML page ("Previous Row" and "Next Row"), which call the
SelectNextRow() and SelectPreviousRow() functions in your applet.
This happens on Tiger (OS X 10.4.X) and Leopard (OS X 10.5.X), though
not on earlier versions of OS X (10.3.X or 10.2.8). I've tracked this
down to a design flaw in the way the Java Embedding Plugin handles
JavaScript-to-Java LiveConnect on Tiger and Leopard -- the JEP always
runs such calls on an event dispatch thread (preferrably the applet's
event dispatch thread).
This won't be easy to change, and I currently don't have much time to
spend on the Java Embedding Plugin. But I do hope to address this
problem at some point in the next few months.
Interestingly, I can also reproduce the same problem in Firefox on all
platforms (and in Safari) with your applet's "Previous (threaded)" and
"Next (threaded)" buttons. The problem seems to be that (on all JVMs)
"action listeners" always run their commands on an event dispatch
thread. Besides OS X (Tiger and Leopard), I tested on Windows XP and
Kubuntu Linux.
The "Previous (threaded)" and "Next (threaded)" buttons are supposed to throw exceptions on all platforms and browsers since, as you pointed out, button presses are run in the event dispatch thread. I included it to show the similarity between that case and the HTML buttons throwing exceptions in Firefox. I guess I should have made it clearer in the comments that this isn't a bug, just another case to compare and contrast against. Sorry for the confusion.
At any rate, thanks for taking a look at the issue. I understand that your work on this plugin is essentially a free service to us, so any time that you do put into it is always appreciated. Now that I have a better understanding of what's going on, I can start working on a workaround for what I'm trying to do.
Finally, did you want me to write up a bug report for this issue? Otherwise, I'm fine with leaving it in the forums.
Thanks again for your help.