Issue after filtering JTable
Asked Answered
E

2

2

I have an issue with a program I am working on. To briefly explain, I have a JTable with multiple columns and rows. Particular columns have editable fields when upon changing the value other column values change according to the inputted data. Everything works well however when I've added a filter option to the JTable, changes made to an editable column won't change the values of other columns as intended after applying the filter. I've attached a couple of images to show the problem.

The first image shows the unfiltered table working correctly. Changing a Discount column value will reduce the corresponding price stored in the GPL column by the percent the inputted discount and displayed in the corresponding row in the SP column. Changing a Quantity column value will multiply the corresponding SP column price with the inputted quantity and displayed in the corresponding row in the Total column.

Unfiltered table

The second image shows the filtered table not working as intended. Changing a value in either Discount or Quantity columns will not change the intended columns.

Filtered table

I've added the SSCCE code below which contains 2 classes. First is the table itself and the second is the listener for the table.

EDIT I've changed the code of the class according to camickr's answer and now fully works.

TableCellChange class

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.math.BigDecimal;
import java.math.MathContext;
import java.text.DecimalFormat;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.RowFilter;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

public final class TableCellChange extends JPanel {  

    private static JFrame frameTableCellChange;
    private JPanel panelTable, panelButtons;
    private JButton buttonResetDiscounts, buttonResetQuantities, buttonExit;
    private JTextField textFilterBox, quantityField, discountField;
    private JLabel labelFilter;
    private DefaultTableModel tableModel;
    private JTable table;
    private TableRowSorter<DefaultTableModel> sorter;
    private TableColumn columnDiscount, columnTotal, columnQuantity;
    private TableCellListener tableCellListener;
    private String checkForNull;
    private DecimalFormat decimalFormatUS;
    private Locale localeUSFormat;    
    private BigDecimal valueDiscount, valueGPL, resultDiscount, resultSP, resultTotal,
            backupDiscount = new BigDecimal("0"); 
    private int selectedColumnIndex, selectedRowIndex, valueQuantity, backupQuantity = 1;

    public TableCellChange() {

        super(); 

        panelTable = new JPanel();
        panelButtons = new JPanel();
        setLayout(new BorderLayout());

        createTable();
        createButtons();
        add(panelTable, BorderLayout.NORTH);
        add(panelButtons, BorderLayout.CENTER);

        // Always focus on the JTextField when opening the window
        SwingUtilities.invokeLater(new Runnable()  {
           @Override
           public void run() {  
               textFilterBox.requestFocusInWindow();
           }
        });
    } // -> TableCellChange()

    // Create the buttons for the query result window
    public void createButtons() {
        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints gridcons = new GridBagConstraints();
        gridcons.fill = GridBagConstraints.HORIZONTAL;
        panelButtons.setLayout(gridbag);

        labelFilter = new JLabel("Quick search:");
        gridcons.insets = new Insets(5,0,0,0);
        gridcons.gridx = 0;
        gridcons.gridy = 0;
        gridcons.gridwidth = 2;
        gridbag.setConstraints(labelFilter, gridcons);
        labelFilter.setHorizontalAlignment(JLabel.CENTER);
        panelButtons.add(labelFilter);

        // Create text field for filtering
        textFilterBox = new JTextField();
        gridcons.insets = new Insets(5,0,0,0);
        gridcons.gridx = 0;
        gridcons.gridy = 1;
        gridcons.gridwidth = 2;
        gridbag.setConstraints(textFilterBox, gridcons);
                textFilterBox.getDocument().addDocumentListener(
                new DocumentListener() {
                    @Override
                    public void changedUpdate(DocumentEvent e) {
                        tableFilter();
                    }
                    @Override
                    public void insertUpdate(DocumentEvent e) {
                        tableFilter();
                    }
                    @Override
                    public void removeUpdate(DocumentEvent e) {
                        tableFilter();
                    }
                }); // -> DocumentListener()
        panelButtons.add(textFilterBox);

        // Create the button to reset the discount column to 0%
        buttonResetDiscounts = new JButton("Reset all discounts");
        buttonResetDiscounts.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {          
                BigDecimal valueGPL, valueTotal;
                int valueQuantity;
                for (int i = 0; i < table.getModel().getRowCount(); i++) {
                    valueGPL = new BigDecimal( table.getModel().
                            getValueAt(i, 2).toString().replaceAll("[$,]", "") );
                    table.getModel().setValueAt("0%", i, 3);   
                    table.getModel().setValueAt(DecimalFormat
                            .getCurrencyInstance(localeUSFormat).format(valueGPL), i, 4);
                    valueQuantity = Integer.parseInt( table.getModel().
                            getValueAt(i, 5).toString() );
                    valueTotal = valueGPL.multiply(new BigDecimal(valueQuantity),
                            new MathContext(BigDecimal.ROUND_HALF_EVEN));
                    table.getModel().setValueAt(DecimalFormat
                            .getCurrencyInstance(localeUSFormat).format(valueTotal), i, 6);
                }
            }
        });         
        gridcons.insets = new Insets(10,0,0,0);
        gridcons.gridx = 0;
        gridcons.gridy = 3;
        gridcons.gridwidth = 1;
        gridbag.setConstraints(buttonResetDiscounts, gridcons);
        panelButtons.add(buttonResetDiscounts);

        // Create button to reset the quantity column to 1 
        buttonResetQuantities = new JButton("Reset all quantities");
        buttonResetQuantities.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {             
                BigDecimal valueSP;
                for (int i = 0; i < table.getModel().getRowCount(); i++) {                    
                    valueSP = new BigDecimal( table.getModel().
                            getValueAt(i, 4).toString().replaceAll("[$,]", "") );
                    table.getModel().setValueAt("1", i, 5); 
                    table.getModel().setValueAt(DecimalFormat.
                            getCurrencyInstance(localeUSFormat).format(valueSP), i, 6);   
                }
            }          
        });         
        gridcons.insets = new Insets(10,0,0,0);
        gridcons.gridx = 1;
        gridcons.gridy = 3;
        gridcons.gridwidth = 1;
        gridbag.setConstraints(buttonResetQuantities, gridcons);
        panelButtons.add(buttonResetQuantities);

        // Create button for closing the window and releasing resources
        buttonExit = new JButton("Exit");
        buttonExit.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e)
            {                
                System.exit(0);
            }
        }); 
        gridcons.insets = new Insets(5,0,0,0);
        gridcons.gridx = 0;
        gridcons.gridy = 5;
        gridcons.gridwidth = 2;
        gridbag.setConstraints(buttonExit, gridcons);
        panelButtons.add(buttonExit);    

    } // -> createButtons()


    // Filters the JTable based on user input
    private void tableFilter() {
        RowFilter<DefaultTableModel, Object> tableRowFilter;// = null;
        // If current expression doesn't parse, don't update
        try {
            tableRowFilter = RowFilter.regexFilter("(?i)" + textFilterBox.
                    getText(), 0, 1, 2);
        } catch (java.util.regex.PatternSyntaxException e) {
            return;
          }
        sorter.setRowFilter(tableRowFilter);       
    } // -> tableFilter

    // Method that creates the JTable
    public void createTable() {

        // Create listener for selecting all text when a text field gains focus
        KeyboardFocusManager.getCurrentKeyboardFocusManager()
        .addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() {
        @Override
           public void propertyChange(final PropertyChangeEvent e) {
               if (e.getNewValue() instanceof JTextField) {
                   SwingUtilities.invokeLater(new Runnable() {
                       @Override
                       public void run() {
                           JTextField textField = (JTextField)e.getNewValue();
                           textField.selectAll();
                       }
                   });
               }
           }
        });

        String[] columnNames = {"Model", "Description", "GPL", "Discount", "SP",
                "Quantity", "Total"};

        Object[][] data = {
            {"MR16", "desc1", "$649.00", "0%", "$649.00", new Integer(1), "$649.00"},
            {"MR24", "desc2", "$1,199.00", "0%", "$1,199.00", new Integer(1), "1,199.00"},
            {"MR62", "desc3", "$699.00", "0%", "$699.00", new Integer(1), "$699.00"},
            {"MR66", "desc4", "$1,299.00", "0%", "$1,299.00", new Integer(1), "$1,299.00"},
            {"MX80", "desc5", "$1,995.00", "0%", "$1,995.00", new Integer(1), "$1,995.00"},
            {"MX90", "desc6", "$3,995.00", "0%", "$3,995.00", new Integer(1), "$3,995.00"},
            {"MX400", "desc7", "$15,995.00", "0%", "$15,995.00", new Integer(1), "$15,995.00"},
            {"MX600", "desc8", "$31,995.00", "0%", "$31,995.00", new Integer(1), "$31,995.00"},
            {"MS22-HW", "desc9", "$1,999.00", "0%", "$1,999.00", new Integer(1), "$1,999.00"},
            {"MS42-HW", "desc10", "$3,499.00", "0%", "$3,499.00", new Integer(1), "$3,499.00"},

        };

        // Create the TableModel and populate it  
        tableModel = new DefaultTableModel(data, columnNames) {
            Class [] classes = {String.class, String.class, String.class,
                String.class, String.class, int.class, String.class, Boolean.class};                
            @Override
            public Class getColumnClass(int column) {
                return classes[column];
            }
        };

        // Create a JTable and populate it with the content of the TableModel
        table = new JTable(tableModel) {
            @Override
            public boolean isCellEditable(int row, int column) {
                if (column == 0 || column == 1 || column == 2 || column == 4 ||
                        column == 6) {
                    return false;
                }
                return true;
            }
        };

        // This sorter is used for text filtering
        sorter = new TableRowSorter<>(tableModel);
        for (int column = 3; column < 6; column++) {
            sorter.setSortable(column, false);
        }
        table.setRowSorter(sorter);

        columnTotal= table.getColumnModel().getColumn(6);
        columnTotal.setPreferredWidth(100);

        // Filter user input in the quantity text field to only allow digits
        discountField =new JTextField();
        discountField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e)
            {
                if(!Character.isDigit(e.getKeyChar()) && e.getKeyChar() !=KeyEvent.VK_BACK_SPACE) {
                    discountField.setEditable(false);
                    discountField.setBackground(Color.WHITE);
                } else {
                    discountField.setEditable(true);
                }
            }
        });

        // Set the text field to the cells of the quantity column 
        columnQuantity = table.getColumnModel().getColumn(5);
        columnQuantity.setCellEditor(new DefaultCellEditor (discountField));

        // Filter user input in the discount text field to only allow digits
        quantityField =new JTextField();
        quantityField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e)
            {
                if(!Character.isDigit(e.getKeyChar()) && e.getKeyChar() !=KeyEvent.VK_BACK_SPACE) {
                    quantityField.setEditable(false);
                    quantityField.setBackground(Color.WHITE);
                    //JOptionPane.showMessageDialog(null,"Only digit input is allowed!");
                } else {
                    quantityField.setEditable(true);
                }
            }
        });

        // Set the text field to the cells of the quantity column 
        columnDiscount = table.getColumnModel().getColumn(3);
        columnDiscount.setCellEditor(new DefaultCellEditor(discountField));

        // Create an US number format
        localeUSFormat = Locale.US;
        decimalFormatUS = (DecimalFormat) DecimalFormat.getInstance(localeUSFormat);
        decimalFormatUS.setMaximumFractionDigits(2);

        // Create abstract action which listens for changes made in the JTable  
        Action actionTableListener = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {

                TableCellListener tcl = (TableCellListener)e.getSource();
                // Get the current row and column index of the table
                selectedRowIndex = tcl.getRow();
                selectedColumnIndex = tcl.getColumn();  
                TableModel model = tcl.getTable().getModel();

                // Have a string variable check for null cell value 
                checkForNull = model.getValueAt(selectedRowIndex,selectedColumnIndex).toString();

                // Change the discounted and total price values 
                if (selectedColumnIndex == 3) {

                    // Check if the discount value is null and replace with 
                    // last used value if true                   
                    if (checkForNull.equals("")) {
                        model.setValueAt(backupDiscount + "%",selectedRowIndex, selectedColumnIndex);
                        return;
                    }

                    // Get the discount value and replace the '%' with nothing
                    valueDiscount = new BigDecimal(( model
                            .getValueAt(selectedRowIndex,selectedColumnIndex)
                            .toString().replaceAll("[%]","") ));
                    // 
                    model.setValueAt(valueDiscount + "%",selectedRowIndex, selectedColumnIndex);

                    // Check if the discount value is greater than 100
                    if ( (valueDiscount.compareTo(new BigDecimal(100)) == 1 ) ) {
                        model.setValueAt(backupDiscount + "%",selectedRowIndex, selectedColumnIndex);
                        JOptionPane.showMessageDialog(null,"Discount cannot be more than 100%.");
                    } else {
                        backupDiscount = valueDiscount;
                        valueDiscount = valueDiscount.divide(new BigDecimal(100)
                                , 2, BigDecimal.ROUND_HALF_EVEN);

                        // Calculate SP and Total values based on the discount input
                        valueGPL = new BigDecimal( ( model
                                .getValueAt(selectedRowIndex,selectedColumnIndex - 1)
                                .toString().replaceAll("[$,]","") ) );
                        // Get the quantity value
                        valueQuantity = Integer.parseInt( ( model
                                .getValueAt(selectedRowIndex,selectedColumnIndex + 2)
                                .toString() ) );
                        // Calculate the new discount value
                        resultDiscount = valueGPL.multiply(valueDiscount, 
                                new MathContext(BigDecimal.ROUND_HALF_EVEN));
                        // Calculate the new SP value
                        resultSP = valueGPL.subtract(resultDiscount, 
                                new MathContext(BigDecimal.ROUND_HALF_EVEN));
                        // Calculate the new result value
                        resultTotal = resultSP.multiply(new BigDecimal(valueQuantity), 
                                new MathContext(BigDecimal.ROUND_HALF_EVEN));
                        // Display the new SP value
                        model.setValueAt(DecimalFormat.getCurrencyInstance(localeUSFormat)
                                .format(resultSP),selectedRowIndex, selectedColumnIndex + 1);
                        // Display the new Total value
                        model.setValueAt(DecimalFormat.getCurrencyInstance(localeUSFormat)
                                .format(resultTotal),selectedRowIndex, selectedColumnIndex + 3);
                      }
                }
                // Change the total price values based on the quantity column 
                if (selectedColumnIndex == 5) {
                    // Check if the quantity value is null and replace with 
                    // last used value if true
                    if (checkForNull.equals("")) {
                        model.setValueAt(backupQuantity,selectedRowIndex, selectedColumnIndex);
                        return;
                    }

                    // Change total price value based on the quantity column
                    resultSP = new BigDecimal( ( model.
                            getValueAt(selectedRowIndex,
                            selectedColumnIndex - 1).toString().replaceAll("[$,]","") ) );
                    valueQuantity = Integer.parseInt( ( model.getValueAt(selectedRowIndex,
                            selectedColumnIndex).toString() ) );

                    // Check if the value quantity is over a certain limit
                    if (valueQuantity <= 0 || valueQuantity >= 999999) {
                        model.setValueAt(backupQuantity,selectedRowIndex, selectedColumnIndex);
                        JOptionPane.showMessageDialog(null,"Quantity value is too high or invalid!");
                    } else {

                        // If the value is under the limit: backup the new quantity
                        // value, calculate the new total value and display it
                        backupQuantity = valueQuantity;
                        resultTotal = resultSP.multiply(new BigDecimal(valueQuantity),
                                new MathContext(BigDecimal.ROUND_HALF_EVEN));
                        model.setValueAt(DecimalFormat.getCurrencyInstance(localeUSFormat)
                                .format(resultTotal), selectedRowIndex, selectedColumnIndex + 1);
                      }
                } 

            }
        }; // -> AbstractAction() 

        tableCellListener = new TableCellListener(table, actionTableListener);
        table.setPreferredScrollableViewportSize(table.
               getPreferredSize());       
        table.setRowHeight(22);

        setVisibleRowCount(table,10);

        table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
        table.setFillsViewportHeight(true);
        table.getTableHeader().setReorderingAllowed(false);
        table.getTableHeader().setResizingAllowed(false);
        panelTable.add(new JScrollPane(table));
    } // -> createTable() 

    // Method to display a fixed number of rows in the JTable viewport
    public static void setVisibleRowCount(JTable table, int rows){ 
        int height = 0; 
        for(int row=0; row<rows; row++) {
            height += table.getRowHeight(row);
        } 
        table.setPreferredScrollableViewportSize(new Dimension( 
            table.getPreferredScrollableViewportSize().width, height )); 
     }

      // Create and display the contents of the frame
      public static void showGUI() {
        // Disable boldface controls
        UIManager.put("swing.boldMetal", Boolean.FALSE);

        // Create the frame
        frameTableCellChange = new JFrame("Table frame");
        frameTableCellChange.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     
        frameTableCellChange.addWindowListener(new WindowAdapter() {
            @Override
                public void windowClosing(WindowEvent we) {
                System.exit(0);
            }
        });

        // Create and set up the content pane.
        TableCellChange newContentPane = new TableCellChange();
        newContentPane.setOpaque(true); //content panes must be opaque
        frameTableCellChange.setContentPane(newContentPane);

        // Arrange and display the window.
        frameTableCellChange.pack(); //must be called first 
        frameTableCellChange.setLocationRelativeTo(null); //center window
        frameTableCellChange.setResizable(false); 
        frameTableCellChange.setVisible(true);        
      } //-> showQueryResultGUI() 

   public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
         try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.
                    UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException |
                IllegalAccessException |
                javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(TableCellChange.class.getName()).
                    log(java.util.logging.Level.SEVERE, null, ex);
        }
        // Display the frame and it's contents      
        TableCellChange.showGUI();
            }
        });
    } //-> main(String[] args)      

} //-> TableCellChange class

EDIT This class was created by Rob Camick (a.k.a. camickr), all credits go to him for creating this awesome piece of code. Only comments were removed from the code in order to respect the character limit.

TableCellListener class

import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Action;
import javax.swing.JTable;
import javax.swing.SwingUtilities;

/*
 *  This class listens for changes made to the data in the table via the
 *  TableCellEditor. When editing is started, the value of the cell is saved
 *  When editing is stopped the new value is saved. When the old and new
 *  values are different, then the provided Action is invoked.
 *  The source of the Action is a TableCellListener instance.
 */
public class TableCellListener implements PropertyChangeListener, Runnable {
    private JTable table;
    private Action action;

    private int row;
    private int column;
    private Object oldValue;
    private Object newValue;

    public TableCellListener(JTable table, Action action) {
        this.table = table;
        this.action = action;

        this.table.addPropertyChangeListener(this);
    }

    private TableCellListener(JTable table, int row, int column, Object oldValue, Object newValue) {
        this.table = table;
        this.row = row;
        this.column = column;
        this.oldValue = oldValue;
        this.newValue = newValue;
    }

    public int getColumn() {
        return column;
    }

    public Object getNewValue() {
        return newValue;
    }

    public Object getOldValue() {
        return oldValue;
    }

    public int getRow() {
        return row;
    }

    public JTable getTable() {
        return table;
    }
    @Override
    public void propertyChange(PropertyChangeEvent e) {  
        if ("tableCellEditor".equals(e.getPropertyName())) {
            if (table.isEditing()) {
                processEditingStarted();
            } else {
                processEditingStopped();
            }
        }
    }

    private void processEditingStarted() {
        SwingUtilities.invokeLater(this);
    }
    @Override
    public void run() {
        row = table.convertRowIndexToView(table.getEditingRow());
        row = table.getEditingRow();

        column = table.convertColumnIndexToModel(table.getEditingColumn());

        oldValue = table.getModel().getValueAt(row, column);
        newValue = null;
    }

    private void processEditingStopped() {
        newValue = table.getModel().getValueAt(row, column);   
        if (!newValue.equals(oldValue)) {

            TableCellListener tcl = new TableCellListener(
                getTable(), getRow(), getColumn(), getOldValue(), getNewValue());

            ActionEvent event = new ActionEvent(
                tcl,
                ActionEvent.ACTION_PERFORMED,
                "");
            action.actionPerformed(event);
        }
    }
}

I understand that when filtering a table the indexes of the table view change and must be synchronized with the indexes of the underlying model. How can that be done in order for the filtered table to work?

Ethiop answered 20/4, 2013 at 16:16 Comment(0)
C
5

You implemented the Action for the TableCellListener incorrectly. You can't use the selected row/colum because those values are in the Table view. The TableCellListener works on the model.

Check out the example Action provided with Table Cell Editor. To get the row/column that was changed you must reference the TableCellListener itself.

Edit:

Here is my simple text example. When you change the "Price", the "Price Change" and "Value" columns are automatically updated.

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

public class TableCellListenerTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI()
    {
        String[] columnNames = {"Stock", "Shares", "Price", "Price Change", "Value"};
        Object[][] data =
        {
            {"IBM",    new Integer(100),  new Double(85),  new Double(0), new Double(8500)},
            {"Apple",  new Integer(300),  new Double(30),  new Double(0), new Double(9000)},
            {"Sun",    new Integer(1500), new Double(5),   new Double(0), new Double(7500)},
            {"Google", new Integer(100),  new Double(100), new Double(0), new Double(10000)}
        };

        DefaultTableModel model = new DefaultTableModel(data, columnNames)
        {
            public Class getColumnClass(int column)
            {
                return getValueAt(0, column).getClass();
            }

            public boolean isCellEditable(int row, int column)
            {
                return column == 2;
            }
        };

        JTable table = new JTable(model);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane(table);

        //  Add a sorter

        TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<DefaultTableModel>(model);
        table.setRowSorter(sorter);

        //  Filter

        try
        {
            RowFilter<DefaultTableModel, Object> rf = RowFilter.regexFilter("l", 0);
            sorter.setRowFilter(rf);
        }
        catch (java.util.regex.PatternSyntaxException e) {}

        Action action = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                TableCellListener tcl = (TableCellListener)e.getSource();
                int column = tcl.getColumn();

                if (column == 2)
                {
                    int row = tcl.getRow();
                    double oldPrice = ((Double)tcl.getOldValue()).doubleValue();
                    double newPrice = ((Double)tcl.getNewValue()).doubleValue();
                    TableModel model = tcl.getTable().getModel();

                    double priceChange = new Double(newPrice - oldPrice);
                    model.setValueAt(priceChange, row, 3);

                    double shares = ((Integer)model.getValueAt(row, 1)).doubleValue();
                    Double value = new Double(shares * newPrice);
                    model.setValueAt(value, row, 4);
                }
            }
        };

      TableCellListener tcl = new TableCellListener(table, action);

        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("Table Cell Listener");
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.add( scrollPane );
        frame.setSize(400, 160);
        frame.setLocationRelativeTo( null );
        frame.setVisible(true);
    }
}
Centennial answered 20/4, 2013 at 20:15 Comment(5)
Thank you for responding camickr. I wanted to credit you for the TableCellListener class but I forgot the link to your blog and I apologize for not doing this earlier. This works perfect on an unfiltered table. I know this might be too much from you, but can you show me what exactly to add/change in the code in order for the action to work properly for the filtered table? I am still confused about the index conversion thing. I also hope this post will help others with the same issue. Cheers!Ethiop
can you show me what exactly to add/change in the code in order for the action to work properly for the filtered table? - No, because I don't know what you are attempting to do. I am still confused about the index conversion thing. - you don't need to worry about the conversion. The row/column are already converted for you. Just use the getColumn() and getRow() method of the TableCellListener class. The blog entry shows you how to do this. I also added my simple test code. Don't use the table.getSelectedXXX(...) methods.Centennial
I've changed the following: selectedRowIndex = table.getSelectedRow(); selectedColumnIndex = table.getSelectedColumn(); with TableCellListener tcl = (TableCellListener)e.getSource(); selectedRowIndex = tcl.getRow(); selectedColumnIndex = tcl.getColumn(); They work fine on the table when unfiltered but when I filter it and for example only choose the third row by inputting in the text box "MR62" the results are the same as in the second image in my post. The same applies when I sort a column of the table. Am I still doing it wrong?Ethiop
Did my example work? My example does both sorting and filtering. All updates should be done directly to the model since the row/column is relative to the model. Don't use table.setValueAt().Centennial
I've looked closer at your code and made changes to my class. The table now correctly changes the values when filtered and/or sorted. I've edited my original post to reflect the changes. I also credited you for the TableCellListener class. Thank you again for all your patience in helping me solve this issue.Ethiop
M
5

You may need to do the conversion from view to model. Take a look at Sorting and Filtering part of How to Use Tables tutorial:

When a table uses a sorter, the data the users sees may be in a different order than that specified by the data model, and may not include all rows specified by the data model. The data the user actually sees is known as the view, and has its own set of coordinates. JTable provides methods that convert from model coordinates to view coordinates — convertColumnIndexToView and convertRowIndexToView — and that convert from view coordinates to model coordinates — convertColumnIndexToModel and convertRowIndexToModel.

EDIT: convert from view (table) to model:

Replace these rows:

row = table.convertRowIndexToView(table.getEditingRow());
row = table.getEditingRow();

With:

row = table.convertRowIndexToModel(table.getEditingRow());
Mcgary answered 20/4, 2013 at 16:43 Comment(8)
Thanks for the response. I've tried that but it did not work. Can you show me where to place those methods in my presented code?Ethiop
@Ethiop I cannot reproduce the problem.Mcgary
Did the program run successfully? If it did, type in the text box one of the values from the Model column then try changing a value from the Discount or Quantity column.Ethiop
It isn't working. Now when I change a Discount value it reverts to 0% and changes the SP and Total values to the value of the first entry of the table and when I change a Quantity value it changes the Total value to the value of the first entry of the table.Ethiop
@Ethiop selectedRowIndex = table.getSelectedRow(); in actionTableListener also needs to be translated to a model index.Mcgary
@Ethiop actually, actionTableListener has another problem. You get values using table.getModel().getValueAt but you set them with table.setValueAt. There is index mismatch. Either use model everywhere but also translate the index, or use table.getValueAt() and table.setValueAt().Mcgary
+1 for identifying model/view inconsistencies. The TableCellListener class was designed to work easily with the model. This class provides methods for getting the row/column values of the model that were changed. The Action should reference these values. See my answer for a link to the actual blog.Centennial
@Centennial +1 for Table Cell ListenerMcgary
C
5

You implemented the Action for the TableCellListener incorrectly. You can't use the selected row/colum because those values are in the Table view. The TableCellListener works on the model.

Check out the example Action provided with Table Cell Editor. To get the row/column that was changed you must reference the TableCellListener itself.

Edit:

Here is my simple text example. When you change the "Price", the "Price Change" and "Value" columns are automatically updated.

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

public class TableCellListenerTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI()
    {
        String[] columnNames = {"Stock", "Shares", "Price", "Price Change", "Value"};
        Object[][] data =
        {
            {"IBM",    new Integer(100),  new Double(85),  new Double(0), new Double(8500)},
            {"Apple",  new Integer(300),  new Double(30),  new Double(0), new Double(9000)},
            {"Sun",    new Integer(1500), new Double(5),   new Double(0), new Double(7500)},
            {"Google", new Integer(100),  new Double(100), new Double(0), new Double(10000)}
        };

        DefaultTableModel model = new DefaultTableModel(data, columnNames)
        {
            public Class getColumnClass(int column)
            {
                return getValueAt(0, column).getClass();
            }

            public boolean isCellEditable(int row, int column)
            {
                return column == 2;
            }
        };

        JTable table = new JTable(model);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane(table);

        //  Add a sorter

        TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<DefaultTableModel>(model);
        table.setRowSorter(sorter);

        //  Filter

        try
        {
            RowFilter<DefaultTableModel, Object> rf = RowFilter.regexFilter("l", 0);
            sorter.setRowFilter(rf);
        }
        catch (java.util.regex.PatternSyntaxException e) {}

        Action action = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                TableCellListener tcl = (TableCellListener)e.getSource();
                int column = tcl.getColumn();

                if (column == 2)
                {
                    int row = tcl.getRow();
                    double oldPrice = ((Double)tcl.getOldValue()).doubleValue();
                    double newPrice = ((Double)tcl.getNewValue()).doubleValue();
                    TableModel model = tcl.getTable().getModel();

                    double priceChange = new Double(newPrice - oldPrice);
                    model.setValueAt(priceChange, row, 3);

                    double shares = ((Integer)model.getValueAt(row, 1)).doubleValue();
                    Double value = new Double(shares * newPrice);
                    model.setValueAt(value, row, 4);
                }
            }
        };

      TableCellListener tcl = new TableCellListener(table, action);

        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("Table Cell Listener");
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.add( scrollPane );
        frame.setSize(400, 160);
        frame.setLocationRelativeTo( null );
        frame.setVisible(true);
    }
}
Centennial answered 20/4, 2013 at 20:15 Comment(5)
Thank you for responding camickr. I wanted to credit you for the TableCellListener class but I forgot the link to your blog and I apologize for not doing this earlier. This works perfect on an unfiltered table. I know this might be too much from you, but can you show me what exactly to add/change in the code in order for the action to work properly for the filtered table? I am still confused about the index conversion thing. I also hope this post will help others with the same issue. Cheers!Ethiop
can you show me what exactly to add/change in the code in order for the action to work properly for the filtered table? - No, because I don't know what you are attempting to do. I am still confused about the index conversion thing. - you don't need to worry about the conversion. The row/column are already converted for you. Just use the getColumn() and getRow() method of the TableCellListener class. The blog entry shows you how to do this. I also added my simple test code. Don't use the table.getSelectedXXX(...) methods.Centennial
I've changed the following: selectedRowIndex = table.getSelectedRow(); selectedColumnIndex = table.getSelectedColumn(); with TableCellListener tcl = (TableCellListener)e.getSource(); selectedRowIndex = tcl.getRow(); selectedColumnIndex = tcl.getColumn(); They work fine on the table when unfiltered but when I filter it and for example only choose the third row by inputting in the text box "MR62" the results are the same as in the second image in my post. The same applies when I sort a column of the table. Am I still doing it wrong?Ethiop
Did my example work? My example does both sorting and filtering. All updates should be done directly to the model since the row/column is relative to the model. Don't use table.setValueAt().Centennial
I've looked closer at your code and made changes to my class. The table now correctly changes the values when filtered and/or sorted. I've edited my original post to reflect the changes. I also credited you for the TableCellListener class. Thank you again for all your patience in helping me solve this issue.Ethiop

© 2022 - 2024 — McMap. All rights reserved.