1
votes

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();

            }
        });
    }
}
2
For better help sooner, post an MCVE. - Andrew Thompson
I've added some more code, mostly just a stripped down version of my TableModel. - Ron
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
Some examples of table based MVCEs I've been involved with 1, 2, 3, 4.. Try to base your example on codes like that. - Andrew Thompson
model.getValue(table.convertRowIndexToModel(table.getSelectedRow()), 0) - or is that already hidden in all the noise above? - kleopatra

2 Answers

4
votes

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);
2
votes

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);
    }
}