大家!
在我的应用程序中,我需要创建一个被动的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);
}
}
}
还有另一种方法不使用选择模型。你可以这样做:
在表构造函数中注释行getColumnModel().setSelectionModel(new ColumnSelectionModel());
。
注释方法 Table#createDefaultSelectionModel 方法。
替换 Table#dataMoved 方法:
void dataMoved()
{
_dataMoved = true;
try
{
int rowIndex = _dataSet.getCurrentIndex();
changeSelection(rowIndex, getSelectedColumn(), false, false);
}
finally
{
_dataMoved = false;
}
}
重写表#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 方法的文档说:
选择的大多数更改都是键盘或鼠标的结果 UI接收的事件通过此方法传递,以便 我要被子类重写的行为。
所以我解释了
大多数更改
并非所有更改都意味着可能存在一些未通过此方法的选择更改。我是对的,还是我可以信任 changeSelection 方法?
提前谢谢。
马科斯
答案 0 :(得分:2)
您的方法的主要缺陷是您的(导航)模型(aka: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
...
}