SwingWorker + JTable:如何更改代码才不会出错?
SwingWorker + JTable: how to change the code so that there was no error?
我使用此代码并遇到错误 java.lang.ArrayIndexOutOfBoundsException。我知道问题是 多线程和 EDT。我阅读了有关使用方法 publish() 的信息,但我不知道如何使用它,也不知道如何更改代码以更正错误。请告诉我如何更改代码以避免任何错误。感谢你!
错误信息:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 1 >= 1
at java.util.Vector.elementAt(Unknown Source)
at javax.swing.table.DefaultTableColumnModel.getColumn(Unknown Source)
at javax.swing.plaf.basic.BasicTableHeaderUI.getHeaderHeight(Unknown Source)
at javax.swing.plaf.basic.BasicTableHeaderUI.createHeaderSize(Unknown Source)
at javax.swing.plaf.basic.BasicTableHeaderUI.getPreferredSize(Unknown Source)
at javax.swing.JComponent.getPreferredSize(Unknown Source)
at javax.swing.ViewportLayout.preferredLayoutSize(Unknown Source)
at java.awt.Container.preferredSize(Unknown Source)
at java.awt.Container.getPreferredSize(Unknown Source)
at javax.swing.JComponent.getPreferredSize(Unknown Source)
at javax.swing.ScrollPaneLayout.layoutContainer(Unknown Source)
at java.awt.Container.layout(Unknown Source)
at java.awt.Container.doLayout(Unknown Source)
at java.awt.Container.validateTree(Unknown Source)
at java.awt.Container.validate(Unknown Source)
at javax.swing.RepaintManager.run(Unknown Source)
at javax.swing.RepaintManager.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain.doIntersectionPrivilege(Unknown Source)
at javax.swing.RepaintManager.validateInvalidComponents(Unknown Source)
at javax.swing.RepaintManager$ProcessingRunnable.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access0(Unknown Source)
at java.awt.EventQueue.run(Unknown Source)
at java.awt.EventQueue.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
主要Class:
import java.sql.*;
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class DatabaseTable extends JFrame {
private String
dsn = "jdbc:mysql://localhost:3306/TestDB",
uid = "root",
pwd = "root";
private static int start = 0, count = 10;
private JProgressBar progressBar = null;
private JButton btnNewButton = null;
private DatabaseTableModel dbm = null;
private Statement st = null;
Connection connections() throws ClassNotFoundException, SQLException{
Connection conn = null;
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(dsn, uid, pwd);
return conn;
}
public DatabaseTable(){
dbm = new DatabaseTableModel(false);
JTable table = new JTable(dbm);
try {
JFrame frame = new JFrame("Next record");
frame.setSize(400, 300);
frame.getContentPane().add(new JScrollPane(table));
btnNewButton = new JButton("Next!");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
Task task = new Task();
task.execute();
}
});
frame.getContentPane().add(btnNewButton, BorderLayout.NORTH);
progressBar = new JProgressBar();
frame.getContentPane().add(progressBar, BorderLayout.SOUTH);
frame.show();
} catch (Exception ex) {
System.out.println("DatabaseTable().Exception");
}
}
class Task extends SwingWorker<Void, Void> {
@Override
public Void doInBackground() throws Exception {
btnNewButton.setEnabled(false);
progressBar.setIndeterminate(true);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
st = connections().createStatement();
ResultSet rs = st.executeQuery("select * from testTable LIMIT "+start+","+count);
start += 10;
dbm.setDataSource(rs);
rs.close();
connections().close();
}catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
System.out.println("Task.ClassNotFoundException");
}catch ( SQLException e) {
System.out.println("Task.SQLException");
}catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("Task.Exception");
}
return null;
}
@Override
public void done(){
progressBar.setIndeterminate(false);
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
progressBar.setValue(100);
btnNewButton.setEnabled(true);
}
}
public static void main(String[] args) {
new DatabaseTable();
}
}
Class 表模型:
import javax.swing.*;
import javax.swing.table.*;
import java.sql.*;
import java.util.*;
public class DatabaseTableModel extends AbstractTableModel {
private ArrayList columnNames = new ArrayList();
private ArrayList columnTypes = new ArrayList();
private ArrayList data = new ArrayList();
public DatabaseTableModel(boolean editable) {
this.editable = editable;
}
private boolean editable;
public int getRowCount() {
synchronized (data) {
return data.size();
}
}
public int getColumnCount() {
return columnNames.size();
}
public Class getColumnClass(int column) {
return (Class)columnTypes.get(column);
}
public String getColumnName(int column) {
return (String)columnNames.get(column);
}
public Object getValueAt(int row, int column) {
synchronized (data) {
return ((ArrayList)data.get(row)).get(column);
}
}
public boolean isEditable(int row, int column) {
return editable;
}
public void setValueAt(Object value, int row, int column){
synchronized (data) {
((ArrayList)data.get(row)).set(column, value);
}
}
public void setDataSource(ResultSet rs) throws Exception {
data.clear();
columnNames.clear();
columnTypes.clear();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for ( int i=0; i<columnCount; i++) {
columnNames.add(rsmd.getColumnName(i+1));
Class type = Class.forName(rsmd.getColumnClassName(i+1));
columnTypes.add(type);
}
fireTableStructureChanged();
while ( rs.next() ) {
ArrayList row = new ArrayList();
for ( int i=0; i<columnCount; i++) {
if (columnTypes.get(i) == String.class)
row.add(rs.getString(i+1));
else
row.add(rs.getObject(i+1));
}
synchronized (data) {
data.add(row);
fireTableRowsInserted(data.size()-1, data.size()-1);
}
}
}
}
TableModel 中不需要 "synchronized" 逻辑。将 TableModel 添加到 JTable 后,应在事件调度线程上对其进行更新。
所以我建议不要使用:
dbm = new DatabaseTableModel(false);
JTable table = new JTable(dbm);
您在 SwingWorker 中创建了 TableModel。然后在SwingWorker的"done()"方法中可以使用:
table.setModel( )
"done()" 方法中的代码在事件调度线程上执行,因此您可以安全地重置 table 的模型。
编辑:
您使用如下代码创建了一个新的 TableModel:
DatabaseTableModel dbm = new DatabaseTableModel(false);
dbm.setDataSource(rs);
然后将 TableModel 传递给 "done()" 方法,以便您可以使用新的 TableModel 重置 table。
阅读 Swing 教程中关于 Simple Background Tasks 的部分,了解使用 "return" 语句将数据从 "doInBackground()" 方法传递到 "done()" 方法的示例,以及"get()" 方法。
看起来有两个线程正在同时访问数据库 table 模型。每次 DatabaseTableModel.setDataSource
方法被调用时,columnNames
和 columnTypes
字段被清除。从堆栈跟踪中我得到的印象是
EDT 正在布置 table header 并尝试在数据库 table 模型被另一个模型清除时获取列数据
线程。
在 SwingWorker
文档 (http://docs.oracle.com/javase/8/docs/api/javax/swing/SwingWorker.html) 中,一个工人
线程描述了应用程序可以在不执行 GUI-related 活动的情况下执行 time-consuming 工作的位置。在你的
case,doInBackground()
方法可以查询数据库,得到正确格式的数据。 done()
方法可以
然后更新数据库 table 模型并将更改通知所有侦听器(JTable
object)。
您当前的 doInBackground()
方法确实 GUI-related 有效。您可以通过存储数据(和列信息)来更改此设置
在单独的字段中(在 Task
class 中)。前三行可以移动到 btnNewButton 动作侦听器,之前
调用 task.execute()
.
在 doInBackground()
方法中获取 data/columns 可能看起来有点像这个虚拟示例:
// Dummy data to test without a database.
if (columnNames.size() == 0) {
columnNames = new ArrayList<>(Arrays.asList("A", "B", "C"));
columnTypes = new ArrayList<>(Arrays.asList(String.class, String.class, String.class));
}
data = new ArrayList();
data.add(new ArrayList<>(Arrays.asList(data.size(), data.size() + 1, data.size() + 2)));
此代码可以添加到 done()
方法的开头,以将 data/columns 传输到数据库 table 模型
并通知听众:
dbm.updateModel(columnNames, columnTypes, data);
System.out.println("Task.done - dbm.getRowCount() = " + dbm.getRowCount());
if (dbm.getRowCount() == 1)
dbm.fireTableStructureChanged();
dbm.fireTableRowsInserted(dbm.getRowCount() - 1, dbm.getRowCount() - 1);
更新数据库 table 模型的方法可能如下所示:
public void updateModel(ArrayList columnNames, ArrayList columnTypes, ArrayList data) {
if (columnNames != null && columnTypes != null) {
this.columnNames = columnNames;
this.columnTypes = columnTypes;
}
this.data = data;
//this.data.addAll(data);
}
这个 Stack Overflow 问题和答案处理的是一个类似的问题:
Java Swing: Jtable ArrayIndexOutOfBoundsException
我使用此代码并遇到错误 java.lang.ArrayIndexOutOfBoundsException。我知道问题是 多线程和 EDT。我阅读了有关使用方法 publish() 的信息,但我不知道如何使用它,也不知道如何更改代码以更正错误。请告诉我如何更改代码以避免任何错误。感谢你! 错误信息:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 1 >= 1
at java.util.Vector.elementAt(Unknown Source)
at javax.swing.table.DefaultTableColumnModel.getColumn(Unknown Source)
at javax.swing.plaf.basic.BasicTableHeaderUI.getHeaderHeight(Unknown Source)
at javax.swing.plaf.basic.BasicTableHeaderUI.createHeaderSize(Unknown Source)
at javax.swing.plaf.basic.BasicTableHeaderUI.getPreferredSize(Unknown Source)
at javax.swing.JComponent.getPreferredSize(Unknown Source)
at javax.swing.ViewportLayout.preferredLayoutSize(Unknown Source)
at java.awt.Container.preferredSize(Unknown Source)
at java.awt.Container.getPreferredSize(Unknown Source)
at javax.swing.JComponent.getPreferredSize(Unknown Source)
at javax.swing.ScrollPaneLayout.layoutContainer(Unknown Source)
at java.awt.Container.layout(Unknown Source)
at java.awt.Container.doLayout(Unknown Source)
at java.awt.Container.validateTree(Unknown Source)
at java.awt.Container.validate(Unknown Source)
at javax.swing.RepaintManager.run(Unknown Source)
at javax.swing.RepaintManager.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain.doIntersectionPrivilege(Unknown Source)
at javax.swing.RepaintManager.validateInvalidComponents(Unknown Source)
at javax.swing.RepaintManager$ProcessingRunnable.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access0(Unknown Source)
at java.awt.EventQueue.run(Unknown Source)
at java.awt.EventQueue.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
主要Class:
import java.sql.*;
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class DatabaseTable extends JFrame {
private String
dsn = "jdbc:mysql://localhost:3306/TestDB",
uid = "root",
pwd = "root";
private static int start = 0, count = 10;
private JProgressBar progressBar = null;
private JButton btnNewButton = null;
private DatabaseTableModel dbm = null;
private Statement st = null;
Connection connections() throws ClassNotFoundException, SQLException{
Connection conn = null;
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(dsn, uid, pwd);
return conn;
}
public DatabaseTable(){
dbm = new DatabaseTableModel(false);
JTable table = new JTable(dbm);
try {
JFrame frame = new JFrame("Next record");
frame.setSize(400, 300);
frame.getContentPane().add(new JScrollPane(table));
btnNewButton = new JButton("Next!");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
Task task = new Task();
task.execute();
}
});
frame.getContentPane().add(btnNewButton, BorderLayout.NORTH);
progressBar = new JProgressBar();
frame.getContentPane().add(progressBar, BorderLayout.SOUTH);
frame.show();
} catch (Exception ex) {
System.out.println("DatabaseTable().Exception");
}
}
class Task extends SwingWorker<Void, Void> {
@Override
public Void doInBackground() throws Exception {
btnNewButton.setEnabled(false);
progressBar.setIndeterminate(true);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
st = connections().createStatement();
ResultSet rs = st.executeQuery("select * from testTable LIMIT "+start+","+count);
start += 10;
dbm.setDataSource(rs);
rs.close();
connections().close();
}catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
System.out.println("Task.ClassNotFoundException");
}catch ( SQLException e) {
System.out.println("Task.SQLException");
}catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("Task.Exception");
}
return null;
}
@Override
public void done(){
progressBar.setIndeterminate(false);
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
progressBar.setValue(100);
btnNewButton.setEnabled(true);
}
}
public static void main(String[] args) {
new DatabaseTable();
}
}
Class 表模型:
import javax.swing.*;
import javax.swing.table.*;
import java.sql.*;
import java.util.*;
public class DatabaseTableModel extends AbstractTableModel {
private ArrayList columnNames = new ArrayList();
private ArrayList columnTypes = new ArrayList();
private ArrayList data = new ArrayList();
public DatabaseTableModel(boolean editable) {
this.editable = editable;
}
private boolean editable;
public int getRowCount() {
synchronized (data) {
return data.size();
}
}
public int getColumnCount() {
return columnNames.size();
}
public Class getColumnClass(int column) {
return (Class)columnTypes.get(column);
}
public String getColumnName(int column) {
return (String)columnNames.get(column);
}
public Object getValueAt(int row, int column) {
synchronized (data) {
return ((ArrayList)data.get(row)).get(column);
}
}
public boolean isEditable(int row, int column) {
return editable;
}
public void setValueAt(Object value, int row, int column){
synchronized (data) {
((ArrayList)data.get(row)).set(column, value);
}
}
public void setDataSource(ResultSet rs) throws Exception {
data.clear();
columnNames.clear();
columnTypes.clear();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for ( int i=0; i<columnCount; i++) {
columnNames.add(rsmd.getColumnName(i+1));
Class type = Class.forName(rsmd.getColumnClassName(i+1));
columnTypes.add(type);
}
fireTableStructureChanged();
while ( rs.next() ) {
ArrayList row = new ArrayList();
for ( int i=0; i<columnCount; i++) {
if (columnTypes.get(i) == String.class)
row.add(rs.getString(i+1));
else
row.add(rs.getObject(i+1));
}
synchronized (data) {
data.add(row);
fireTableRowsInserted(data.size()-1, data.size()-1);
}
}
}
}
TableModel 中不需要 "synchronized" 逻辑。将 TableModel 添加到 JTable 后,应在事件调度线程上对其进行更新。
所以我建议不要使用:
dbm = new DatabaseTableModel(false);
JTable table = new JTable(dbm);
您在 SwingWorker 中创建了 TableModel。然后在SwingWorker的"done()"方法中可以使用:
table.setModel( )
"done()" 方法中的代码在事件调度线程上执行,因此您可以安全地重置 table 的模型。
编辑:
您使用如下代码创建了一个新的 TableModel:
DatabaseTableModel dbm = new DatabaseTableModel(false);
dbm.setDataSource(rs);
然后将 TableModel 传递给 "done()" 方法,以便您可以使用新的 TableModel 重置 table。
阅读 Swing 教程中关于 Simple Background Tasks 的部分,了解使用 "return" 语句将数据从 "doInBackground()" 方法传递到 "done()" 方法的示例,以及"get()" 方法。
看起来有两个线程正在同时访问数据库 table 模型。每次 DatabaseTableModel.setDataSource
方法被调用时,columnNames
和 columnTypes
字段被清除。从堆栈跟踪中我得到的印象是
EDT 正在布置 table header 并尝试在数据库 table 模型被另一个模型清除时获取列数据
线程。
在 SwingWorker
文档 (http://docs.oracle.com/javase/8/docs/api/javax/swing/SwingWorker.html) 中,一个工人
线程描述了应用程序可以在不执行 GUI-related 活动的情况下执行 time-consuming 工作的位置。在你的
case,doInBackground()
方法可以查询数据库,得到正确格式的数据。 done()
方法可以
然后更新数据库 table 模型并将更改通知所有侦听器(JTable
object)。
您当前的 doInBackground()
方法确实 GUI-related 有效。您可以通过存储数据(和列信息)来更改此设置
在单独的字段中(在 Task
class 中)。前三行可以移动到 btnNewButton 动作侦听器,之前
调用 task.execute()
.
在 doInBackground()
方法中获取 data/columns 可能看起来有点像这个虚拟示例:
// Dummy data to test without a database.
if (columnNames.size() == 0) {
columnNames = new ArrayList<>(Arrays.asList("A", "B", "C"));
columnTypes = new ArrayList<>(Arrays.asList(String.class, String.class, String.class));
}
data = new ArrayList();
data.add(new ArrayList<>(Arrays.asList(data.size(), data.size() + 1, data.size() + 2)));
此代码可以添加到 done()
方法的开头,以将 data/columns 传输到数据库 table 模型
并通知听众:
dbm.updateModel(columnNames, columnTypes, data);
System.out.println("Task.done - dbm.getRowCount() = " + dbm.getRowCount());
if (dbm.getRowCount() == 1)
dbm.fireTableStructureChanged();
dbm.fireTableRowsInserted(dbm.getRowCount() - 1, dbm.getRowCount() - 1);
更新数据库 table 模型的方法可能如下所示:
public void updateModel(ArrayList columnNames, ArrayList columnTypes, ArrayList data) {
if (columnNames != null && columnTypes != null) {
this.columnNames = columnNames;
this.columnTypes = columnTypes;
}
this.data = data;
//this.data.addAll(data);
}
这个 Stack Overflow 问题和答案处理的是一个类似的问题: Java Swing: Jtable ArrayIndexOutOfBoundsException