2013-04-05 58 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

相關問題