# JTable, (Auto-)RowSorter und NullPointer-Exception



## Daniel_L (25. Feb 2009)

Hallo,

ich habe ein Problem mit Tabellen, deren Spalten sortiert sind.

Beim Programmstart verknüpfe ich über folgende Methode TableSorter mit Tabellen:

```
private void setCustomTableRowSorter(JTable table, int column) {
        // create new table sorter
        TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>();
        // tell this jtable that it has an own sorter
        table.setRowSorter(sorter);
        // and tell the sorter, which table model to sort.
        sorter.setModel((DefaultTableModel)table.getModel());
        // in this table, the first column needs a custom comparator.
        try {
            sorter.setComparator(column,new Comparer());
            // in case we have the table with titles, we make an exception, because
            // this table has two more columns that should be sorted, the columns with
            // the entries timestamps.
            if (table==jTableTitles) {
                sorter.setComparator(2,new DateComparer());
                sorter.setComparator(3,new DateComparer());
            }
        }
        catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }
```

Zwischendurch werden die Tabelleninhalte immer mal wieder mit Daten neu erstellt, und zwar so:

```
// reset the table
            tableModel.setRowCount(0);
            // go through all entries of the datafile
            for (int cnt=1; cnt<=dataObj.getCount(CDaten.ZKNCOUNT); cnt++) {
                // get zettel-title
                String title = dataObj.getZettelTitle(cnt);
...
...
                // create a new object with these data
                Object[] ob = new Object[4];
                ob[0] = cnt; // ob[0] = String.valueOf(cnt);
                ob[1] = title;
                ob[2] = created;
                ob[3] = edited;
                // and add it to the table
                tableModel.addRow(ob);
                // update progressbar
                setProgress(cnt,0,dataObj.getCount(CDaten.ZKNCOUNT));
            }
```

Das klappt erstmal ohne Probleme. Sobald ich aber eine Tabellenspalte per Mausklick auf die Spaltenüberschrift sortiere, und _anschließend_ irgendwann den Tabelleninhalt wieder neu erstelle, kommt es zu folgender Fehlermeldung:


> Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
> at javax.swing.DefaultRowSorter.convertRowIndexToModel(DefaultRowSorter.java:501)
> at javax.swing.JTable.convertRowIndexToModel(JTable.java:2564)
> at javax.swing.JTable.getValueAt(JTable.java:2639)
> ...



Muss ich, sobald irgendwann mal eine Tabellenspalte vom Anwender per Klick auf die Spaltenüberschrift sortiert wurde, beim erneuten Erstellen der Tabelleninhalte den RowSorter irgendwie abstellen, oder wie kann ich hier die NullPointer-Exception vermeiden?

Gruß
Daniel


----------



## Daniel_L (25. Feb 2009)

Verstehe ich das richtig, dass dies ein Java-Bug ist? Weiß jemand, ob es workarounds gibt, oder ob man mit dem Bug leben kann?


----------



## Daniel_L (25. Feb 2009)

So, habe die Lösung gefunden. Ich habe einen modalen Dialog zum Neuerstellen der Tabelleninhalte verwendet. Hier startete ein Thread, sodass die Anwendung zwar durch den modalen Dialog "blockiert" war, aber eine ProgressBar den Fortschritt des Erstellens der Inhalte anzeigte.

Bevor ich den Thread aufrufe, muss ich setAutoCReateRowSorter auf false setzen.

Das reicht aber noch nicht. In der doInBackground-Methode des Threads darf ich das Table-Model noch nicht direkt bearbeiten. Dort fülle ich die Daten erstmal in eine LinkedList. Dann in der succeeded-Methode kommen die Daten erst in das TableModel:


```
@Override protected void succeeded(Object result) {
            // Runs on the EDT.  Update the GUI based on
            // the result computed by doInBackground().
            // reset the table
            tableModel.setRowCount(0);
            // check whether we have any entries at all...
            if (list!=null) {
                // create iterator for linked list
                Iterator<Object[]> i = list.iterator();
                // go through linked list and add all objects to the table model
                try {
                    while (i.hasNext()) tableModel.addRow(i.next());
                }
                catch (ConcurrentModificationException e) {
                    // reset the table when we have overlappings threads
                    tableModel.setRowCount(0);
                }
            }
```

So klappt es ohne Fehler/Exceptions...


----------



## Ebenius (25. Feb 2009)

Java-Bug 6386900 ist ganz sicher was anderes.

Da Du mit mehreren Threads arbeitest: Du machst dann die Tabellenmodelländerungen schon alle hübsch mit SwingUtilities.invokeLater(...) oder ähnlichen Konstrukten?

Ebenius


----------



## Daniel_L (25. Feb 2009)

Ich verwende eigentlich eine Art "Template" für Background-Tasks, das ich aus NetBeans-Beispielen übernommen habe.

Hier der Code des JDialogs:


```
public CShowTitlelistDialog(java.awt.Frame parent, CDaten d, DefaultTableModel tm) {
        super(parent);
        initComponents();
        
        dataObj = d;
        tableModel = tm;
        // show status text
        msgLabel.setText(resourceMap.getString("msg1"));
        // start the background task manually
        Task clT = createTitleList();
        // get the application's context...
        ApplicationContext appC = Application.getInstance().getContext();
        // ...to get the TaskMonitor and TaskService
        TaskMonitor tM = appC.getTaskMonitor();
        TaskService tS = appC.getTaskService();
        // with these we can execute the task and bring it to the foreground
        // i.e. making the animated progressbar and busy icon visible
        tS.execute(clT);
        tM.setForegroundTask(clT);   
    }

    
    /**
     * The Action which starts the task. Not needed indeed, but for starting the task manually
     * within the constructor, we need a method we can call. Therefor this action
     * @return
     */
    @Action
    public Task createTitleList() {
        return new createTitlelistTask(org.jdesktop.application.Application.getInstance(zettelkasten.ZettelkastenApp.class));        
    }
    
    private class createTitlelistTask extends org.jdesktop.application.Task<Object, Void> {
        LinkedList<Object[]> list;
        createTitlelistTask(org.jdesktop.application.Application app) {
            // Runs on the EDT.  Copy GUI state that
            // doInBackground() depends on from parameters
            // to createLinksTask fields, here.
            super(app);
            
            createTitleTask = this;
        }        
        
        @Override protected Object doInBackground() {
            // Your Task's code here.  This method runs
            // on a background thread, so don't reference
            // the Swing GUI from here.

            // create new instance of that variable
            list = new LinkedList<Object[]>();
            // go through all Authors of the Author datafile
            for (int cnt=1; cnt<=dataObj.getCount(CDaten.ZKNCOUNT); cnt++) {
                // get zettel-title
                String title = dataObj.getZettelTitle(cnt);
...
...
                Object[] ob = new Object[4];
                ob[0] = cnt; // ob[0] = String.valueOf(cnt);
                ob[1] = title;
                ob[2] = created;
                ob[3] = edited;
                // and add it to the table
                list.add(ob);
                // update progressbar
                setProgress(cnt,0,dataObj.getCount(CDaten.ZKNCOUNT));
            }
            return null;
        }
        
        @Override protected void succeeded(Object result) {
            // Runs on the EDT.  Update the GUI based on
            // the result computed by doInBackground().
            // reset the table
            tableModel.setRowCount(0);
            // check whether we have any entries at all...
            if (list!=null) {
                // create iterator for linked list
                Iterator<Object[]> i = list.iterator();
                // go through linked list and add all objects to the table model
                try {
                    while (i.hasNext()) tableModel.addRow(i.next());
                }
                catch (ConcurrentModificationException e) {
                    // reset the table when we have overlappings threads
                    tableModel.setRowCount(0);
                }
            }
            dataObj.setTitlelistUpToDate(true);
        }

        @Override
        protected void finished() {
            super.finished();
            // when the task is finished, clear it
            createTitleTask = null;
            // and close window
            dispose();
            setVisible(false);
        }
    }
```


Aufgerufen wird der JDialog so:

```
// disable auto-creation of row-sorter, so we can fill the tablemodel with
        // new data without getting exceptions
        jTableTitles.setAutoCreateRowSorter(false);
        JFrame mainFrame = ZettelkastenApp.getApplication().getMainFrame();
        // if dialog window isn't already created, do this now
        if (null == showTitleDlg) {
            // create a new dialog window
            showTitleDlg = new CShowTitlelistDialog(mainFrame,data,(DefaultTableModel)jTableTitles.getModel());
            // center window
            showTitleDlg.setLocationRelativeTo(mainFrame);
        }
        ZettelkastenApp.getApplication().show(showTitleDlg);
        // we have to manually dispose the window and release the memory
        // because next time this method is called, the showKwlDlg is still not null,
        // i.e. the constructor is not called (because the if-statement above is not true)
        showTitleDlg.dispose();
        showTitleDlg = null;
        // re-activate autosorter again
        jTableTitles.setAutoCreateRowSorter(true);
```


----------



## Daniel_L (26. Feb 2009)

Ok, jetzt kapiere ich so langsam... Da Änderungen am TableModel sich auf den GUI-State auswirken (JTable ändert sich), dürfen Änderungen daran nicht in der doInBackground-Methode, sondern erst in succeeded auftauchen... steht ja auch in den Kommentaren, die vom NetBeans-Template erzeugt werden - aber als Java-Anfänger muss man das natürlich erstmal kapieren. ^^


```
@Override protected Object doInBackground() {
            // Your Task's code here.  This method runs
            // on a background thread, [b]so don't reference
            // the Swing GUI from here.[/b]

        @Override protected void succeeded(Object result) {
            // Runs on the EDT.  [b]Update the GUI based on
            // the result computed by doInBackground().[/b]
            // reset the table
```


----------



## Ebenius (26. Feb 2009)

Daniel_L hat gesagt.:


> Ok, jetzt kapiere ich so langsam... Da Änderungen am TableModel sich auf den GUI-State auswirken (JTable ändert sich), dürfen Änderungen daran nicht in der doInBackground-Methode, sondern erst in succeeded auftauchen... steht ja auch in den Kommentaren, die vom NetBeans-Template erzeugt werden - aber als Java-Anfänger muss man das natürlich erstmal kapieren.[/code]


Das meinte ich oben eigentlich, ja. Bin nur noch nicht wieder dazu gekommen, es nochmal zu erklären. Schön, dass Du's selbst gefunden hast.

Ebenius


----------

