在我的主应用程序中,当从单元格编辑器组件显示对话框时,JTable 失去焦点。
下面是我为您制作的简单 SSCCE 以查看问题。
做这些简单的实验:
- 在第一个表格列中按 F2 开始编辑。然后将列内容更改为数字 2 并按 ENTER 键。表格将失去焦点,表单中的第一个字段将失去焦点。
- 在第一个表格列中按 F2 开始编辑。然后将列内容更改为数字 2 并按 TAB 键。表格将失去焦点,表单中的第一个字段将失去焦点。
表单中的第一个字段也是一个 SearchField 组件。因为它不在 JTable 中,所以当您将其内容更改为数字 2 并提交编辑(使用 ENTER 或 TAB)时,它会正常运行。
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.NumberFormatter;
public class SSCCE extends JPanel
{
private SSCCE()
{
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JPanel pnlFields = new JPanel();
pnlFields.setLayout(new BoxLayout(pnlFields, BoxLayout.PAGE_AXIS));
pnlFields.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
SearchField field1 = new SearchField();
configureField(field1);
pnlFields.add(field1);
pnlFields.add(Box.createRigidArea(new Dimension(0, 3)));
JTextField field2 = new JTextField();
configureField(field2);
pnlFields.add(field2);
add(pnlFields, BorderLayout.PAGE_START);
add(new JScrollPane(createTable()), BorderLayout.CENTER);
}
private void configureField(JTextField field)
{
field.setPreferredSize(new Dimension(150, field.getPreferredSize().height));
field.setMaximumSize(field.getPreferredSize());
field.setAlignmentX(LEFT_ALIGNMENT);
}
private JTable createTable()
{
JTable table = new JTable(new CustomTableModel());
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setCellSelectionEnabled(true);
table.getTableHeader().setReorderingAllowed(false);
table.setPreferredScrollableViewportSize(new Dimension(500, 170));
table.setDefaultEditor(Integer.class, new SearchFieldCellEditor(new SearchField()));
return table;
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE (JTable Loses Focus)");
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 CustomTableModel extends AbstractTableModel
{
private String[] columnNames = {"Column1 (Search Field)", "Column 2"};
private Class<?>[] columnTypes = {Integer.class, String.class};
private Object[][] data = {{1, ""}, {3, ""}, {4, ""}, {5, ""}, {6, ""}};
@Override
public int getColumnCount()
{
return columnNames.length;
}
@Override
public int getRowCount()
{
return data.length;
}
@Override
public String getColumnName(int col)
{
return columnNames[col];
}
@Override
public Object getValueAt(int row, int col)
{
return data[row][col];
}
@Override
public Class<?> getColumnClass(int c)
{
return columnTypes[c];
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return true;
}
@Override
public void setValueAt(Object value, int row, int col)
{
data[row][col] = value;
fireTableCellUpdated(row, col);
}
}
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
@Override
public void setValue(Object value)
{
searchField.setValue(value);
}
@Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
@Override
public boolean stopCellEditing()
{
try
{
((SearchField) getComponent()).commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
@Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
@Override
public void propertyChange(PropertyChangeEvent evt)
{
Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue))
{
JOptionPane.showMessageDialog(
null, "Not found: " + newValue + ".", "Warning",
JOptionPane.WARNING_MESSAGE);
}
}
}
}
那么,有没有办法解决这个问题呢?这个问题的解决方案对我来说非常重要。
谢谢你。
马科斯
*更新*
我想我已经找到了一个解决方案,但如果它真的是一个值得信赖的解决方案,我想听听你的意见。
将stopCellEditing方法更改为此并再次测试 SSCCE:
@Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
Component table = searchField.getParent();
table.requestFocusInWindow();
return super.stopCellEditing();
}
那么,您认为这真的解决了问题还是有任何缺陷?
马科斯
更新 2
我发现了一个小漏洞。通过以下更改进行了更正:
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
@Override
public void setValue(Object value)
{
searchField.setValue(value);
}
@Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
@Override
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
SearchField searchField = (SearchField) getComponent();
searchField.setPreparingForEdit(true);
try
{
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
finally
{
searchField.setPreparingForEdit(false);
}
}
@Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
Component table = searchField.getParent();
table.requestFocusInWindow();
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private boolean _isPreparingForEdit;
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
void setPreparingForEdit(boolean isPreparingForEdit)
{
_isPreparingForEdit = isPreparingForEdit;
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
@Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
@Override
public void propertyChange(PropertyChangeEvent evt)
{
final Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue) && !_isPreparingForEdit)
{
JOptionPane.showMessageDialog(null, "Not found: " + newValue + ".", "Warning",
JOptionPane.WARNING_MESSAGE);
}
}
}
}
你是否也发现了更多的漏洞?我想听听你的评论。
马科斯
更新 3
kleopatra建议后的另一个解决方案:
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.setShowMessageAsynchronously(true);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
@Override
public void setValue(Object value)
{
searchField.setValue(value);
}
@Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
@Override
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
SearchField searchField = (SearchField) getComponent();
searchField.setPreparingForEdit(true);
try
{
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
finally
{
searchField.setPreparingForEdit(false);
}
}
@Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private boolean _showMessageAsynchronously;
private boolean _isPreparingForEdit;
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
public boolean isShowMessageAsynchronously()
{
return _showMessageAsynchronously;
}
public void setShowMessageAsynchronously(boolean showMessageAsynchronously)
{
_showMessageAsynchronously = showMessageAsynchronously;
}
void setPreparingForEdit(boolean isPreparingForEdit)
{
_isPreparingForEdit = isPreparingForEdit;
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
@Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
@Override
public void propertyChange(PropertyChangeEvent evt)
{
final Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue) && !_isPreparingForEdit)
{
if (_showMessageAsynchronously)
{
SwingUtilities.invokeLater(
new Runnable()
{
@Override
public void run()
{
showMessage(newValue);
}
}
);
}
else
{
showMessage(newValue);
}
}
}
}
private void showMessage(Object value)
{
JOptionPane.showMessageDialog(null, "Not found: " + value + ".",
"Warning", JOptionPane.WARNING_MESSAGE);
}
}
关于最后一个解决方案的评论和建议仍然受到赞赏。这是最终和最优的解决方案吗?
马科斯