все!Выполнить действие в 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
Какой проблемы это решить? – trashgod
@trashgod Проблема выполнения действия перед выбором строки в JTable. При необходимости выбор строки может быть отменен. В приведенном выше примере новая строка в таблице не будет выбрана, если набор данных не сможет обновить свое внутреннее состояние на основе индекса строки, предложенного таблицей. Таким образом, строка в таблице выбирается только в ответ на изменение набора данных, а не наоборот. – Marcos
@trashgod другими словами: таблица должна отражать состояние набора данных (его currentIndex). Если набор данных не может изменить свой currentIndex, выбранная строка в таблице также не изменится. – Marcos