2013-04-05 84 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); 
      } 
     } 
    } 
} 

那么,有没有办法解决这个问题?这个问题的解决方案对我来说非常重要。

谢谢。

马科斯

* 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(); 
} 

,你觉得这确实解决了问题,或者是有什么缺陷?

马科斯

更新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

建议后,另一种解决方案由克列奥帕特拉

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

不知道解决办法(不得不承认,我真的没有考虑这个问题,为时已晚,现在:-)不过:在您的示例代码,可以通过包装成一个invokeLater的实现不要在编辑器中改变表格的状态(就像你要求重新回到它上面一样):这会引入比解决问题更多的问题。 – kleopatra 2013-04-05 22:50:04

+0

@kleopatra现在,改变表的状态似乎是唯一使它工作的东西。我甚至可以将选择调用仅限于显示对话框的情况下,从而最大限度地减少问题。我在'table.requestFocusInWindow'的时候看到,永久焦点所有者是'null',所以我给焦点管理器一些建议,它需要下一个焦点,否则它会选择它喜欢的任何东西,在这种情况下,表单中的第一个字段。无论如何,这个问题似乎是一个艰难的问题。如果您有其他解决方案,我会很高兴收到您的回复。谢谢。 – Marcos 2013-04-06 00:35:09

回答

1

正如我已经评论说:这是一个有点腥改变编辑器中的表的状态,特别是当它涉及到焦点即使在最佳情况下也很脆弱。所以我会竭尽全力避免它。

错误的行为感觉类似于一个错误实现的InputVerifier,它在验证和shouldYieldFocus中有副作用(如抓取焦点),这是正确的:在这样的上下文中,focusManager会变得困惑,它“忘记了“关于自然最后焦点的人 - 以前。

补救办法可能是让经理先完成工作,并在完成后才显示该信息。

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

     } 
    }); 
} 
+0

:)相信我或没有,我甚至在发布我的问题之前已经找到了这个完全解决方案。我只是没有使用它,因为在显示对话框之前看到表格中的选择有点奇怪。我也没有在这里发表,认为人们不会试图找到更好的。但也许这是唯一合理的解决方案。我只是希望这个解决方案没有任何特殊情况,并且它始终有效。尽管如此,我会更新我的帖子并为您提供信用。再次感谢你帮助我。 – Marcos 2013-04-06 11:24:03

+0

好吧,你的基本问题仍然是你让编辑组件做的比编辑器应该做的更多:-)另一方面,你允许将不太有效的值提交给模型。这有点极端......在你的鞋子里,我会尝试将编辑组件中的通知负担移出,首先给出这个要求,仔细观察。 – kleopatra 2013-04-06 13:23:52

+0

关于提交给模型的无效值,这对我来说不是问题。无效值仅在模型中被临时接受。如果模型有效,模型只会发布到数据库。因此,在用户正在编辑时,可以使用暂时无效的模型。我真正的编辑器组件也必须在表格外工作。如果这个消息不存在,我会重复传播代码,并且我强迫我的应用程序的其他部分为此付出代价。我仍然认为信息是编辑工作。 – Marcos 2013-04-06 13:39:05

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

在我的真实应用程序中,搜索字段只是在找不到实体时将值恢复为空。所以,我并不真正想要验证输入,因为用户不允许进入下一个组件(或表格单元格)。此外,对话框来自搜索组件,而不是表格单元格编辑器对我来说非常重要。这意味着我的应用程序需要更改很多代码。我真的害怕我不能使用你的解决方案。我认为这可能是另一种方法(即使是复杂的方法)在不通过_stopCellEditing_方法进行验证的情况下执行此操作。 – Marcos 2013-04-05 16:57:34