2

I have a JTable called transactionList, each row represents a Transaction where the first column holds the actual Transaction object and each subsequent column displays some data about the Transaction.

I don't want the Transaction object appearing to the user so I remove it from the ColumnModel:

TableColumnModel cm = transactionList.getColumnModel();
cm.removeColumn(cm.getColumn(0));
transactionList.setColumnModel(cm);

This works great and I can then retrieve the selected Transaction using:

(Transaction) transactionList.getModel().getValueAt(transactionList.getSelectedRow(), 0))

The problem begins when the user sorts a column in the table, then the selected row does not match up correctly. I solve this by changing the above line so that we get the selected row from the table directly and not from the model:

(Transaction) transactionList.getValueAt(transactionList.getSelectedRow(), 0));

But now the 0 column is not my hidden Transaction object but simply its first field.


I will try to explain in another way as well.

Without allowing sorting of the JTable columns, both examples works:

1) Display Transaction object in first column (don't remove from ColumnModel)

Can retrieve Transaction object with:

transactionList.getValueAt(transactionList.getSelectedRow(), 0));

2) Display Transaction object in first column (remove from ColumnModel)

Can retrieve Transaction object with:

transactionList.getModel().getValueAt(transactionList.getSelectedRow(), 0))

If I now allow sorting of the columns, #1 still works. However, method #2 now passes the JTable.getSelectedRow() to TableModel.getValueAt(). These indexes are no longer equal though, the problem is that the JTable and ColumnModel don't contain the Transaction object (only the TableModel does).

transactionList.getColumnCount(); //return 5
transactionList.getColumnModel().getColumnCount(); //return 5
transactionList.getModel().getColumnCount(); //returns 6

A possible solution I see is to sort the TableModel along with the JTable when the user clicks a column header. Is this viable? Are there better ways of achieving this goal (having a "hidden" object attached to a JTable row)?


TableModel

public class TransactionTableModel extends AbstractTableModel {
    String[] columnNames = { "<Transaction_Object>", "Date", "Name",
            "Hours", "Amount", "Notes" };

    public TransactionTableModel() {
    }
    public String getColumnName(int col) {
        return columnNames[col].toString();
    }

    public Class<?> getColumnClass(int col) {
        switch (col) {
        case 1:
            return Calendar.class;
        case 2:
            return String.class;
        }
    }

    public int getRowCount() {
        return DB.getInstance().getTransactions().size();
    }

    public int getColumnCount() {
        return columnNames.length;
    }

    public Object getValueAt(int row, int col) {
        Transaction t = null;
        t = DB.getInstance().getTransactionsChronological().get(row);
        switch (col) {
        case 0:
            return t;
        case 1:
            return t.getDate();
        case 2:
            return t.getStudent.getName();
        }
    }
}

MCVE

There is a hidden first column which has matching values to the second. With an unsorted JTable the button prints the correct value, but after sorting a column by clicking on the header the models are out of sync.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;

public class SwingTesting {

    JFrame frame;
    TablePane tablePane;

    public SwingTesting() {
        tablePane = new TablePane();

        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        JButton test = new JButton("Print hidden item");
        test.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                printHiddenItem();
            }
        });

        frame.add(tablePane);
        frame.add(test, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    class TablePane extends JPanel {

        private final JTable table;
        private final TableModel tableModel;
        private final ListSelectionModel listSelectionModel;

        public TablePane() {
            table = new JTable();
            tableModel = createTableModel();
            table.setModel(tableModel);
            table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
            table.setAutoCreateRowSorter(true);
            table.add(table.getTableHeader(), BorderLayout.PAGE_START);
            table.setFillsViewportHeight(true);

            listSelectionModel = table.getSelectionModel();
            table.setSelectionModel(listSelectionModel);

            this.add(new JScrollPane(table));

            testMethod();
        }

        private TableModel createTableModel() {
            DefaultTableModel model = new DefaultTableModel(new Object[] {
                    "First", "Second", "Third" }, 0) {
            };

            addTableData(model);
            return model;
        }

        private void addTableData(DefaultTableModel model) {
            model.addRow(new Object[] { "ONE", "ONE", "2007" });
            model.addRow(new Object[] { "TWO", "TWO", "2012" });
            model.addRow(new Object[] { "THREE", "THREE", "2009" });
            model.addRow(new Object[] { "FOUR", "FOUR", "2005" });
            model.addRow(new Object[] { "FIVE", "FIVE", "2001" });
        }


        private void testMethod() {
            TableColumnModel cm = table.getColumnModel();
            cm.removeColumn(cm.getColumn(0));
            table.setColumnModel(cm);
        }

    }

    public void printHiddenItem() {
        System.out.println(tablePane.table.getModel().getValueAt(tablePane.table.getSelectedRow(), 0));
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new SwingTesting();

            }
        });
    }
}
Ron
  • 1,450
  • 15
  • 27
  • For better help sooner, post an [MCVE](http://stackoverflow.com/help/mcve). – Andrew Thompson Jan 10 '14 at 07:00
  • I've added some more code, mostly just a stripped down version of my TableModel. – Ron Jan 10 '14 at 07:38
  • not help me somehow this edit, again this is quite basic task, nothing compliaced, depends of goal, out of this thread to avoids any my guessing – mKorbel Jan 10 '14 at 07:46
  • @AndrewThompson There are too many classes that this is dependent on. I don't think a full MCVE is necessary here, perhaps I am miscommunicating the issue. I just need to access a TableModel by row after the user has destroyed the JTable view order through sorting. Accessing the JTable directly does not suffice as the data is in a hidden column that is only in the Model. – Ron Jan 10 '14 at 07:52
  • 1
    Some examples of table based MVCEs I've been involved with [1](http://stackoverflow.com/a/10606820/418556), [2](http://stackoverflow.com/a/9852722/418556), [3](http://stackoverflow.com/q/7369814/418556), [4](http://stackoverflow.com/a/6175860/418556).. Try to base your example on codes like that. – Andrew Thompson Jan 10 '14 at 08:02
  • @AndrewThompson Thank you for the examples, I've made use of them and edited my question to include an MVCE. – Ron Jan 10 '14 at 08:40
  • @AndrewThompson I chose to abide heavily by the Minimal spec. I think this MVCE shows the issue quite succinctly, I am needing a way to associate the sorted list with the original model so I can access a row in the hidden column. Thanks for looking at it – Ron Jan 10 '14 at 08:54
  • 1
    model.getValue(table.convertRowIndexToModel(table.getSelectedRow()), 0) - or is that already hidden in all the noise above? – kleopatra Jan 10 '14 at 09:44
  • @AndrewThompson oooohhh nooooo - at last I had learned all letters (and mostly in the correct order) of SSCCE, now you are introducing some others ... – kleopatra Jan 10 '14 at 09:46
  • @kleopatra model.getValue(table.convertRowIndexToModel(table.getSelectedRow()), 0) is exactly what I'm looking for! – Ron Jan 10 '14 at 10:45
  • glad that your problem is solved - just seeing that @mKorbel already mentioned it a couple of hours earlier than me (in his comments the Andrew's answer) - so you _could_ have read it earlier :-) – kleopatra Jan 10 '14 at 10:56
  • @kleopatra I don't see where mKorbel mentioned it in any of his comments here. Either way, thank you all for your time in helping me. – Ron Jan 10 '14 at 11:33
  • _you can to convert row index and column index too, bot are important 1st for filtering/sorting_ – kleopatra Jan 10 '14 at 12:03
  • @kleopatra That quote starts with "not" and was also very vague. If he mentioned converting JTable index to TableModel index that would be what I was looking for. I guess it's a trivial problem and everyone is overthinking it. You provided exactly the method I needed. Make an answer and I'll accept. – Ron Jan 10 '14 at 12:50

2 Answers2

5

One thingy to keep in mind is that JTable and TableModel have two separate coordinate systems, the view vs. model system:

  • column indices may differ due to re-ordering
  • row indices may differ due to sorting/filtering

JTable has methods convertRow/ColumnToView/Model to map back and forth, for the concrete problem of accessing the model row of the selected (view!) row:

model.getValueAt(table.convertRowIndexToModel(table.getSelectedRow()), 0);
kleopatra
  • 51,061
  • 28
  • 99
  • 211
2

Am I going about using the table correctly?

No. For this I would create a custom TransactionTableModel that accepts a collection of Transaction objects and creates exactly as many columns as needed to display the relevant Transaction details.

See Creating a Table Model for more details.

It might go a little something like this:

import java.awt.*;
import java.awt.event.*;
import java.util.Vector;
import javax.swing.*;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelListener;
import javax.swing.table.*;

public class SwingTesting {

    JFrame frame;
    TablePane tablePane;

    public SwingTesting() {
        tablePane = new TablePane();

        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        JButton test = new JButton("Print hidden item");
        test.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                //printHiddenItem();
            }
        });

        frame.add(tablePane);
        frame.add(test, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    class TablePane extends JPanel {

        private final JTable table;
        private final ListSelectionModel listSelectionModel;

        public TablePane() {
            super(new BorderLayout());
            Vector<Vector<Transaction>> transactions =
                    new Vector<Vector<Transaction>>();
            addTransactionToVector(
                    transactions, new Transaction("ONE", "ONE", "2007"));
            addTransactionToVector(
                    transactions, new Transaction("TWO", "TWO", "2012"));
            addTransactionToVector(
                    transactions, new Transaction("THREE", "THREE", "2009"));
            addTransactionToVector(
                    transactions, new Transaction("FOUR", "FOUR", "2005"));
            addTransactionToVector(
                    transactions, new Transaction("FIVE", "FIVE", "2001"));

            Vector<String> columnNames = new Vector<String>();
            columnNames.add("Column 1");
            columnNames.add("Column 2");
            columnNames.add("Year");

            table = new JTable(
                    new TransactionTableModel(transactions, columnNames));

            listSelectionModel = new DefaultListSelectionModel();
            table.setSelectionModel(listSelectionModel);
            table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);

            table.setAutoCreateRowSorter(true);
            table.setFillsViewportHeight(true);

            this.add(new JScrollPane(table));
        }

        private void addTransactionToVector(
                Vector<Vector<Transaction>> transactions, Transaction transaction) {
            Vector<Transaction> transactionVector = new Vector<Transaction>();
            transactionVector.add(transaction);
            transactions.add(transactionVector);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new SwingTesting();
            }
        });
    }
}

class Transaction {

    String number1;
    String number2;
    String year;

    Transaction(String number1, String number2, String year) {
        this.number1 = number1;
        this.number2 = number2;
        this.year = year;
    }

    public String toString() {
        return "Transaction:- number1: "
                + number1
                + " number2: "
                + number2
                + " year: "
                + year;
    }
}

class TransactionTableModel extends DefaultTableModel {

    TransactionTableModel(
            Vector<Vector<Transaction>> transactions,
            Vector<String> columnNames) {
        super(transactions, columnNames);
    }

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

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Object o = super.getValueAt(rowIndex, 0);
        Transaction transaction = (Transaction)o;
        switch (columnIndex) {
            case 0:
                return transaction.number1;
            case 1:
                return transaction.number2;
            case 2:
                return transaction.year;
            default:
                return null;
        }
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        Transaction transaction = (Transaction)aValue;
        Vector<Transaction> values = new Vector<Transaction>();
        values.add(transaction);
        super.setValueAt(aValue, rowIndex, 0);
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • I have already subclassed AbstractTableModel, the issue is programmatically retrieving data that was stored in the model originally but was removed from the columnModel (this only removes it from the view). The number of columns never changes, only the row count and order. – Ron Jan 10 '14 at 07:20
  • 1
    @Ron E nobody knows without your MVCE, short, runnable, compilable with hardcoded value for AbstractTableModel in local variable, you have to look at ColumnModel, convert view to model or model to view (directions depends of ..., not clear from your question), just guessing – mKorbel Jan 10 '14 at 07:41
  • @mKorbel It sounds like I would want to convert View to Model. But when I created my JTable I was pushed in the direction of creating a hidden column to store the Transaction object, this is not available in the view. – Ron Jan 10 '14 at 07:45
  • What would solve my issue (kind of a workaround) is once the user has sorted the JTable, find out what row the currently selected row occupied in the original table. – Ron Jan 10 '14 at 07:46
  • not you can to convert row index and column index too, bot are important 1st for filtering/sorting, 2nd for column reordering/removing – mKorbel Jan 10 '14 at 08:03
  • OK. Solved the (silly) bug. See the edit. Note that during the course of changes, I altered the button to do nothing. I'm working on the presumption that a lot of what I removed is redundant with the new `TableModel`..? – Andrew Thompson Jan 10 '14 at 10:42