2013-04-01 3 views
2

все!Выполнить действие в JTable до выбора строки

В моем приложении мне нужно будет создать пассивный JTable. Пассивным я имею в виду, что выбор строки не выполняется непосредственно JTable, а по запросу другого компонента. Таким образом, когда пользователь переходит к новой строке, таблица не реагирует немедленно, но сначала попросите набор данных обновить его внутреннее состояние на основе нужной новой строки, после чего набор данных перезвонит таблице, чтобы сделать реальный выбор. Поэтому я просто пытаюсь выполнить действие, прежде чем новая строка будет выбрана в таблице.

Я создал небольшой прототип для вас, чтобы иметь представление о том, чего я хочу. Ниже прототипа вы найдете мои вопросы.

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); 
     } 
    } 
} 
  • Видите ли вы какие-либо недостатки в этой конструкции?
  • мне нужно переопределить несколько методов в ColumnSelectionModel и RowSelectionModel классов, чтобы заставить договор, или просто метод setSelectionInterval достаточно ли? До сих пор я не обнаружил недостатков в этом отношении.
  • Меня действительно раздражает класс ColumnSelectionModel. Его цель состоит только в том, чтобы поймать старый выбранный столбец до того, как новый выбран, чтобы он мог быть восстановлен в RowSelectionModel # setSelectionInterval, если что-то пойдет не так. Я не мог сделать это только с класса RowSelectionModel. Есть ли другой способ?

Существует еще один подход, который не использует модели выбора. Вы можете сделать это:

Комментировать строку getColumnModel().setSelectionModel(new ColumnSelectionModel()); в конструкторе таблицы.

Комментарий метода Таблица # createDefaultSelectionModel метод.

Заменить Таблица # dataMoved метод этим:

void dataMoved() 
{ 
    _dataMoved = true; 
    try 
    { 
     int rowIndex = _dataSet.getCurrentIndex(); 
     changeSelection(rowIndex, getSelectedColumn(), false, false); 
    } 
    finally 
    { 
     _dataMoved = false; 
    } 
} 

Override Таблица # changeSelection метод:

@Override 
public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) 
{ 
    if (_dataMoved) 
    { 
     super.changeSelection(rowIndex, columnIndex, toggle, extend); 
    } 
    else 
    { 
     if (rowIndex != _dataSet.getCurrentIndex()) 
     { 
      _dataSet.moveTo(rowIndex); 
     } 
     super.changeSelection(_dataSet.getCurrentIndex(), columnIndex, toggle, extend); 
    } 
} 

Но я не использовал этот подход, даже если он намного проще, чем при выборе моделей, поскольку документация метода changeSelection говорит:

Большинство изменений в выборе, которые являются результатом клавиатуры или мыши события, полученные в пользовательском интерфейсе, направляются через этот метод так, что поведение моей перекрываться в subclasse.

Так я интерпретировал

Большинство изменений

не все изменения, а это означает, что могут быть некоторые изменения выбора, которые не проходят считали этот метод. Правильно ли я на этом, или могу ли я доверять changeSelection?

Заранее спасибо.

Marcos

+0

Какой проблемы это решить? – trashgod

+0

@trashgod Проблема выполнения действия перед выбором строки в JTable. При необходимости выбор строки может быть отменен. В приведенном выше примере новая строка в таблице не будет выбрана, если набор данных не сможет обновить свое внутреннее состояние на основе индекса строки, предложенного таблицей. Таким образом, строка в таблице выбирается только в ответ на изменение набора данных, а не наоборот. – Marcos

+0

@trashgod другими словами: таблица должна отражать состояние набора данных (его currentIndex). Если набор данных не может изменить свой currentIndex, выбранная строка в таблице также не изменится. – Marcos

ответ

2

Основной недостаток вашего подхода является жестко проводной двунаправленной связью между вашей (навигационной) моделью - иначе: DataSet - и видом. Выход такой как VetoableSelectionModel: тогда вы можете зарегистрировать DataSet как vetoablePropertyChangeListener для модели выбора, которая является свободной связью, которую можно настроить без подкласса таблицы.

Некоторых фрагменты код из общей проводки:

final JTable table = new JTable(new Model()); 
VetoableListSelectionModel selectionModel = new VetoableListSelectionModel(); 
table.setSelectionModel(selectionModel); 
VetoableChangeListener veto = new VetoableChangeListener() { 

    @Override 
    public void vetoableChange(PropertyChangeEvent evt) 
      throws PropertyVetoException { 
     if (2 == (Integer) evt.getNewValue()) throw new PropertyVetoException("", evt); 
    } 
}; 
selectionModel.addVetoableChangeListener(veto); 
table.getSelectionModel().addListSelectionListener(
    new ListSelectionListener() 
    { 
     @Override 
     public void valueChanged(ListSelectionEvent e) 
     { 
      if (!e.getValueIsAdjusting()) 
      { 
       selectedRow.setText(
        "Selected row: " + table.getSelectedRow()); 
      } 
     } 
    } 
); 

Модель выбора скелета (полный код в the swingx incubator - берегитесь: никто не поддерживались!)

/** 
* Quick impl of a list selection model which respects a veto before 
* changing selection state. The veto is effect in SINGLE_SELECTION mode 
* only. 
*/ 
public class VetoableListSelectionModel extends DefaultListSelectionModel { 
    private VetoableChangeSupport vetoableChangeSupport; 

    /** 
    * Defaults to SINGLE_SELECTION mode. 
    * 
    */ 
    public VetoableListSelectionModel() { 
     super(); 
     setSelectionMode(SINGLE_SELECTION); 
    } 

    @Override 
    public void setSelectionInterval(int index0, int index1) { 
     if (isVetoable()) { 
      try { 
       fireVetoableChange(getMinSelectionIndex(), index0); 
      } catch (PropertyVetoException e) { 
       // vetoed - do nothing 
       return; 
      } 
     } 
     super.setSelectionInterval(index0, index1); 
    } 

    // similar for all methods that change the selection 
    ... 

    // methods to add/remove listeners and fire the event 
    ... 
} 
+0

Согласовано. Высокое сцепление всегда плохое. Однако в моем реальном приложении все немного чище. Рад, что в вашем обзоре проблема связи была единственной проблемой, которую вы нашли. – Marcos

+0

Возможно, я ошибаюсь, но я думаю, что проблема связана с RowInserted, пожалуйста, как этот код может синхронизировать OPs_unknow_rowIndex – mKorbel

+0

@mKorbel Не могли бы вы подробнее рассказать? – Marcos

Смежные вопросы