Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
189 views
in Technique[技术] by (71.8m points)

java - Convert modelRowIndex to viewRowIndex for sorted JTable

I have a sortable JTable; when a row is added I want to know its view-index. I tried using a table model listener like this:

@Override
public void tableChanged(TableModelEvent event)
{
    if (event.getType() == TableModelEvent.INSERT)
    {
        int modelRowIndex = event.getFirstRow();
        int viewRowIndex = table.convertRowIndexToView(modelRowIndex);
        System.out.println("viewRowIndex: " + viewRowIndex);
    }
}

This works if the table is not sorted. Unfortunately, if the table is sorted the conversion-method results in an ArrayIndexOutOfBoundsException caused by DefaultRowSorter.java:503.

I guess this happens because the table model notifies my listener before it notifies the row sorter. Any ideas? Thanks!

Here is a SSCCE:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;

public class RowIndexSSCCE extends JFrame
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                new RowIndexSSCCE().setVisible(true);
            }
        });
    }

    private final JTable table;

    public RowIndexSSCCE()
    {
        JButton button = new JButton("Add");
        button.addActionListener(new ButtonListener());

        table = new JTable(new Model());
        table.setAutoCreateRowSorter(true);
        table.getModel().addTableModelListener(new ModelListener());

        JScrollPane scrollPane = new JScrollPane(table);
        JPanel panel = new JPanel();
        panel.add(button);
        panel.add(scrollPane);
        add(panel);
        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private class ButtonListener implements ActionListener
    {
        @Override
        public void actionPerformed(ActionEvent event)
        {
            ((Model) table.getModel()).addRow("asdf" + table.getRowCount());
        }
    }

    private class ModelListener implements TableModelListener
    {
        @Override
        public void tableChanged(TableModelEvent event)
        {
            if (event.getType() == TableModelEvent.INSERT)
            {
                int modelRowIndex = event.getFirstRow();
                int viewRowIndex = table.convertRowIndexToView(modelRowIndex);
                System.out.println("viewRowIndex: " + viewRowIndex);
            }
        }
    }

    private class Model extends AbstractTableModel
    {
        private final List<String> data = new ArrayList<String>();

        public void addRow(String string)
        {
            int oldSize = data.size();
            data.add(string);
            fireTableRowsInserted(oldSize, oldSize);
        }

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

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

        @Override
        public Object getValueAt(int rowIndex, int columnIndex)
        {
            return data.get(rowIndex);
        }
    }
}

Edit:

I just figured it out myself... using SwingUtilities.invokeLater ensures that the row index conversion is done after the row sorter has been updated:

@Override
public void tableChanged(final TableModelEvent event)
{
    if (event.getType() == TableModelEvent.INSERT)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                int modelRowIndex = event.getFirstRow();
                int viewRowIndex = table.convertRowIndexToView(modelRowIndex);
                System.out.println("viewRowIndex: " + viewRowIndex);
            }
        });
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You are right, the table model listeners are informed in the wrong order. The easiest solution that comes to my mind is to implement the listeners yourself. As for me, I never implement table models by extending the AbstractTableModel. I find it more transparent to implement the functionality myself.

private class Model extends AbstractTableModel {
    private final List<TableModelListener> listeners = new ArrayList<TableModelListener>();

    @Override
    public void addTableModelListener(TableModelListener l) {
        listeners.add(l);
    }

    @Override
    public void fireTableRowsInserted(int firstRow, int lastRow) {
        for (TableModelListener l : listeners) {
            l.tableChanged(new TableModelEvent(Model.this, firstRow,
                    lastRow, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
        }
    }
    ...
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...