8 Replies Latest reply on Jun 29, 2011 11:40 AM by 871939

    How to Filter JTable Rows as Data Changes

    871939
      Hello,

      any helpful suggestions are welcome, I'm at a bit of a dead end at the moment.

      So I have a JTable backed by a table model. The data in the table is liable to change while the system is running, when it does some events are raised. The table model listens for the appropriate events so that it can work out which row in the table needs to be updated, I can then call through to
      int row = determineWhichRow( changedDataObject );
      fireTableRowsUpdated( row, row );
      This correctly causes the table row to update. Now the bit thats going wrong. The user is able to enable filters on the table, specifically on an attribute of the data in this table that changes. So when I fire the update event I need my row sorter (with the filters set) to re-sort the table. Having a look around there is a method on row sorter:
      getSortsOnUpdate()
      which from the javadoc I believed would create the behaviour I needed - assuming I told it to return true. Having tried this the functionality remains the same, as the data model changes, the values in the table update but the filters are not reapplied.

      Debugging through the jdk code to get an idea of what might be going on it seems that the filters are called and it determines that the changed row should be filtered out, but gets to a point where it decides that because the event from the table model was an update event it shouldn't call a resize and repaint method and nothing further seems to happen.

      I believe that simply re-setting the row sorter on the table would cause the desired effect but would be incredibly inefficient with a data model that can potentially change at any time. I can post the specific line numbers and jdk version if that is going to help anyone, but hopefully you'll all be able to give me an idea of what I might have missed.

      Thanks,

      Dan.
        • 1. Re: How to Filter JTable Rows as Data Changes
          871939
          Ok, so I've thrown together a quick demonstration of my problem. If you run the following, the idea is there is a filter on the table which should remove all rows where the boolean is set to false. You can modify the table model by selecting the row number from the combo, (de)selecting the check box and selecting to apply, the idea is the table will correctly update either removing or adding a row. It does partially update when you are removing a row but the same number of rows are retained, when it comes to adding a row back in nothing changes. The final button completely resets the sorter on the table and results in the correct appearance of the table but causes the entire table to be sorted/filtered which I do not want to do.

          Anyway, as said before any helpful suggestions are appreciated and thanks.
          package test;
          
          import java.awt.BorderLayout;
          import java.awt.event.ActionEvent;
          import java.awt.event.ActionListener;
          
          import javax.swing.DefaultComboBoxModel;
          import javax.swing.JButton;
          import javax.swing.JCheckBox;
          import javax.swing.JComboBox;
          import javax.swing.JFrame;
          import javax.swing.JPanel;
          import javax.swing.JTable;
          import javax.swing.RowFilter;
          import javax.swing.table.AbstractTableModel;
          import javax.swing.table.TableModel;
          import javax.swing.table.TableRowSorter;
          
          public class JTableFiltering extends JFrame
          {
             private JTable table;
             private TableRowSorter< TableModel > sorter;
             private BooleanModel tblModel;
             
             private JComboBox combo;
             private JCheckBox checkBox;
             
             private class BooleanModel extends AbstractTableModel
             {
                public Boolean[] model;
                
                public BooleanModel()
                {
                   this.model = new Boolean[] { true, true, true, true, true };
                }
                
                @Override
                public int getColumnCount()
                {
                   return 2;
                }
                
                @Override
                public int getRowCount()
                {
                   return model.length;
                }
                
                @Override
                public Object getValueAt( int row, int column )
                {
                   switch( column ) {
                      case 0:
                         return row;
                      case 1:
                         return model[ row ];
                   }
                   return null;
                }
                
                @Override
                public boolean isCellEditable( int rowIndex, int columnIndex )
                {
                   return false;
                }
             }
             
             public JTableFiltering()
             {
                super( "Why won't you filter?" );
                
                JPanel contentPane = new JPanel( new BorderLayout() );
                setContentPane( contentPane );
           
                tblModel = new BooleanModel();
                table = new JTable( tblModel );
                contentPane.add( table, BorderLayout.CENTER );
                
                sorter = new TableRowSorter< TableModel >( tblModel );
                RowFilter< TableModel, Integer > filter = new RowFilter< TableModel, Integer >() {
                   @Override
                   public boolean include( javax.swing.RowFilter.Entry< ? extends TableModel, ? extends Integer > entry )
                   {
                      TableModel model = entry.getModel();
                      return (Boolean)model.getValueAt( entry.getIdentifier(), 1 );
                   }
                };
                sorter.setRowFilter( filter );
                
                //I thought this would resolve my problem....
                sorter.setSortsOnUpdates( true );
                
                table.setRowSorter( sorter );
                
                JPanel controls = new JPanel();
                contentPane.add( controls, BorderLayout.EAST );
                
                combo = new JComboBox( new DefaultComboBoxModel( new Object[] { 0, 1, 2, 3, 4 } ) );
                controls.add( combo );
                
                checkBox = new JCheckBox( "Set", true );
                controls.add( checkBox );
                
                JButton apply = new JButton( "Apply Change" );
                controls.add( apply );
                
                apply.addActionListener( new ActionListener() {
                   @Override
                   public void actionPerformed( ActionEvent e )
                   {
                      Integer i = (Integer)combo.getSelectedItem();
                      boolean selected = checkBox.isSelected();
                      tblModel.model[ i ] = selected;
                      
                      tblModel.fireTableRowsUpdated( i, i );
                   }
                });
                
                JButton applySorter = new JButton( "Reapply Sorter" );
                controls.add( applySorter );
                
                applySorter.addActionListener( new ActionListener() {
                   @Override
                   public void actionPerformed( ActionEvent e )
                   {
                      table.setRowSorter( sorter );
                   }
                });
                
                setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
                setResizable( false );
                
                pack();
                setVisible( true );
             }
             
             public static void main( String[] args )
             {
                new JTableFiltering();
             }
          }
          • 2. Re: How to Filter JTable Rows as Data Changes
            jduprez
            Hello,
            any helpful suggestions are welcome, I'm at a bit of a dead end at the moment.
            Could you post an SSCCE (http://sscce.org) so that we can try from where you already are?
            EDIT: OK you provided one while I was writing this :o)
            So when I fire the update event I need my row sorter (with the filters set) to re-sort the table. Having a look around there is a method on row sorter:
            getSortsOnUpdate()
            which from the javadoc I believed would create the behaviour I needed - assuming I told it to return true.
            It's strange the way you word it: you seem to imply that you have to override this method, whereas there exists the corresponding setter <TT>setSortsOnUpdate()</TT>.
            Having tried this the functionality remains the same, as the data model changes, the values in the table update but the filters are not reapplied.
            Please provide the SSCCE.
            Debugging through the jdk code to get an idea of what might be going on it seems that the filters are called and it determines that the changed row should be filtered out, but gets to a point
            where it decides (...)
            Where (which JDK class)?
            I haven't found such code but I probably have not looked in the right class.
            I believe that simply re-setting the row sorter on the table would cause the desired effect
            That wouldn't be intuitive code for maintenance.
            You can probably achieve the same effect (SSCCE, please!) by having your TableModel issue a <TT>fireTableDataChanged()</TT>, or better, more self-describing, with <TT>myTableROwSorter.sort()</TT>.
            but would be incredibly inefficient with a data model that can potentially change at any time.
            Maybe. Maybe not.
            I agree that the limitation you seem to have found is annoying (and I interpret the sortsOnUpdate property the same way you do), but if all else fails, use the hammer solution, and actually measure whether it has an impact on the program's usability.
            It is frequent that people overestimate the performance overhead of Swing operations. If your table has less than a few hundred rows (and if it has more, it shouldn't be a table), and if the model's itself is reasonably fast (if getDataAt() is simply an accessor to internally cached data), and if the updates come in less than one per second, this shouldn't be a major problem. If the latter condition is not fulfilled (you really have a continuous and dense flow of updates), you could buffer the updates to actually sort the view no more than one per second.

            Best regards,

            J.

            Edited by: jduprez on Jun 29, 2011 12:02 PM
            • 3. Re: How to Filter JTable Rows as Data Changes
              871939
              Hi,

              I've been following through jdk1.6.0_25. Having fired table rows updated you end up in a private method - DefaultRowSorter#rowsUpdated0. This seems to perform the filtering correctly. Returning back up the stack you end up back in another private method JTable#sortedTableChanged at line 4106 notifySorter, with the class variable 'sorterChanged' already set to true. Continuing down this method you step into an if block:
              if (sorterChanged) {
                 // Update the editing row
                 if (this.editingRow != -1) {
                    int newIndex = (editingModelIndex == -1) ? -1 : convertRowIndexToView(editingModelIndex,change);
                    restoreSortingEditingRow(newIndex);
                 }
              
                 // And handle the appropriate repainting.
                 if (e == null || change.type != TableModelEvent.UPDATE) {
                    resizeAndRepaint();
                 }
              }
              so sorterChanged is true, nothing is being edited so the first block is not entered, then the second block where a resizeAndRepaint method - which sounds more than useful given that it seems that a correct repaint is that is missing - is not entered as the event is an UPDATE. The resizeAndRepaint does just call revalidate and repaint which I have tried by overridding JTable#tableChanged ( higher up in this call tree ) and calling revalidate and repaint - I'm just not sure how much of an impact that will have on performance.

              The tables generally shouldn't have more than a few hundred rows in, but there will be four of them in the client at any one time. There are two different branches of data types and two different time periods the tables are for.

              As you say if this doesn't work, then the 'hammer' approach is all that is left...thanks for having look though.
              • 4. Re: How to Filter JTable Rows as Data Changes
                terai
                Sorry, OP already said that.

                Hi,
                --Probably you are looking for: [url http://download.oracle.com/javase/6/docs/api/javax/swing/RowSorter.html#rowsUpdated(int,%20int)]RowSorter#rowsUpdated(int, int)--

                Edited by: aterai on 2011/06/29 20:28
                1 person found this helpful
                • 5. Re: How to Filter JTable Rows as Data Changes
                  jduprez
                  Hello,

                  thanks for the SSCCE, and for the update.

                  I tested my "hammer" solutions in the SCCE, and they work.
                  so sorterChanged is true, nothing is being edited so the first block is not entered, then the second block where a resizeAndRepaint method - which sounds more than useful given that it seems that a correct repaint is that is missing - is not entered as the event is an UPDATE.
                  That reminds me that the setSortsOnUpdate javadoc explicitly mentions +"For example, if this is true and the user edits an entry the location of that item in the view may change. The default is false."+ , so apparently they had editing in mind and not the other implications, such as filtered out rows changing the table's row count and row positions.
                  The resizeAndRepaint does just call revalidate and repaint which I have tried by overridding JTable#tableChanged ( higher up in this call tree ) and calling revalidate and repaint - I'm just not sure how much of an impact that will have on performance.
                  Probably the same impact one as the other hammer methods (I'm not sure repainting implies sorting and filtering, that would defeat the purpose of caching sort results; have you tried it, and does it work?).

                  I also notice in private method DefaultRowSorter.shouldOptimizeChange() , which is called somewhere while processing the row update, that the code checks whether the change spans more one one tenth (and yes, the ten is hard-coded!) of the rows, and doesn't force re-sorting if it doesn't... With the explicit comment "too much changed, sort it all". I really wonder how much they poured over the consequences of updates in sorting/filtering...
                  The tables generally shouldn't have more than a few hundred rows in, but there will be four of them in the client at any one time. There are two different branches of data types and two different time periods the tables are for.
                  As you say if this doesn't work, then the 'hammer' approach is all that is left...thanks for having look though.
                  Revisiting this point: I don't see how you could save a complete repaint anyway: if the model change results in the first row being filtered out, all rows have to be repainted (up one position), and even a new row can be displayed at the bottom. Again if painting implies sorting, there's no way around sorting anyway, so it's not worth trying to avoid the sort.
                  I agree that this should be handled automatically (and I consider as a bug the fact that this isn't).

                  In fact if you <TT>fireRowUpdated(i-1, i)</TT> (quick hacky-ish test), you'll notice that the two rows are appropriately repainted and shift, but not the rest of the table (which should be shifted up one row). That is clearly a bug, it looks that the JTable repaints only the range of rows in the update event, not taking into account that new sort impluies changes in other row indices outside of this range.

                  *In short: rowupdated in the presence of a sorter/filter should imply resorting/filtering, and if needed repainting, of the whole table. and that should be in the stock JDK, not forced upon applicative code to do that.*

                  Edited by: jduprez on Jun 29, 2011 1:23 PM
                  • 6. Re: How to Filter JTable Rows as Data Changes
                    jduprez
                    aterai wrote:
                    Hi,
                    Probably you are looking for: [url http://download.oracle.com/javase/6/docs/api/javax/swing/RowSorter.html#rowsUpdated(int,%20int)]RowSorter#rowsUpdated(int, int)
                    Damn that works >o(
                    I'm worried, because its Javadoc says: +"You normally do not call this method. This method is public to allow view classes to call it."+
                    Indeed JTable code should call it when appropriate, not applicative code. But apparently it solves the OP's problem (tested in his SSCCE).
                    • 7. Re: How to Filter JTable Rows as Data Changes
                      terai
                      jduprez wrote:
                      aterai wrote:
                      Hi,
                      Probably you are looking for: [url http://download.oracle.com/javase/6/docs/api/javax/swing/RowSorter.html#rowsUpdated(int,%20int)]RowSorter#rowsUpdated(int, int)
                      Damn that works >o(
                      I'm worried, because its Javadoc says: +"You normally do not call this method. This method is public to allow view classes to call it."+
                      Indeed JTable code should call it when appropriate, not applicative code. But apparently it solves the OP's problem (tested in his SSCCE).
                      deleted as my post was nonsense. thank you.
                      • 8. Re: How to Filter JTable Rows as Data Changes
                        871939
                        Hi again,

                        thanks to both of you for the help.

                        Not sure why calling rowsUpdated again causes the expected behaviour as I thought that was already being called to get to the rowsUpdated0 method I previously metioned. Even if its not supposed to be called I might just see what its doing differently a second time through.

                        jduprez - you are right that I'm going to need to paint everything under the filtered row think I was mainly concerned about reapplying the sorter, as calling revalidate and repaint seems to work I might just go with that for now and hope I'm not calling it more frequently than I need.
                              apply.addActionListener( new ActionListener() {
                                 @Override
                                 public void actionPerformed( ActionEvent e )
                                 {
                                    Integer i = (Integer)combo.getSelectedItem();
                                    boolean selected = checkBox.isSelected();
                                    tblModel.model[ i ] = selected;
                                    
                                    tblModel.fireTableRowsUpdated( i, i );
                                    //******
                                    table.revalidate();
                                    table.repaint();
                                    //******
                                 }
                              });
                        thanks again.