В моем основном приложении JTable теряет фокус, когда диалог отображается из компонента редактора ячейки.JTable теряет фокус при отображении диалога из компонента редактора ячейки
Ниже представлен простой SSCCE, который я сделал для вас, чтобы увидеть проблему.
ли эти эксперименты Simples:
- Нажмите 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);
}
}
}
}
Итак, есть ли способ решить эту проблему? Решение этой проблемы очень важно для меня.
спасибо.
Marcos
* UPDATE *
Я думаю, я нашел решение, но я хотел бы иметь свое мнение, если это действительно надежное решение.
Изменение 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();
}
Итак, вы думаете, это действительно решает проблему или есть недостаток?
Marcos
UPDATE 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);
}
}
}
}
У вас есть еще недостатки? Я хотел бы получить ваш отзыв.
Marcos
UPDATE 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);
}
}
Комментарии и предложения по поводу этого последнего решения до сих пор ценится. Это конечное и оптимальное решение?
Marcos
не знаю, решение (и должен признать, что я действительно не вникать в проблему, слишком поздно, прямо сейчас :-), но не меняйте состояние таблицы в редакторе (как вы это делаете, обращая внимание на него): это вызовет больше проблем, чем это решит. – kleopatra
@ kleopatra На данный момент изменение состояния таблицы кажется единственным, что заставляет его работать. Я даже могу ограничить вызов выбора только теми случаями, когда отображается диалог, сведение к минимуму проблемы. Я видел, что во время «table.requestFocusInWindow» владелец постоянной фокуса «null», поэтому я даю некоторые рекомендации менеджеру фокуса, какой компонент ему нужно сосредоточить дальше, иначе он выберет все, что ему нравится, в этом случае первое поле в форме. Во всяком случае, этот вопрос кажется жестким. Если у вас есть другое решение, я был бы рад услышать от вас. Спасибо. – Marcos