Kevin Stich
Kevin Stich

Reputation: 783

JTable not returning selected row correctly

I am working with an extension of the DefaultTableModel as follows:

This is the NEW AchievementTableModel after updating it to reflect input from some answers.

public AchievementTableModel(Object[][] c, Object[] co) {
    super(c,co);
}
public boolean isCellEditable(int r, int c) {return false;}
public void replace(Object[][] c, Object[] co) {
    setDataVector(convertToVector(c), convertToVector(co));
    fireTableDataChanged();
}

My GUI is a JTable that has the following properties:

if(table==null)
    table = new JTable(model);
else
    table.setModel(model);
table.setFillsViewportHeight(true);
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
table.getTableHeader().setReorderingAllowed(false);
table.getTableHeader().setResizingAllowed(false);
table.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION);
table.getColumnModel().setColumnSelectionAllowed(false);

I have a JComboBox that selects which data to display. The TableModel is updated with a call to model.replace(cells) and then runs through the above table creation code again.

When selecting a row in the GUI JTable, and printing the table.getSelectedRow() value, I ALWAYS get -1 after changing the table data with a model.replace(cells) call from the first selection, even if I reselect the first JComboBox option. Is there a reason for this that I'm missing? Should I change some code?

EDIT: The code has changed a lot over trying to answer this question so here is the updated code. The new AchievementTableModel is above.

This sets up the model and table to be viewed correctly and displayed in a ScrollPane

if(model==null)
    model = new AchievementTableModel(cells, columns);
else
    model.replace(cells, columns);
if(table==null) {
    table = new JTable(model);
    table.setFillsViewportHeight(true);
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    table.getTableHeader().setReorderingAllowed(false);
    table.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION);
    table.getColumnModel().setColumnSelectionAllowed(false);
    table.getTableHeader().setResizingAllowed(false);
} else
    table.setModel(model);

column = table.getColumn(columns[0]);
column.setPreferredWidth(25);
column = table.getColumn(columns[1]);
column.setPreferredWidth(225);
column = table.getColumn(columns[2]);
column.setPreferredWidth(40);
table.doLayout();

add(new JScrollPane(table), BorderLayout.CENTER);

Upvotes: 1

Views: 9566

Answers (8)

OscarRyz
OscarRyz

Reputation: 199334

Ok, now I'm interested

It looks like you have to really really clean up your code because there are a lot of reference all around.

The reason you are not seeing the table with a selected index is because each time you create a new JTable the method where you print the selected record still points to the original. Since you're displaying now a "newly" created table the old one prints -1.

The reason you get empty table when using the DefaultTableModel is because the vectors are null ( perhaps obtained from the combo ) and thus both the data and the headers disappear from the table.

You don't need a subclass if you're using Object[][] as data anyway.

So here is a somehow simpler test class that you can see to correct yours.

I test it with both, your custom TableModel and the DefaultTableModel

This has nothing to do with your custom table model but the way you're using your references.

I hope this helps.

import javax.swing.*;
import java.awt.*;
import javax.swing.table.*;
import java.util.*;
import java.awt.event.*;
public class Test { 

    private DefaultTableModel tableModel = null;
    //private AchievementTableModel tableModel = null;
    private Object []   headers = new Object[]{"Name", "Last Name"};
    private Object [][] data;
    private Object [][] dataA = new Object[][]{{"Oscar","Reyes"},{"John","Doe"}};
    private Object [][] dataB = new Object[][]{{"Color","Green"},{"Thing","Car"}};
    private JTable table;


    public static void main( String [] args ) { 
        Test test = new Test();
        test.main();
    }
    public void main() { 
        // Create the frame
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

        // Create the unique table.
        table = new JTable();
        frame.add(new JScrollPane( table ));

        // Add two buttons
        frame.add( new JPanel(){{ 
            // swap table model button ( simulates combo )
            add(new JButton("Change Table model"){{
                addActionListener( new ActionListener() { 
                    public void actionPerformed( ActionEvent e ) { 
                        if( tableModel == null ) { 
                            data = dataA;
                            tableModel = new DefaultTableModel( data, headers );
                            //tableModel = new AchievementTableModel( data, headers );
                            table.setModel( tableModel );
                        } else { 
                            data = data == dataA ? dataB : dataA;
                            tableModel.setDataVector( data, headers );
                            //tableModel.replace( data ); // not needed DefaultTableModel already has it.

                        }
                    }
                });
            }});
            // and print selectedRow button
            add( new JButton("Print selected row"){{
                addActionListener( new ActionListener() { 
                    public void actionPerformed( ActionEvent e ) { 
                        System.out.println(table.getSelectedRow());
                    }
                });
            }});

        }}, BorderLayout.SOUTH);

        // show the frame
        frame.pack();
        frame.setVisible( true );
    }

}

Your subclass unchanged.

class AchievementTableModel extends DefaultTableModel {

    public AchievementTableModel(Object[][] c, Object[] co) {
        super.dataVector = super.convertToVector(c);
        super.columnIdentifiers = super.convertToVector(co);
    }
    public int getColumnCount() {return super.columnIdentifiers.size();}
    public int getRowCount() {return super.dataVector.size();}
    public String getColumnName(int c) {return (String)super.columnIdentifiers.get(c);}
    @SuppressWarnings("unchecked")
    public Object getValueAt(int r, int c) {return ((Vector<Object>)super.dataVector.get(r)).get(c);}
    public boolean isCellEditable(int r, int c) {return false;}
    public void replace(Object[][] c) {
        super.dataVector = super.convertToVector(c);
        super.fireTableDataChanged();
    }
}

Try it and see how it doesn't lose the table reference and always print the correct selectedRow.

alt text

Compare it with your code and fix it from there.

Upvotes: 2

user1639485
user1639485

Reputation: 808

I had the same problem of getting a -1 always for getSelectedRow(). The problem could have got solved by now. Nonetheless, posting the code which fixed my problem:

final int selectedRowIndex = table.rowAtPoint(mouseEvent.getPoint());
final int modelRowIndex = table.convertRowIndexToModel(selectedRowIndex);

Upvotes: 0

akf
akf

Reputation: 39495

you shouldnt reinitialize your table with a new JTable after you call replace. the fireTableDataChanged() method will alert your existing table that it should repaint. what is happening is that you are looking at the table that you put into the panel, but you are changing the variable to a different instance of JTable. When you query that new, but not visible table, it will give you -1 for the selected row count. it might be helpful if you edit your post to display what is going on in that area of the code.

2nd edit:

instead of this:

  if(model==null)
    model = new AchievementTableModel(cells, columns);
  else
    model.replace(cells, columns);
  if(table==null) {
    table = new JTable(model);
    table.setFillsViewportHeight(true);
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    table.getTableHeader().setReorderingAllowed(false);
    table.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION);
    table.getColumnModel().setColumnSelectionAllowed(false);
    table.getTableHeader().setResizingAllowed(false);
  } else
    table.setModel(model);

  column = table.getColumn(columns[0]);
  column.setPreferredWidth(25);
  column = table.getColumn(columns[1]);
  column.setPreferredWidth(225);
  column = table.getColumn(columns[2]);
  column.setPreferredWidth(40);
  table.doLayout();

  add(new JScrollPane(table), BorderLayout.CENTER);

do this instead:

 if(model==null) {
    model = new AchievementTableModel(cells, columns);
 } else {
    model.setDataVector(cells, columns);
 }
 if(table==null) {
    table = new JTable(model);
    table.setFillsViewportHeight(true);
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    table.getTableHeader().setReorderingAllowed(false);
    table.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION);
    table.getColumnModel().setColumnSelectionAllowed(false);
    table.getTableHeader().setResizingAllowed(false);

    column = table.getColumn(columns[0]);
    column.setPreferredWidth(25);
    column = table.getColumn(columns[1]);
    column.setPreferredWidth(225);
    column = table.getColumn(columns[2]);
    column.setPreferredWidth(40);
    table.doLayout();

    add(new JScrollPane(table), BorderLayout.CENTER);
   } else {
    table.setModel(model);
   }

you dont need to add the table to a new scrollpane and re-add it to the panel on each model change.

Upvotes: 2

Tom Neyland
Tom Neyland

Reputation: 6968

Another thing I thought of, when you do table= new JTable(model); you are changing the table that the variable 'table' is referring to, however that may not automatically cause the new table to be rendered.

If you table is contained within a ScrollPane, you may need to call ScrollPane.setViewportView(table);

Upvotes: 0

Petriborg
Petriborg

Reputation: 2960

When it swaps out the data its removing the selection (since the index is now different), you'll need recalculate the selection and set it programmatically.

I'll point out that in my experience, this is why I tend to extend AbstractTableModel or out right implement my own TableModel interface from the ground up. Modifying the backing data reference as here, always causes a million problems IMHO.

Upvotes: 1

Tom Neyland
Tom Neyland

Reputation: 6968

Maybe try using

super.setDataVector(Vector dataVector, Vector ColumnNames);

javax.​swing.​table.​DefaultTableModel
public void setDataVector(Vector dataVector, Vector columnIdentifiers)

From JavaDoc

Replaces the current dataVector instance variable with the new Vector of rows, dataVector. Each row is represented in dataVector as a Vector of Object values. columnIdentifiers are the names of the new columns. The first name in columnIdentifiers is mapped to column 0 in dataVector. Each row in dataVector is adjusted to match the number of columns in columnIdentifiers either by truncating the Vector if it is too long, or adding null values if it is too short. Note that passing in a null value for dataVector results in unspecified behavior, an possibly an exception. Parameters: dataVector - the new data vector columnIdentifiers - the names of the columns

Upvotes: 1

OscarRyz
OscarRyz

Reputation: 199334

Sounds like when changing your selection is lost.

Does "getSelectedRow()" return anything "BEFORE" you change the model?

If so, then hold that index, change the model and then set that index again.

Probably you need to use a custom ListSelectionModel for that

Upvotes: 0

Gandalf
Gandalf

Reputation: 9855

When you reorder a JTable you need to keep track of the original indexes in the TableModel for your data, not the current indexes on the JTable. Visually the table may have shifted but the underlying data model has not.

Upvotes: 0

Related Questions