This discussion is archived
6 Replies Latest reply: Apr 4, 2013 5:58 AM by 809259 RSS

Execute an action before a row is selected in JTable

809259 Newbie
Currently Being Moderated
Hello, everybody!

First of all, I need to be honest and tell you that I already posted my questions regarding this issue in another forum:

http://stackoverflow.com/questions/15747932/execute-an-action-in-jtable-before-a-row-is-selected

but as I haven't got any answer from there, I'm posting it also here in the hope that some of you can give me some advice.

In my application I will need to create a passive JTable. By passive I mean that the selection of row is not done directly by the JTable, but when requested by another component. So, when the user go to a new row the table doesn't react immediately, but first ask a dataset to update its internal state based on the desired new row and after that the dataset calls back the table to do the real selection. So I'm just trying to execute an action before the new row is selected in the table.

I created a little prototype for you to have an idea of what I want. Below the prototype you'll find my questions.
import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.DefaultListSelectionModel;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;

public class SSCCE extends JPanel
{
     public SSCCE()
     {
          setLayout(new BorderLayout());
          
          final JLabel selectedRow = new JLabel();
          
          final Table table = new Table();
          table.getSelectionModel().addListSelectionListener(
               new ListSelectionListener()
               {
                    @Override
                    public void valueChanged(ListSelectionEvent e)
                    {
                         if (!e.getValueIsAdjusting())
                         {
                              selectedRow.setText(
                                   "Selected row: " + table.getSelectedRow());
                         }
                    }
               }
          );
          
          new DataSet(table);
          
          add(new JScrollPane(table), BorderLayout.CENTER);
          add(selectedRow, BorderLayout.PAGE_END);
     }
     
     private static void createAndShowGUI()
     {
          JFrame frame = new JFrame("Table Test");
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.add(new SSCCE());
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
     }

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

class DataSet
{
     private final Table _table;
     private int _currentIndex;
     
     DataSet(Table table)
     {
          _table = table;
          _table.setDataSet(this);
     }
     
     int getCurrentIndex()
     {
          return _currentIndex;
     }
     
     void moveTo(int index) throws MovementException
     {
          if (index < 0 || index > 4)
          {
               throw new IndexOutOfBoundsException();
          }
          // Let's suppose there was a problem moving to the 2nd index. Maybe
          // the data set was in edit mode and couldn't persist the changes
          // because of a validation error.
          if (index == 2)
          {
               throw new MovementException();
          }
          _currentIndex = index;
          // Notifies the table that the data was moved so that the table can
          // update its selection model based on the current index of the
          // data set.
          _table.dataMoved();
     }
}

class MovementException extends RuntimeException
{
}

class Table extends JTable
{
     private DataSet _dataSet;
     // When true signals that the data was moved in the data set, so selection
     // is allowed.
     private boolean _dataMoved;
     // Previous selected column.
     private int _oldSelectedColumn;
     
     Table()
     {
          super(new Model());
          
          setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
          setCellSelectionEnabled(true);
          getTableHeader().setReorderingAllowed(false);
          setPreferredScrollableViewportSize(new Dimension(500, 170));
          
          getColumnModel().setSelectionModel(new ColumnSelectionModel());
     }
     
     void setDataSet(DataSet dataSet)
     {
          _dataSet = dataSet;
     }
     
     // Called by DataSet#moveTo.
     void dataMoved()
     {
          _dataMoved = true;
          try
          {
               int rowIndex = _dataSet.getCurrentIndex();
               // Select the new row.
               setRowSelectionInterval(rowIndex, rowIndex);
          }
          finally
          {
               _dataMoved = false;
          }
     }
     
     @Override
     protected ListSelectionModel createDefaultSelectionModel()
     {
          return new RowSelectionModel();
     }
     
     private class ColumnSelectionModel extends DefaultListSelectionModel
     {
          @Override
          public void setSelectionInterval(int index0, int index1)
          {
               // Save the old selected column to be restored in
               // RowSelectionModel#setSelectionInterval in case of an error.
               _oldSelectedColumn = getSelectedColumn();
               super.setSelectionInterval(index0, index1);
          }
     }
     
     private class RowSelectionModel extends DefaultListSelectionModel
     {
          @Override
          public void setSelectionInterval(int index0, int index1)
          {
               if (_dataMoved || index1 == _dataSet.getCurrentIndex())
               {
                    super.setSelectionInterval(index0, index1);
               }
               else
               {
                    try
                    {
                         _dataSet.moveTo(index1);
                    }
                    catch (MovementException ex)
                    {
                         // There was a problem in the data set. Restore the old
                         // selected column.
                         setColumnSelectionInterval(
                              _oldSelectedColumn, _oldSelectedColumn);
                         throw ex;
                    }
               }
          }
     }

     private static class Model extends AbstractTableModel
     {
          private String[] columnNames =
             {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};
        private Object[][] data = {
             {"Kathy", "Smith", "Snowboarding", 5, false},
             {"John", "Doe", "Rowing", 3, true},
             {"Sue", "Black", "Knitting", 2, false},
             {"Jane", "White", "Speed reading", 20, true},
             {"Joe", "Brown", "Pool", 10, false}
        };

        public int getColumnCount()
        {
            return columnNames.length;
        }

        public int getRowCount()
        {
            return data.length;
        }

        public String getColumnName(int col)
        {
            return columnNames[col];
        }

        public Object getValueAt(int row, int col)
        {
            return data[row][col];
        }

        public Class<?> getColumnClass(int c)
        {
            return getValueAt(0, c).getClass();
        }
        
        public void setValueAt(Object value, int row, int col)
        {
            data[row][col] = value;
            fireTableCellUpdated(row, col);
        }
    }
}
- Do you see any flows in this design?
- Do I need to override more methods in the ColumnSelectionModel and RowSelectionModel classes to force the contract, or just the setSelectionInterval method suffices? I haven't found any flows in this regard until now.
- It really annoys me to have the ColumnSelectionModel class. Its purpose is only to catch the old selected column before the new one is selected so that it can be restored in the RowSelectionModel#setSelectionInterval if something goes wrong. I couldn't do it only with the RowSelectionModel class. Is there another way?

There is another approach that doesn't use selection models. You can do this:

Create the field +private int _columnIndex+ in the Table class.

Comment the line getColumnModel().setSelectionModel(new ColumnSelectionModel()); in the table constructor.

Comment the method Table#createDefaultSelectionModel method.

Replace the Table#dataMoved method by this:
void dataMoved()
{
    _dataMoved = true;
    try
    {
        int rowIndex = _dataSet.getCurrentIndex();
        changeSelection(rowIndex, _columnIndex, false, false);
    }
    finally
    {
        _dataMoved = false;
    }
}
Override the Table#changeSelection method:
@Override
public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend)
{
    if (_dataMoved || rowIndex == _dataSet.getCurrentIndex())
    {
        super.changeSelection(rowIndex, columnIndex, toggle, extend);
    }
    else
    {
        _columnIndex = columnIndex;
        _dataSet.moveTo(rowIndex);
    }
}
But I didn't use this approach, even though it is a lot simpler than with the selection models, because the documentation of the changeSelection method says:

+"Most changes to the selection that are the result of keyboard or mouse events received by the UI are channeled through this method so that the behaviour my be overriden by a subclasse."+

So I interpreted +"Most changes"+ as not all changes, meaning that there could be some selection changes that don't pass thought this method. Am I right on this or can I trust the changeSelection approach?

Thank you in advance.

Marcos

Edited by: marcos_aps on 02/04/2013 05:17
  • 1. Re: Execute an action before a row is selected in JTable
    TPD-Opitz-Consulting-com Expert
    Currently Being Moderated
    My impression is that you do this the wrong way around.

    First you should do some reading on the MVC-pattern.
    Then you should have a look at the JTable tutorial: http://docs.oracle.com/javase/tutorial/uiswing/components/table.html

    Once you have your business data stuffed into a <tt>TableModel</tt> your problem meight be solved.

    bye
    TPD
  • 2. Re: Execute an action before a row is selected in JTable
    809259 Newbie
    Currently Being Moderated
    Thanks for replying, TPD Opitz-Consulting.

    In another forum I said to someone that asked me about the intent of my code: "the table should reflect the state of the dataset (its currentIndex). If the dataset can't change its currentIndex, the selected row in the table won't change as well." This is basically what I want to accomplish with the code above.
    TPD Opitz-Consulting com wrote:
    First you should do some reading on the MVC-pattern.
    Then you should have a look at the JTable tutorial: http://docs.oracle.com/javase/tutorial/uiswing/components/table.html
    I've already read both.

    >
    Once you have your business data stuffed into a <tt>TableModel</tt> your problem meight be solved.
    In my real application, my business data is in a TableModel. I mean, even though the data comes from another place (some kind of data set), it is made accessible to the JTable by a custom table model.
    My impression is that you do this the wrong way around.
    Ok, I'm here for suggestions. In your opinion how would I do this? Is there other way besides using selection models and the changeSelection method? By the way, did you find any bug in the code?

    Marcos
  • 3. Re: Execute an action before a row is selected in JTable
    TPD-Opitz-Consulting-com Expert
    Currently Being Moderated
    marcos_aps wrote:
    Ok, I'm here for suggestions. In your opinion how would I do this?
    I would do this by disallowing any changes in the Table itself (<tt>isEditable(in row, int col) { return false;}</tt>) and providing a detail area where the fileds can be edited in some kind of form. This form would call <tt>table.setEnabled()</tt> with the result of its own valitity check.

    Alternatively I'd present the form in a modal dialog (eg. after double clicking a row).
    public class UnselectableTableTest {
    
         @Test
         public void test() {
              final JTable jtable = new JTable(new DefaultTableModel(){
    
                   @Override
                   public boolean isCellEditable(int row, int column) {
                        return false;
                   }
    
                   @Override
                   public int getRowCount() {
                        return 10;
                   }
    
                   @Override
                   public int getColumnCount() {
                        return 10;
                   }
    
                   @Override
                   public Object getValueAt(int row, int column) {
                        return (row+1)*100+ column+1;
                   }
                   
              });
              jtable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
              jtable.addMouseListener(new MouseAdapter() {
    
                   @Override
                   public void mouseClicked(MouseEvent e) {
                         if(e.getClickCount() == 2){
                              JOptionPane.showMessageDialog(null,"edit values of row "+jtable.getSelectedRow());
                         }
                   }
              });
              JOptionPane.showMessageDialog(null,jtable);
         }
    }
    *[edit]* also: did you try to set a selection model that simply does nothing?

    bye
    TT

    Edited by: TPD Opitz-Consulting com on 03.04.2013 23:30
  • 4. Re: Execute an action before a row is selected in JTable
    809259 Newbie
    Currently Being Moderated
    TPD Opitz-Consulting com wrote:
    I would do this by disallowing any changes in the Table itself (<tt>isEditable(in row, int col) { return false;}</tt>) and providing a detail area where the fileds can be edited in some kind of form. This form would call <tt>table.setEnabled()</tt> with the result of its own valitity check.

    Alternatively I'd present the form in a modal dialog (eg. after double clicking a row).
    I would only consider these approaches if it was really not possible to edit directly in the table. Besides, editing right in the table cells provides a better user experience.
    *[edit]* also: did you try to set a selection model that simply does nothing?
    Yes, but that's not what I want.

    Marcos
  • 5. Re: Execute an action before a row is selected in JTable
    TPD-Opitz-Consulting-com Expert
    Currently Being Moderated
    In this case you may habe better luck following this: http://stackoverflow.com/questions/5873033/jtable-input-verifier
    and create your own <tt>CellEditor</tt>s.

    *[edit]* looks like this really works
    public class UnselectableTableTest {
    
         @Test
         public void test() {
              final JTable jtable = new JTable(new DefaultTableModel() {
    
                   @Override
                   public int getRowCount() {
                        return 10;
                   }
    
                   @Override
                   public int getColumnCount() {
                        return 10;
                   }
    
                   @Override
                   public Object getValueAt(int row, int column) {
                        return (row + 1) * 100 + column + 1;
                   }
    
              });
              final JTextField textField = new JTextField();
              jtable.setToolTipText("type 'fail' to prevent row or column change.");
              TableCellEditor editor = new DefaultCellEditor(textField) {
                   @Override
                   public boolean stopCellEditing() {
                        textField
                                  .setBackground("fail".equals(textField.getText()) ? Color.RED
                                            : Color.WHITE);
                        return (!"fail".equals(textField.getText()))
                                  && super.stopCellEditing();
    
                   }
              };
              jtable.setDefaultEditor(Object.class, editor);
              JOptionPane.showMessageDialog(null, jtable);
         }
    }
    bye
    TPD

    Edited by: TPD Opitz-Consulting com on 04.04.2013 14:35
  • 6. Re: Execute an action before a row is selected in JTable
    809259 Newbie
    Currently Being Moderated
    TPD Opitz-Consulting com wrote:
    In this case you may habe better luck following this: http://stackoverflow.com/questions/5873033/jtable-input-verifier
    and create your own <tt>CellEditor</tt>s.
    Yes, this is worth looking at. By the way, in my application I already use custom cell editors in all my columns.

    Marcos

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points