2013-04-05 2 views
2

В моем основном приложении 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

+0

не знаю, решение (и должен признать, что я действительно не вникать в проблему, слишком поздно, прямо сейчас :-), но не меняйте состояние таблицы в редакторе (как вы это делаете, обращая внимание на него): это вызовет больше проблем, чем это решит. – kleopatra

+0

@ kleopatra На данный момент изменение состояния таблицы кажется единственным, что заставляет его работать. Я даже могу ограничить вызов выбора только теми случаями, когда отображается диалог, сведение к минимуму проблемы. Я видел, что во время «table.requestFocusInWindow» владелец постоянной фокуса «null», поэтому я даю некоторые рекомендации менеджеру фокуса, какой компонент ему нужно сосредоточить дальше, иначе он выберет все, что ему нравится, в этом случае первое поле в форме. Во всяком случае, этот вопрос кажется жестким. Если у вас есть другое решение, я был бы рад услышать от вас. Спасибо. – Marcos

ответ

1

Как я уже отметил: это немного тусклый, чтобы изменить состояние таблицы в редакторе, особенно если это связано с фокус, который является хрупким даже в лучшем виде. Поэтому я бы сделал все возможное, чтобы избежать этого.

Неправильное поведение похоже на неверно реализованный InputVerifier, который имеет побочные эффекты (например, захват фокуса) в своей проверке, и в его shouldYieldFocus, как было бы правильно: в таком контексте focusManager запутывается, забывает "о естественном last-focusOwner-before.

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

if (needsMessage()) { 
    SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      JOptionPane.showMessageDialog(null, "Not found: " + 
        newValue + ".", "Warning", 
        JOptionPane.WARNING_MESSAGE); 

     } 
    }); 
} 
+0

:) Поверьте мне, или нет, я уже нашел это решение точно, даже после публикации моей проблемы здесь. Я просто не использовал его, потому что мне было немного странно видеть выбор в таблице до того, как было показано диалоговое окно. Я также не публиковал его здесь, думая, что люди не попытаются найти лучшего. Но, возможно, это единственное разумное решение. Я просто надеюсь, что с этим решением нет никакого угольного дела, и он всегда работает. Тем не менее, я обновляю свой пост и дам вам кредиты. Еще раз спасибо за помощь. – Marcos

+0

Ну, ваша базовая проблема по-прежнему заключается в том, что вы позволяете компоненту редактирования делать больше, чем даже редактор должен делать :-) С другой стороны, вы допускаете, чтобы недопустимое значение было привязано к модели. Это немного экстремально ... в твоих ботинках я бы попытался перенести извещение об уведомлении из компонента редактирования, начиная с того, чтобы придать этому требованию внешний вид. – kleopatra

+0

Что касается недействительных значений, полученных для модели, это не проблема в моем случае. Недопустимые значения принимаются в модели только временно. Модели публикуются только в базе данных, если они действительны. Таким образом, во время редактирования пользователь может иметь временно недопустимую модель. Мой реальный компонент редактора должен работать за пределами таблицы. Если сообщение было вне этого, у меня был бы дублированный код, и я заставлял другие части моего приложения платить за это. Я все еще думаю, что сообщение является заданием редактора. – Marcos

1

ли редактирование в методе stopCellEditing().

В этом примере вы вынуждены ввести строку 5 символов:

import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import javax.swing.text.*; 
import javax.swing.event.*; 
import javax.swing.border.*; 
import javax.swing.table.*; 

public class TableEdit extends JFrame 
{ 
    TableEdit() 
    { 
     JTable table = new JTable(5,5); 
     table.setPreferredScrollableViewportSize(table.getPreferredSize()); 

     JScrollPane scrollpane = new JScrollPane(table); 
     add(scrollpane); 

     // Use a custom editor 

     TableCellEditor fce = new FiveCharacterEditor(); 
     table.setDefaultEditor(Object.class, fce); 

     add(new JTextField(), BorderLayout.NORTH); 
    } 

    class FiveCharacterEditor extends DefaultCellEditor 
    { 
     FiveCharacterEditor() 
     { 
      super(new JTextField()); 
     } 

     public boolean stopCellEditing() 
     { 
      JTable table = (JTable)getComponent().getParent(); 

      try 
      { 
       System.out.println(getCellEditorValue().getClass()); 
       String editingValue = (String)getCellEditorValue(); 

       if(editingValue.length() != 5) 
       { 
        JTextField textField = (JTextField)getComponent(); 
        textField.setBorder(new LineBorder(Color.red)); 
        textField.selectAll(); 
        textField.requestFocusInWindow(); 

        JOptionPane.showMessageDialog(
         null, 
         "Please enter string with 5 letters.", 
         "Alert!",JOptionPane.ERROR_MESSAGE); 
        return false; 
       } 
      } 
      catch(ClassCastException exception) 
      { 
       return false; 
      } 

      return super.stopCellEditing(); 
     } 

     public Component getTableCellEditorComponent(
      JTable table, Object value, boolean isSelected, int row, int column) 
     { 
      Component c = super.getTableCellEditorComponent(
       table, value, isSelected, row, column); 
      ((JComponent)c).setBorder(new LineBorder(Color.black)); 

      return c; 
     } 

    } 

    public static void main(String [] args) 
    { 
     JFrame frame = new TableEdit(); 
     frame.setDefaultCloseOperation(EXIT_ON_CLOSE); 
     frame.pack(); 
     frame.setLocationRelativeTo(null); 
     frame.setVisible(true); 
    } 
} 
+0

В моем реальном приложении поле поиска просто возвращает значение null, когда объект не найден. Таким образом, я не пытаюсь проверить входные данные в том смысле, что пользователю не разрешено перейти к следующему компоненту (или ячейке таблицы). Кроме того, для меня очень важно, что диалог происходит от компонента поиска, а не редактора ячейки таблицы. Это означало бы, что в моем приложении менялось много кода. Я действительно боюсь, что не смогу использовать ваше решение. Я думаю, что это может быть другим способом (хотя и сложным) сделать это без проверки в методе _stopCellEditing_. – Marcos