Java Tips Weblog

  • Blog Stats

    • 2,569,184 hits
  • Categories

  • Archives

Table Cell Listener

Posted by Rob Camick on June 7, 2009

A TableModelListener is used to listen for changes to a TableModel. Relying solely on the TableModelListener can have potential drawbacks. In particular:

  • in some cases, a TableModelEvent is fired even though nothing has changed. This would happen when you place a table cell in editing mode but then simply tab (or click) off the cell without changing anything.
  • in all cases, when the event is received you only have access to the current state of the TableModel. This means that you know what has changed, but you don’t know what it was changed from.

In the first case this may result in extra processing being repeated, which may or may not be a big deal depending on the type of processing required. The second case is obviously a bigger problem when you have processing that needs to know the previous value of the cell. The information simply isn’t available.

There is a simple solution to handle both concerns. However, the solution discussed here is only applicable to updates made to the TableModel through a JTable via a table cell editor. The solution identifies when an actual change in the cell data occurs and then it invokes custom processing. The TableCellListener class implements the solution.

Lets take a quick look at how the TableCellListener works. A JTable fires a PropertyChangeEvent when a cell:

  • starts editing – therefore we can save the current value of the cell before it is changed (the old value)
  • finishes editing – therefore we can retrieve the current value of the cell (the new value). In addition, we can now compare the old and new values and invoke processing when they are different.

When creating a TableCellListener you need to provide a JTable and an Action as the parameters. The table is specified so we can listen for the above PropertyChangeEvents. The Action will be invoked when the values have changed. The TableCellListener will be the source of the Action so you will be able to access all the information about the changed cell value.

Test the TableCellListener by adding it to any table using code like the following:

Action action = new AbstractAction()
{
    public void actionPerformed(ActionEvent e)
    {
        TableCellListener tcl = (TableCellListener)e.getSource();
        System.out.println("Row   : " + tcl.getRow());
        System.out.println("Column: " + tcl.getColumn());
        System.out.println("Old   : " + tcl.getOldValue());
        System.out.println("New   : " + tcl.getNewValue());
    }
};

TableCellListener tcl = new TableCellListener(table, action);

In summary, the TableCellListener class can be used as a replacement for a TableModelListener in the special cases described above. Instead of implementing a TableModelListener you would implement an Action.

Get The Code

TableCellListener.java

81 Responses to “Table Cell Listener”

  1. LD said

    Hey Rob,

    I implemented your TableCellListener class into my application and it works well. But I was wondering if it is possible to use the TableCellListener class to bring what I want to do to a higher level.

    The thing is, I wanted to restrict the users only to enter positive values in the table cells, so I looked around in your blog and found this class. Now, after implementing the TableCellListener class, what my application does, concerning the tables, is: when the user edits a cell, and he enters a negative value, then hits tab or enter, the cell goes back to its old value.

    However, what I would really want is: instead of letting the user enter all the unwanted characters (-~!@#$% etc…), the application should consume all of that – meaning not letting the user enter those character at all in the first place.

    Can we do that using your TableCellListener class, or would recommend something else?

    Regards,
    LD

    • Rob Camick said

      Editing data is not part of the functionality of the TableCellListener. To do that you need to create a custom cell editor. The Swing tutorial explains the concept. You can use a JFormattedTextField as the editor or a JTextField with a DocumentFilter.

  2. Chintan said

    Rob,
    I liked the TableCellListener example that you have put up. But my environment is Java 1.5 so in effect I cannot use the utility since “convertRowIndexToModel” is not available in 1.5. Is there any workaround?

    Thanks for all the help.

    ~ Chintan

    • Rob Camick said

      Well, it means you aren’t using any of the sorting or filtering features that where added in JDK6, so you should be able to just use the editing row directly.

      • Chintan said

        .Yeah, I figured that out & did this.

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

        Thanks for the prompt response. If you had be kind enough to let me know if it is possible to disable the table cell listener for certain columns then that would be great. I tried this,

        if (“tableCellEditor”.equals(e.getPropertyName())) {
        if (table.getEditingColumn() != columnIndexToBeIgnored) {
        if (table.isEditing()) {


        But this doesnt seem to work. Also there is amoment at which the table.editifingColumn becomes -1 even though I am in a specific column.

        Thanks again.

      • Rob Camick said

        The comment in processEditingStarted() explains why you have this problem with the column.

        The TableCellListener was designed to replace the TableModelListener, so this means the event is always fired. So your custom Action should filter out the columns you want to ignore.

  3. Manish said

    Hi,
    I am working on table model with combobox in jtable (in column 2).
    How can i get the selected item for the selected combobox?

    Thanks in advance.

  4. Mike B. said

    Wow this is really helpful.
    But I was wondering how can I not include the cells in the first column of my table?

    • Rob Camick said

      The event is fired for all rows/columns. If you don’t care about the first column then you just ignore it. At the beginning of your Action you just add something like:

      if (tcl.getColumn() == 0) return;

  5. alvaro said

    excelent code it was really helpfull and simple to use

  6. Jet said

    you’re a life saver man… thank you!

  7. Anonymous said

    Very good !!!

  8. Jeff said

    THANK YOU ! I know this is a couple years old now but I’ve spent 3 days trying to implement this functionality using listselections and listeners and whatnot with only partial success.

    I just had to change …
    Action action = new AbstractAction()
    to …
    AbstractAction action = new AbstractAction()

    (using netbeans)

    But it worked like a charm! Should be part of core!

  9. Anonymous said

    Not sure why it doesn’t work for you when using Netbeans. AbstractAction implements the Action interface so you should be able to use the code as given in the example above. Anyway, glad the class does what you need.

    Not sure why this comment is displayed with “Anonymous” as the user. Something is wrong with the comments.

  10. d3viam said

    In:

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

    Line: this.table.addPropertyChangeListener( this );
    Netbeans comment: Leaking this in constructor

    • Rob Camick said

      Not sure what that comment means, or what the suggested solution would be.

      • Viktor said

        This may mean that if there is addPropertyListener, there should be corresponding removePropertyListener else where to avoid memory leaking.

        P.S. Thanks for this nice solution anyway.

  11. Amit said

    Excellent !

    Just what I wanted :)

    Thank you!!

  12. Mubashir said

    This requires pressing enter key, what i require is that when the value is changed the event automatically fires.

    • Rob Camick said

      This class has nothing to do with that type of functionality. If you want to know when data is changed in the editor then you need to add a DocumentListener to the editor.

      • Mubashir said

        Can you tell me how can i use The TableCellListener class with document listener event for the jtable. As I have written many events in my code for Action Event which uses your TableCellListener classs.

      • Mubashir said

        Thanks For Your Reply , Actually i expicitly called Table Click Event On My Save Button Click Event

        MouseEvent me = new MouseEvent(tblDetailInfo, 0, 0, 0, 100, 100, 1, false);
        for(MouseListener ml: tblDetailInfo.getMouseListeners()){
        ml.mouseClicked(me);
        }
        which solves my problem.

  13. Kasun Bandara said

    Nice work budy.
    How can i fire event even data hasn’t changed in cell ?

  14. Anonymous said

    Excellent code example for cell data change listener
    Thanks

  15. Anonymous said

    Works great! Thanks

  16. Excellent!
    It works like magic.

  17. to prevent null pointer exceptions i had to do this:

    ...
    // Determine if data was changed
    boolean data_changed = false;
    if ( newValue == null ) {
    	data_changed = ( oldValue != null );
    }
    else if ( oldValue == null ) {
    	data_changed = ( newValue != null );
    }
    else {
    	data_changed = !newValue.equals( oldValue ); 
    }
    
    // The data has changed, invoke the supplied Action
    if ( data_changed ) {
    ...
    
    • Rob Camick said

      Not sure why you needed to do that. The supplied code works fine for me. That is I don’t get an Exception in the TableCellListener class when changing a cell that contains a null or when setting a cell to a null value. This is the behaviour I was going for.

      Yes, the code in the sample Action does result in an Exception being thrown. The code in the Action is not complete as it should check for nulls. An Exception is still thrown in the Action with your fix as well.

      • Josue said

        I did this :
        if(newValue==null ^ oldValue==null) if (! newValue.equals(oldValue)) {…
        and it is fixed, it’s the same logic proposed by Aleksandr Zaigrayev and works fine.

      • Shawn Lavelle said

        I have experienced this situation, and it’s always because oldValue wasn’t populated. Why might this occur and how can we prevent it? It also always seems to only apply to the first row in the table, not the rest, but I can’t verify that is *always* the case.

    • Luis said

      Aleksandr. I’m getting also NPE. Could you ellaborate where did you include such code? Thnaks.

  18. Sudheer said

    Excellent work Rob!!
    I have used this TableCellListener in my project. My table has three rows – first two rows has combobox, and third row has text field.
    And I am creating the two comboboxes dynamically with items when i am creating the rows.

    Now my problem is, when i change the value in first column combobox, the items in second column combobox should be changed accordingly.

    How to achieve this. I am struck here. because i have to get the row of the first combobox changed and update corresponding row second combobox.

    Could you please help me in this regard.

    Thanks!! in advance.

  19. Antonis said

    Why doesn’t it work if I implement my own AbstractTableModel , and use it as model of the table???

  20. gogalai said

    Thank you so much for this. Saved me lot of time.

  21. Anonymous said

    good code, you are talented.

  22. Anonymous said

    Thanks so much for the code, it works wonders. I have a question though, and perhaps its because I am fairly new to Swing GUI programming but how would I fire this programmatically?

    • Rob Camick said

      It wasn’t designed to be fired manually. I’m not sure why you would want to do this. But if you really want to try then take a look at the code from the processEditingStopped() method. You would need to create the TableCellListener, the ActionEvent and then invoke the ActionListener.

      • Anonymous said

        I agree it is a very odd use case. The basic gist is that I have codes in a JTable, I use your TableCellListener, to fire off a task to get a description of the code and update the table. However there are multiple versions and some codes are valid in one version and not in another. So when the user changes versions (through another control), I need to fire off the task and go get the description for the codes in the table, that is where the programmatic firing comes in.

        Anywho, you suggestion lead me in the right direction and I have it working now. Pretty obvious solution, not sure how I missed it in the first place.

        Thanks again for this!

  23. Anonymous said

    Hi Rob,

    Is there a way to make this fire on every key stroke? Or do you have another method for when something needs to fired on every key stroke?

    Thanks.

  24. Najib Mozahem said

    Hi. I tried using the TableCellListener class but I am having a problem. Everything seems to work fine if the user uses the mouse to navigate between cells and to change the data. if the user uses the TAB key then I get an error when the cursor moves to the last column of the table. This is the error:
    Exception in thread “AWT-EventQueue-0” java.lang.ArrayIndexOutOfBoundsException: -1
    The problem is that the listener is firing when the user presses the TAB key to move from column number 2 to the last column even though no changes are made. This does not happen with other columns. I would appreciate your help. Thanks.

  25. kumar said

    Please someone give me the example of how can i use these button on the click of the enter(keyEvent e) . Thanks in advance.

    • Rob Camick said

      What buttons?

      • kumar said

        Thanks for reply Rob
        I wanna know how can i implement the the keyListener event on the button of tableModel. actually i am populating the data from sqlight database to the jtable and in each cell i have shown a “edit” and “select” button, functionality of edit and select button are working fine when i click those button with mouse click event also when i press “space Bar” from keyboard it still works fine but i wanna know how can i do this thing(“edit” and “select”) with click of Enter/return button.

      • Rob Camick said

        This class has nothing to do with buttons.

  26. Shawn Lavelle said

    I love this class, but I’ve encountered a problem, and I think it stems from the reliance on PropertyChangeListener and what it responds to. Well, here’s the setup:

    The TableModel is a DefaultTableModel anonymously extended to override the getColumnClass to be that of { Object, Boolean, Boolean, and Object }. Otherwise, it’s a standard implementation. Boolean class renders as a jCheckbox by default. Clicking on the checkbox and changing its value does not always result in PropertyChangeListener firing.

    new javax.swing.table.DefaultTableModel(
                new Object [][] { },
                new String [] { "Name", "Display", "Activate", "Delete" } )
                {
                    Class[] types = new Class [] {
                        java.lang.Object.class, java.lang.Boolean.class, java.lang.Boolean.class, java.lang.Object.class
                    };
    
                    @Override
                    public Class getColumnClass(int columnIndex) {
                        return types [columnIndex];
                    }
                };
    

    Do you have any thoughts onto why this might be and what changes could be made to resolve the issue?

    Does this class still function properly in Java1.6/1.7/1.8+?

    Thanks for your time,
    ~ Shawn

    • Rob Camick said

      It still works for me in JDK7 on Windows 7. When I tested I also extended the DefaultTableModel with an annonymous inner class. I have no idea why the PropertyChangeListener doesn’t fire. The PropertyChangeListener is added to the JTable, not the TableModel and is invoked when the table starts/stops editing so a custom TableModel should have no affect.

      I am going on vacation in a couple of hours. The best I can do is give you my test code that I modified in an attempt to duplicate your situation:

      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", "Boolean"};
      		Object[][] data =
      		{
      			{null, null, null, null, null, null},
      			{"IBM",    new Integer(100),  new Double(85),  new Double(0), new Double(8500), Boolean.TRUE},
      			{"Apple",  new Integer(300),  new Double(30),  new Double(0), new Double(9000), Boolean.FALSE},
      			{"Sun",    new Integer(1500), new Double(5),   new Double(0), new Double(7500), Boolean.TRUE},
      			{"Google", new Integer(100),  new Double(100), new Double(0), new Double(10000), Boolean.FALSE}
      		};
      
      		DefaultTableModel model = new DefaultTableModel(data, columnNames)
      		{
      			public Class getColumnClass(int column)
      			{
      				return getValueAt(1, column).getClass();
      			}
      
      			public boolean isCellEditable(int row, int column)
      			{
      				return true;
      			}
      		};
      
      		JTable table = new JTable(model);
      		table.setPreferredScrollableViewportSize(table.getPreferredSize());
          	JScrollPane scrollPane = new JScrollPane(table);
      
          	Action action = new AbstractAction()
          	{
          		public void actionPerformed(ActionEvent e)
          		{
          			TableCellListener tcl = (TableCellListener)e.getSource();
         				int column = tcl.getColumn();
      
      				System.out.println(tcl.getOldValue() + " : " + tcl.getNewValue());
      	   		}
          	};
      
      		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);
          }
      }
      
  27. Trevor Sandy said

    Thanks for this excellent sample. Here are a few nice enhancements:

    – Allow user to “rollback” a change.

    …Add this method to TableCellListener.java:

    	/*
    	 * Rollback the change 
    	 */
    	public void rollback()
    	{
    		table.getModel().setValueAt(oldValue, row, column);
    	}
    

    ..Update action like this:

    	Action action = new AbstractAction()
    	{
    	        /**
    		 * 
    		 */
    		private static final long serialVersionUID = 1L;
    
    		@Override
    		public void actionPerformed(ActionEvent e)
    	    {
    	        TableCellListener tcl = (TableCellListener)e.getSource();
    	        
    	        int response = JOptionPane.showConfirmDialog(table, "The following change will be committed:\n " +
    	        		"New Value : " + tcl.getNewValue() + "\n" +
    	        		"Old Value : " + tcl.getOldValue() + "\n" +
    	        		"Row       : " + tcl.getRow() + "\n" +
    	        		"Column    : " + tcl.getColumn(), 
    					"Confirm replace", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
    			if (response != JOptionPane.YES_OPTION) 
    				tcl.rollback();
    	    }
    	};
    
    • Hi ! … I’m trying to implement something like an Oracle Rollback Segment. This code is pretty userful when I update columns or insert new rows, but if I delete rows then the the update columns go wrong (tcl.getRow changes itself if a delete occures in the middle). Do you have an idea about to get something like an unique rowId for updated rows?
      Thanks. Nestor.

  28. Gaurav said

    I wanted to put apply listeners to JButtons which I out in cell of JTables. Will u plz help me out??

  29. Charles said

    Why aren’t your blogs the first thing that shows in Google. I always find the solution here in your blogs.

  30. mentaali said

    Had a problem with a jTable, this TableCellListener really helped me out. There’s an issue though, when you enter a null value in a cell and press the ENTER key and then press the ESCAPE key afterwards, it throws a NullPointerException.

    Possible solution?

    Thanks.

    • Rob Camick said

      I can’t duplicate the problem based on your description. So you need to debug the problem yourself. You know the line causing the problem so figure out which variable is null and add a check for a null value. As far as I can tell my code already checks for null values so I would guess the problem is in your Action trying to access a null variable.

  31. And1 said

    thanks !!! its good !!

  32. Anonymous said

    I’d like to express my deepest thanks in this code! It really helped me a lot!

  33. Yuan said

    Thank you

  34. psb said

    This code is useful for me..this helped me to solved my problem..

  35. THANK YOU! I was doing my head in trying to understand propertyChange event calls with JTables in WindowsBuilder, Eclipse. Plugged this code and everything automagically worked! I can now go back to the hard work of building my app.

  36. Anonymous said

    Thanks for the help, worked perfectly!!

  37. Anonymous said

    Runs fine when the user clicks on the checkbox, but if the checkbox is set by the application the action is never fired (TableCellListener is not envoked). What am I missing?

  38. Hi ! … I’m trying to implement something like an Oracle Rollback Segment. This code is pretty userful when I update columns or insert new rows, but if I delete rows then the the update columns go wrong (tcl.getRow changes itself if a delete occurs in the middle). Do you have an idea about to get something like an unique rowId for updated rows?
    Thanks. Nestor.

    • Rob Camick said

      Sorry, I don’t even know why an event would be generated when you delete a row. This class should only generate an event when you are editing a cell and change a value. If you are in editing mode when you delete a row, then you should stop cell editing before you delete the row.

      • Hi Rob … thanks for your quick response !

        I don’t need an event fired after a delete. The idea is pretty simple: it is based on a HashMap(Integer, Object) and several buttons (insert, update, delete, commit and rollback). Each time I press insert or delete the HashMap adds an unique sequencial numeric value as index and and Object[] with the type of dml and the values (for insert only the new row number, for delete the rownumber and each field value). Each time a column is edited your TCL_Listener does the same (add to HashMap an index and an Object with the dml, numrow, numcol and oldvalue).

        Once I press commit button, it just cleans the HashMap. When I press rollback button, it reverse reads the HashMap doing the opposite dml (inserted row is deleted, deleted row is inserted and modified columns go back to the oldvalue it got from your TCL class).

        If I only do inserts only OR deletes only OR updates only OR a combination of inserts and updates, all goes OK. But if I do at same time updates and deletes, the updates lost the row# reference (for instance: I update some field of row#5 then I delete row#2 => now row#5 is the 4th; and when I do rollback the row#2 is inserted again (but not necesarily it will be row#2 again) and row#5, number gotten with tcl.getRow(), is not the correct one because of the delete => now is the #4, then it updates the wrong row).

        Is there a chance that tcl.getRow() returns a unique and immovable row identifier?

        Thanks in advance.

      • Rob Camick said

        What happens if you insert at the beginning of the table instead of the end? Now all the rows are offset so you can’t have an immovable row identifier. What happens if you delete the last row and then append a row at the end? Now you have two rows with the same identifier. This class wasn’t designed for this purpose. It just simple takes a simple snapshot before/after editing starts. It is not designed for undo/redo logic. You need to build your own undo/redo mechanism saving whatever data you feel is relevant.

      • You are totaly right, Rob. I badly chose the DefaultTableModel method to re-insert the deleted row => I used addRow instead of insertRow. Using insertRow(numRow, Object) I put the row at its original position, then the row number returned by tcl.getRow() matchs again. Now all is fine.
        Thanks you for your TCL_Listener class <= it is the hart of my JTable rollback segment.

  39. Shanshan Li said

    Hi, thanks really much for that. I came across a problem:
    int editingRow = table.getEditingRow();
    row = table.convertRowIndexToModel(editingRow);
    the editingRow is always -1 even though I am in a specific row.

    • Rob Camick said

      Correct because the table has finished editing when the listener is invoked. The listener can only get the new value once editing has stopped. You use the getRow() method of the listener to find out which row was edited as was demonstrated in the sample code in the ActionListener from above.

  40. shahulblog said

    Hi All. TableCellListener class is not there anymore . What is the alternative I could only see a TableCellEditor

  41. Subrat said

    i did not get to how to use it with my code,

    i added the TableCellEditor.java to my package, and the given code to createUi(), where i am passing the table instance like below

    Action action = new AbstractAction()
    {
        public void actionPerformed(ActionEvent e)
        {
            TableCellListener tcl = (TableCellListener)e.getSource();
            if(!tcl.getOldValue().equals(tcl.getNewValue())){
                rowsEdited.add(tcl.getrow());
                if(!updateBtn.isEnabled())
                    updateBtn.setEnabled(true);
           }
        }
    };
    
    TableCellListener tcl = new TableCellListener(table, action);
    

    but when there is a change in cell value the actionPerformed method is not being called.

    basically i want to capture the row in which the edit is being done, after comparing the old value and new value, other than this do i need to add the listener anywhere else.
    Please elaborate with an example, i am new to Swing, if i am able to use it, it will really be helpful.

    • Rob Camick said

      I did provide a simple example that just does a System.out.println(…) to make sure the Action is invoked. So you should first test using that Action before attempting to write a custom Action. Also, the Action will only be invoked when the old/new values are different. So the if statement you use should not be necessary.

Leave a reply to Anonymous Cancel reply