title: Issue after filtering JTable
body: <p>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.</p>
  
  <p>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.</p>
  
  <p><a href="https://i.stack.imgur.com/TtDSp.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/TtDSp.png" alt="Unfiltered table"></a>
  </p>
  
  <p>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.</p>
  
  <p><a href="https://i.stack.imgur.com/p00WL.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/p00WL.png" alt="Filtered table"></a>
  </p>
  
  <p>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.</p>
  
  <p><strong>EDIT</strong> I've changed the code of the class according to camickr's answer and now fully works.</p>
  
  <p>TableCellChange class</p>
  
  <pre><code>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&lt;DefaultTableModel&gt; 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();
             }
          });
      } // -&gt; 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();
                      }
                  }); // -&gt; 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 &lt; 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 &lt; 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);    
  
      } // -&gt; createButtons()
  
  
      // Filters the JTable based on user input
      private void tableFilter() {
          RowFilter&lt;DefaultTableModel, Object&gt; 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);       
      } // -&gt; 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&lt;&gt;(tableModel);
          for (int column = 3; column &lt; 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()) &amp;&amp; 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()) &amp;&amp; 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 &lt;= 0 || valueQuantity &gt;= 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);
                        }
                  } 
  
              }
          }; // -&gt; 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));
      } // -&gt; 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&lt;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);        
        } //-&gt; 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();
              }
          });
      } //-&gt; main(String[] args)      
  
  } //-&gt; TableCellChange class
  </code></pre>
  
  <p><strong>EDIT</strong> 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.</p>
  
  <p>TableCellListener class</p>
  
  <pre><code>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);
          }
      }
  }
  </code></pre>
  
  <p>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?</p>