过滤JTable后发出问题

时间:2013-04-20 16:16:22

标签: java swing jtable rowfilter

大家好日子,

我遇到了正在处理的程序问题。简要说明一下,我有一个包含多个列和行的JTable。特定列具有可编辑字段,当改变值时,其他列值根据输入的数据而改变。一切正常但是当我向JTable添加过滤器选项时,对可编辑列所做的更改将不会在应用过滤器后按预期更改其他列的值。我附上了几张图片来说明问题。

第一张图片显示未经过滤的表格正常工作。更改折扣列值将使输入折扣的百分比减少存储在GPL列中的相应价格,并显示在SP列的相应行中。更改数量列值将相应的SP列价格与输入的数量相乘,并显示在总计列的相应行中。

Unfiltered table http://i36.tinypic.com/2pq7r50.png

第二张图片显示过滤后的表格未按预期工作。更改“折扣”或“数量”列中的值不会更改预期的列。

Filtered table http://i33.tinypic.com/25hcr35.png

我添加了下面包含2个类的SSCCE代码。第一个是表本身,第二个是表的监听器。

编辑我根据camickr的回答更改了该类的代码,现在完全有效。

TableCellChange类

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

编辑这个班级是由Rob Camick(a.k.a。camickr)创建的,所有学分都归他所有,因为他创造了这个令人敬畏的代码。只有注释从代码中删除才能尊重字符限制。

TableCellListener类

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

据我所知,在过滤表时,表视图的索引会发生变化,并且必须与基础模型的索引同步。如何才能使过滤后的表工作?

感谢您阅读并度过美好的一天。 :)

2 个答案:

答案 0 :(得分:4)

您可能需要执行从视图到模型的转换。请查看如何使用表教程的Sorting and Filtering部分:

  

当表使用分拣机时,用户看到的数据可能位于a   不同于数据模型指定的顺序,也可能不是   包括数据模型指定的所有行。用户的数据   实际上看到被称为视图,并有自己的一套   坐标。 JTable提供了从模型转换的方法   坐标来查看坐标 - convertColumnIndexToView和   convertRowIndexToView - 从视图坐标转换为   模型坐标 - convertColumnIndexToModel和   convertRowIndexToModel。

编辑:从视图(表格)转换为模型:

替换这些行:

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

使用:

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

答案 1 :(得分:3)

您错误地为TableCellListener实现了Action。您无法使用所选的行/列,因为这些值位于“表”视图中。 TableCellListener适用于模型。

查看Table Cell Editor提供的示例操作。要获取已更改的行/列,必须引用TableCellListener本身。

编辑:

这是我的简单文字示例。当您更改“价格”时,“价格变动”和“价值”列会自动更新。

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