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 方法被调用时,columnNamescolumnTypes 字段被清除。从堆栈跟踪中我得到的印象是 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