Can't Add TableRowSorter to JTable Produced By

2019-07-20 10:07发布

问题:

Thank You Hovercraft Full Of Eels for making note of the fact that my question was full of a jumbled mess of code that was unlikely to be solved. Since then, I have created a "minimal" Test Program to display the issue:

The Issue

What I am looking to do is have a GUI that displays a table containing employee information, and also allows a user to do a live search of said table by typing into a jtextfield at the top of the gui.

So I currently have a java class that creates a table and fills that table with employee information. This information is placed into the table via an SQL statement that is processed by a SwingWorker. The GUI works perfectly fine, until I try and add a TableRowSorter to the table that contains the info, this produces an error:

Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Invalid range
    at javax.swing.DefaultRowSorter.checkAgainstModel(Unknown Source)
    at javax.swing.DefaultRowSorter.rowsInserted(Unknown Source)
    at javax.swing.JTable.notifySorter(Unknown Source)
    at javax.swing.JTable.sortedTableChanged(Unknown Source)
    at javax.swing.JTable.tableChanged(Unknown Source)
    at javax.swing.table.AbstractTableModel.fireTableChanged(Unknown Source)
    at javax.swing.table.AbstractTableModel.fireTableRowsInserted(Unknown Source)
    at tesPackage.WorkerTest$JDBCModel$JDBCWorker.process(WorkerTest.java:276)

Based on this example, here's a

Working Example

package tesPackage;

import java.awt.BorderLayout;
import java.util.concurrent.ThreadLocalRandom;
import java.awt.EventQueue;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

/**
* @see https://stackoverflow.com/a/34742409/230513
* @see https://stackoverflow.com/a/24762078/230513
*/
public class WorkerTest {

private static final int N = 25;
private static final String URL = "jdbc:h2:mem:test";
private static final Random r = new Random();

private JPanel contentPane;
private static JTable tblSchedule;
private JScrollPane scrollSchedTable;
private JDBCModel model;
private JPanel panel;
private JTextField textField;

private static TableRowSorter<TableModel> rowSorter;

private void display() {

    createTestDatabase(N);  //Create the Test Database

    //Create the GUI
    JFrame f = new JFrame("WorkerTest");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setBounds(100, 100, 780, 450);
    contentPane = new JPanel();
    contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
    f.setContentPane(contentPane);
    contentPane.setLayout(new BorderLayout(0, 0));
    {
        scrollSchedTable = new JScrollPane();
        scrollSchedTable.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        contentPane.add(scrollSchedTable);
        {
            tblSchedule = new JTable();
            tblSchedule.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            tblSchedule.setShowHorizontalLines(true);
            tblSchedule.setShowVerticalLines(true);
            tblSchedule.setFillsViewportHeight(true);
            model = new JDBCModel(getConnection(), "select * from city");
            tblSchedule.setModel(model);
            rowSorter = new TableRowSorter<TableModel>(model);  //IF UNCOMMENT THESE 2 LINES GUI WILL RUN
            tblSchedule.setRowSorter(rowSorter);                //BUT IF TYPE IN SEARCH BOX THERE IS AN ERROR
            scrollSchedTable.setViewportView(tblSchedule);
        }
        {
            panel = new JPanel();
            contentPane.add(panel, BorderLayout.NORTH);
            panel.setLayout(new BorderLayout(0, 0));
            {
                textField = new JTextField();
                panel.add(textField, BorderLayout.EAST);
                textField.setColumns(10);
            }
            {
                panel.add(model.jpb, BorderLayout.CENTER);
            }
            {
                textField.getDocument().addDocumentListener(new DocumentListener(){

                    @Override
                    public void insertUpdate(DocumentEvent e) {
                        String text = textField.getText().trim();
                        if (text.trim().length() == 0) {
                            rowSorter.setRowFilter(null);
                        } else {
                            rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + text));
                        }
                    }

                    @Override
                    public void removeUpdate(DocumentEvent e) {
                        String text = textField.getText();
                        if (text.trim().length() == 0) {
                            rowSorter.setRowFilter(null);
                        } else {
                            rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + text));
                        }
                    }

                    @Override
                    public void changedUpdate(DocumentEvent e) {
                        String text = textField.getText();
                        if (text.trim().length() == 0) {
                            rowSorter.setRowFilter(null);
                        } else {
                            rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + text));
                        }
                    }

                });
            }
        }
    }
    f.setVisible(true);
}

private static class Row {
    String lname;
    String fname;
    String monTime;
    String tueTime;
    String wedTime;
    String thuTime;
    String friTime;
    String satTime;
    String sunTime;
    String totalTime;
}

private static class JDBCModel extends AbstractTableModel {

    private final JProgressBar jpb = new JProgressBar();
    private static final long serialVersionUID = 1L;
    private final List<Row> data = new ArrayList<>();
    private ResultSet rs = null;
    private ResultSetMetaData meta;

    public JDBCModel(Connection conn, String query) {
        try {
            Statement s = conn.createStatement();
            rs = s.executeQuery(query);
            meta = rs.getMetaData();
            JDBCWorker worker = new JDBCWorker();
            jpb.setIndeterminate(true);
            worker.execute();

        } catch (SQLException e) {
            e.printStackTrace(System.err);
        }
    }

    @Override
    public int getRowCount() {
        return data.size();
    }

    @Override
    public int getColumnCount() {
        return 10;
    }



    @Override
    public Object getValueAt(int rowIndex, int colIndex) {
        Row row = data.get(rowIndex);
        switch (colIndex) {
            case 0:
                return row.lname;
            case 1:
                return row.fname;
            case 2:
                return row.monTime;
            case 3:
                return row.tueTime;
            case 4:
                return row.wedTime;
            case 5:
                return row.thuTime;
            case 6:
                return row.friTime;
            case 7:
                return row.satTime;
            case 8:
                return row.sunTime;
            case 9:
                return row.totalTime;
        }
        return null;
    }

    @Override
    public String getColumnName(int colIndex) {
        String name = null;
        switch(colIndex){
        case 0: name = "Last Name";
            return name;
        case 1: name = "First Name";
            return name;
        case 2: name = "Monday";
            return name;
        case 3: name = "Tuesday";
            return name;
        case 4: name = "Wednesday";
            return name;
        case 5: name = "Thursday";
            return name;
        case 6: name = "Friday";
            return name;
        case 7: name = "Saturday";
            return name;
        case 8: name = "Sunday";
            return name;
        case 9: name = "Total";
            return name;
        }
        return name;
    }

    private class JDBCWorker extends SwingWorker<List<Row>, Row> {

        @Override
        protected List<Row> doInBackground() {
            try {
                while (rs.next()) {
                    Row r = new Row();
                    Integer total = 0;
                    r.lname = rs.getString(2);
                    r.fname = rs.getString(1);
                    r.monTime = String.valueOf(rs.getInt(3) + " - " + rs.getInt(4));
                    total += rs.getInt(4) - rs.getInt(3);
                    r.tueTime = String.valueOf(rs.getInt(5) + " - " + rs.getInt(6));
                    total += rs.getInt(6) - rs.getInt(5);
                    r.wedTime = String.valueOf(rs.getInt(7) + " - " + rs.getInt(8));
                    total += rs.getInt(8) - rs.getInt(7);
                    r.thuTime = String.valueOf(rs.getInt(9) + " - " + rs.getInt(10));
                    total += rs.getInt(10) - rs.getInt(9);
                    r.friTime = String.valueOf(rs.getInt(11) + " - " + rs.getInt(12));
                    total += rs.getInt(12) - rs.getInt(11);
                    r.satTime = String.valueOf(rs.getInt(13) + " - " + rs.getInt(14));
                    total += rs.getInt(14) - rs.getInt(13);
                    r.sunTime = String.valueOf(rs.getInt(15) + " - " + rs.getInt(16));
                    total += rs.getInt(16) - rs.getInt(15);
                    r.totalTime = String.valueOf(total);
                    publish(r);
                }
            } catch (SQLException e) {
                e.printStackTrace(System.err);
            }
            return data;
        }

        @Override
        protected void process(List<Row> chunks) {
            int n = getRowCount() + 1;
            System.out.println("Row Count = " + n);
            System.out.println("Chunks Size = " + chunks.size());
            for (Row row : chunks) {
                data.add(row);
            }
            fireTableRowsInserted(n, n + chunks.size());
        }

        @Override
        protected void done(){
            jpb.setIndeterminate(false);
            jpb.setValue(100);
        }
    }
}

//Creates the Test DB with test names and working hours
private static void createTestDatabase(int n) {
    Connection conn = getConnection();
    try {
        Statement st = conn.createStatement();
        st.execute("create table city(fname varchar(10), lname varchar(10), mons int(2), mone int(2), tues int(2), tuee int(2), weds int(2), wede int(2), thus int(2), thue int(2), fris int(2), frie int(2), sats int(2), sate int(2), suns int(2), sune int(2))");
        Random gen = new Random();
        PreparedStatement ps = conn.prepareStatement(
            "insert into city values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
        for (int i = 0; i < n; i++) {
            ps.setString(1, "First" + i);
            ps.setString(2, "Last" + i);
            ps.setInt(3, ThreadLocalRandom.current().nextInt(6, 12));
            ps.setInt(4, ThreadLocalRandom.current().nextInt(13, 22));
            ps.setInt(5, ThreadLocalRandom.current().nextInt(6, 12));
            ps.setInt(6, ThreadLocalRandom.current().nextInt(13, 22));
            ps.setInt(7, ThreadLocalRandom.current().nextInt(6, 12));
            ps.setInt(8, ThreadLocalRandom.current().nextInt(13, 22));
            ps.setInt(9, ThreadLocalRandom.current().nextInt(6, 12));
            ps.setInt(10, ThreadLocalRandom.current().nextInt(13, 22));
            ps.setInt(11, ThreadLocalRandom.current().nextInt(6, 12));
            ps.setInt(12, ThreadLocalRandom.current().nextInt(13, 22));
            ps.setInt(13, ThreadLocalRandom.current().nextInt(6, 12));
            ps.setInt(14, ThreadLocalRandom.current().nextInt(13, 22));
            ps.setInt(15, ThreadLocalRandom.current().nextInt(6, 12));
            ps.setInt(16, ThreadLocalRandom.current().nextInt(13, 22));
            ps.execute();
        }
    } catch (SQLException ex) {
        ex.printStackTrace(System.err);
    }
}

private static Connection getConnection() {
    try {
        return DriverManager.getConnection(URL, "", "");
    } catch (SQLException e) {
        e.printStackTrace(System.err);
    }
    return null;
}

public static void main(String[] args) {
    EventQueue.invokeLater(new WorkerTest()::display);
}
}

EDIT (Only 5 Min Later)

So I started to use the debug function in eclipse and as I was stepping through the program, I found that some event called EDT.pumpOneEventForFilters(int) had a variable called arg0(it's only argument) with a value of -1. Once stepping into this event, the Exception is thrown. I'm not sure if this is useful information, but I thought I'd toss it out there just in case.

回答1:

The original example notified the listening JTable of new rows in the implementation of process():

fireTableRowsInserted(n, n + chunks.size());

Because the table needs to consider all rows when sorting and filtering, the model should notify "listeners that all cell values in the table's rows may have changed."

fireTableDataChanged();

As an aside, a single instance of ThreadLocalRandom is sufficient in createTestDatabase().

Code, as tested:

import java.awt.BorderLayout;
import java.util.concurrent.ThreadLocalRandom;
import java.awt.EventQueue;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

/**
 * @see https://stackoverflow.com/q/43161033/230513
 * @see https://stackoverflow.com/a/34742409/230513
 * @see https://stackoverflow.com/a/24762078/230513
 */
public class WorkerTest {

    private static final int N = 256;
    private static final String URL = "jdbc:h2:mem:test";
    private static final Random r = new Random();

    private JPanel contentPane;
    private static JTable tblSchedule;
    private JScrollPane scrollSchedTable;
    private JDBCModel model;
    private JPanel panel;
    private JTextField textField;

    private static TableRowSorter<TableModel> rowSorter;

    private void display() {

        createTestDatabase(N);  //Create the Test Database

        //Create the GUI
        JFrame f = new JFrame("WorkerTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setBounds(100, 100, 780, 450);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        f.setContentPane(contentPane);
        contentPane.setLayout(new BorderLayout(0, 0));

        scrollSchedTable = new JScrollPane();
        scrollSchedTable.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        contentPane.add(scrollSchedTable);

        tblSchedule = new JTable();
        tblSchedule.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        tblSchedule.setShowHorizontalLines(true);
        tblSchedule.setShowVerticalLines(true);
        tblSchedule.setFillsViewportHeight(true);
        model = new JDBCModel(getConnection(), "select * from city");
        tblSchedule.setModel(model);
        rowSorter = new TableRowSorter<TableModel>(model);
        tblSchedule.setRowSorter(rowSorter);
        scrollSchedTable.setViewportView(tblSchedule);

        panel = new JPanel();
        contentPane.add(panel, BorderLayout.NORTH);
        panel.setLayout(new BorderLayout(0, 0));

        textField = new JTextField();
        panel.add(textField, BorderLayout.EAST);
        textField.setColumns(10);

        panel.add(model.jpb, BorderLayout.CENTER);

        textField.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                String text = textField.getText().trim();
                if (text.trim().length() == 0) {
                    rowSorter.setRowFilter(null);
                } else {
                    rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + text));
                }
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                String text = textField.getText();
                if (text.trim().length() == 0) {
                    rowSorter.setRowFilter(null);
                } else {
                    rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + text));
                }
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                String text = textField.getText();
                if (text.trim().length() == 0) {
                    rowSorter.setRowFilter(null);
                } else {
                    rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + text));
                }
            }

        });
        f.setVisible(true);
    }

    private static class Row {

        String lname;
        String fname;
        String monTime;
        String tueTime;
        String wedTime;
        String thuTime;
        String friTime;
        String satTime;
        String sunTime;
        String totalTime;
    }

    private static class JDBCModel extends AbstractTableModel {

        private final JProgressBar jpb = new JProgressBar();
        private static final long serialVersionUID = 1L;
        private final List<Row> data = new ArrayList<>();
        private ResultSet rs = null;
        private ResultSetMetaData meta;

        public JDBCModel(Connection conn, String query) {
            try {
                Statement s = conn.createStatement();
                rs = s.executeQuery(query);
                meta = rs.getMetaData();
                JDBCWorker worker = new JDBCWorker();
                jpb.setIndeterminate(true);
                worker.execute();

            } catch (SQLException e) {
                e.printStackTrace(System.err);
            }
        }

        @Override
        public int getRowCount() {
            return data.size();
        }

        @Override
        public int getColumnCount() {
            return 10;
        }

        @Override
        public Object getValueAt(int rowIndex, int colIndex) {
            Row row = data.get(rowIndex);
            switch (colIndex) {
                case 0:
                    return row.lname;
                case 1:
                    return row.fname;
                case 2:
                    return row.monTime;
                case 3:
                    return row.tueTime;
                case 4:
                    return row.wedTime;
                case 5:
                    return row.thuTime;
                case 6:
                    return row.friTime;
                case 7:
                    return row.satTime;
                case 8:
                    return row.sunTime;
                case 9:
                    return row.totalTime;
            }
            return null;
        }

        @Override
        public String getColumnName(int colIndex) {
            String name = null;
            switch (colIndex) {
                case 0:
                    name = "Last Name";
                    return name;
                case 1:
                    name = "First Name";
                    return name;
                case 2:
                    name = "Monday";
                    return name;
                case 3:
                    name = "Tuesday";
                    return name;
                case 4:
                    name = "Wednesday";
                    return name;
                case 5:
                    name = "Thursday";
                    return name;
                case 6:
                    name = "Friday";
                    return name;
                case 7:
                    name = "Saturday";
                    return name;
                case 8:
                    name = "Sunday";
                    return name;
                case 9:
                    name = "Total";
                    return name;
            }
            return name;
        }

        private class JDBCWorker extends SwingWorker<List<Row>, Row> {

            @Override
            protected List<Row> doInBackground() {
                try {
                    while (rs.next()) {
                        Row r = new Row();
                        Integer total = 0;
                        r.lname = rs.getString(2);
                        r.fname = rs.getString(1);
                        r.monTime = String.valueOf(rs.getInt(3) + " - " + rs.getInt(4));
                        total += rs.getInt(4) - rs.getInt(3);
                        r.tueTime = String.valueOf(rs.getInt(5) + " - " + rs.getInt(6));
                        total += rs.getInt(6) - rs.getInt(5);
                        r.wedTime = String.valueOf(rs.getInt(7) + " - " + rs.getInt(8));
                        total += rs.getInt(8) - rs.getInt(7);
                        r.thuTime = String.valueOf(rs.getInt(9) + " - " + rs.getInt(10));
                        total += rs.getInt(10) - rs.getInt(9);
                        r.friTime = String.valueOf(rs.getInt(11) + " - " + rs.getInt(12));
                        total += rs.getInt(12) - rs.getInt(11);
                        r.satTime = String.valueOf(rs.getInt(13) + " - " + rs.getInt(14));
                        total += rs.getInt(14) - rs.getInt(13);
                        r.sunTime = String.valueOf(rs.getInt(15) + " - " + rs.getInt(16));
                        total += rs.getInt(16) - rs.getInt(15);
                        r.totalTime = String.valueOf(total);
                        publish(r);
                    }
                } catch (SQLException e) {
                    e.printStackTrace(System.err);
                }
                return data;
            }

            @Override
            protected void process(List<Row> chunks) {
                int n = getRowCount() + 1;
                System.out.println("Row Count = " + n);
                System.out.println("Chunks Size = " + chunks.size());
                for (Row row : chunks) {
                    data.add(row);
                }
                fireTableDataChanged();
            }

            @Override
            protected void done() {
                jpb.setIndeterminate(false);
                jpb.setValue(100);
            }
        }
    }

//Creates the Test DB with test names and working hours
    private static void createTestDatabase(int n) {
        ThreadLocalRandom r = ThreadLocalRandom.current();
        Connection conn = getConnection();
        try {
            Statement st = conn.createStatement();
            st.execute("create table city(fname varchar(10), lname varchar(10), mons int(2), mone int(2), tues int(2), tuee int(2), weds int(2), wede int(2), thus int(2), thue int(2), fris int(2), frie int(2), sats int(2), sate int(2), suns int(2), sune int(2))");
            Random gen = new Random();
            PreparedStatement ps = conn.prepareStatement(
                "insert into city values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
            for (int i = 0; i < n; i++) {
                ps.setString(1, "First" + i);
                ps.setString(2, "Last" + i);
                ps.setInt(3, r.nextInt(6, 12));
                ps.setInt(4, r.nextInt(13, 22));
                ps.setInt(5, r.nextInt(6, 12));
                ps.setInt(6, r.nextInt(13, 22));
                ps.setInt(7, r.nextInt(6, 12));
                ps.setInt(8, r.nextInt(13, 22));
                ps.setInt(9, r.nextInt(6, 12));
                ps.setInt(10, r.nextInt(13, 22));
                ps.setInt(11, r.nextInt(6, 12));
                ps.setInt(12, r.nextInt(13, 22));
                ps.setInt(13, r.nextInt(6, 12));
                ps.setInt(14, r.nextInt(13, 22));
                ps.setInt(15, r.nextInt(6, 12));
                ps.setInt(16, r.nextInt(13, 22));
                ps.execute();
            }
        } catch (SQLException ex) {
            ex.printStackTrace(System.err);
        }
    }

    private static Connection getConnection() {
        try {
            return DriverManager.getConnection(URL, "", "");
        } catch (SQLException e) {
            e.printStackTrace(System.err);
        }
        return null;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new WorkerTest()::display);
    }
}