结合“类似Excel”的行为,并在焦点离开JTable时更新模型

时间:2013-01-26 21:12:04

标签: java swing focus jtable tablecelleditor

我有一个JTable功能请求,允许更像Excel中的数据输入。如果用户键入单元格,则先前的值将替换为新值,并且可以使用箭头键在行和列之间导航。

我找到了几个解决这个问题的方法并实现了一个。

然后给了我一个额外的要求 - 如果用户点击表外,则更新了输入数据的单元格(编辑停止)。

我怀疑我只是遗漏了一些东西,但是我试图实现这两个要求都失败了。

  1. 我有一个自定义单元格编辑器,用于侦听focusLost和停止单元格编辑。如果我在表格中双击然后在表格外单击,则此方法有效。但是,如果我只是在单元格中键入数字,则此方法不会运行。

  2. tableMe.setSurrendersFocusOnKeystroke(真);这个a)导致我失去了第一次按键,并且b)把我放在编辑器中,所以我可以使用右箭头键和左箭头键来移出单元格。

  3. 我在桌面上添加了一个FocusListener。然后,我可以在单元格中键入一个数字并导航到另一个单元格,但我永远无法进入编辑器。双击会立即停止编辑。我想我可以在“真正”编辑(在编辑器窗口中有光标)和在单元格中输入数字之间找到一些区别。我到目前为止所尝试的(isEditing()总是出现,hasFocus()总是出现错误)不起作用。

  4. 有人能为我指出解决方案吗?我的尝试如下。我评论了错误的开始,但他们在那里,所以你可以看到我尝试的东西。

    import java.io.*;
    import java.util.*;
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.table.*;
    import java.text.NumberFormat;
    import javax.swing.text.JTextComponent;
    import javax.swing.border.*;
    import javax.swing.BorderFactory;
    import javax.swing.border.Border;
    
    class ExcelTableTest extends JFrame
    {
        public static final Color activeDark  = new Color(230, 185, 184);
        public static final Color activeLight = new Color(244, 233, 233);
    
        private MyJTable      tableMe;
        private MyTableModel  modelMe;
        private JTextField    textMe;
        private JScrollPane   scrollTable;
    
        private String[]   tableHeaders = { "Column 1", "Column 2" };
        private Object[][] tableData    = new Object[ tableHeaders.length ][ 4 ];
    
        ExcelTableTest()
        {
            setLayout( new BorderLayout() );
            modelMe  = new MyTableModel(tableData, tableHeaders );
            tableMe  = new MyJTable(modelMe);
            tableMe.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            TableColumn column = null;
            for (int i = 0; i < tableHeaders.length; i++)
            {
               column = tableMe.getColumnModel().getColumn(i);
               column.setCellRenderer( new MyCellRenderer()   );
               column.setCellEditor(   new NumberCellEditor() );
            }
            tableMe.setRowSelectionAllowed(false);
    //      tableMe.addFocusListener( new MyFocusListener() );
    //      tableMe.setSurrendersFocusOnKeystroke(true);
    
    //      modelMe.addTableModelListener(new MyTableModelListener());
            scrollTable = new JScrollPane(tableMe);
            scrollTable.setVerticalScrollBarPolicy(
                ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
            scrollTable.setHorizontalScrollBarPolicy(
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
            scrollTable.setPreferredSize(new Dimension(500,150));
    
            textMe = new JTextField( 6 );
    
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            setSize(500,210);
            add(BorderLayout.CENTER, scrollTable );
            add(BorderLayout.SOUTH,  textMe );
            setVisible(true);
        }
    
        class MyJTable extends JTable
        {
           MyJTable( TableModel model)
           {
              super(model);
           }
    //
    // Trying to get cells selected when editing starts
           @Override
           public Component prepareEditor(TableCellEditor editor,
                                             int row, int column)
           {
              Component editComponent = super.prepareEditor(editor, row, column);
              if (editComponent instanceof JTextComponent)
              {
                 System.out.println( "In prepare editor" );
                 ((JTextComponent) editComponent).selectAll();
    //           System.out.println( "Requesting Focus" );
    //           System.out.println( 
    //              ((JTextComponent) editComponent).isFocusable());
    //           System.out.println( 
    //              ((JTextComponent) editComponent).requestFocusInWindow());
              }
              return editComponent;
           }    
    //
    // This also works, but end result is the same- when using this, the editor
    // does not have focus, so I cannot update the cell just by switching focus
    // to another part of the GUI.
    //     @Override
    //     public boolean editCellAt( int row, int col, EventObject e )
    //     {
    //        boolean result = super.editCellAt( row, col, e );
    //
    //        Component editComponent = getEditorComponent();
    //        if (  editComponent == null || 
    //            !(editComponent instanceof JTextComponent) )
    //           return result;
    //        if (e instanceof KeyEvent)
    //           ((JTextComponent) editComponent).selectAll();
    //
    //        return result;
    //     }
    //
        }
    
        class MyFocusListener implements FocusListener
        {
           @Override
           public void focusGained(FocusEvent e)
           {
              System.out.println("Table has focus");
           }
    
           @Override
           public void focusLost(FocusEvent e)
           {
              System.out.println("Table lost focus");
              MyJTable        table       = (MyJTable) e.getSource();
              int[]           columns     = table.getSelectedColumns();
    //
    // I will not be allowing multiple column selection
    //
    // This is still a problem, as the table IS editing, even though the editor
    // does not seem to have focus. This is true whether I am in the case where
    // I really have the editor going (in which case the lostFocus of the editor
    // nicely cleans things up) or the editor is not really going, and then the
    // lostFocus of the editor never triggers.
    //        if ( columns.length > 0 && ! table.isEditing() )
              if ( columns.length > 0 )
              {
                 TableColumn       tableColumn = table.getColumnModel()
                                                      .getColumn(columns[0]);
                 DefaultCellEditor cellEditor  = (DefaultCellEditor)
                                                  tableColumn.getCellEditor();
    // Does not have focus even when I double click to edit the cell
                 if (cellEditor != null && ! cellEditor.getComponent().hasFocus() );
                 {
                    System.out.println( "Editor focus: " + 
                                         cellEditor.getComponent().hasFocus() );
                    if (!cellEditor.stopCellEditing())
                    {
                       cellEditor.cancelCellEditing();
                    }
                 }
              }
           }
        }
    
        class MyTableModel extends DefaultTableModel
        {
    
           public MyTableModel(Object rowData[][], Object columnNames[]) {
               super(rowData, columnNames);
           }
    
           @Override
           public Class getColumnClass(int col) {
              return Double.class;
           }
    
           @Override
           public boolean isCellEditable(int row, int col)
           {
              return true;
           }
        }
    
        class MyCellRenderer extends DefaultTableCellRenderer
        {
            public Component getTableCellRendererComponent(
              JTable table, Object value, boolean selected, boolean focus,
              int row, int col) {
    
              Component renderComponent = super.getTableCellRendererComponent(
                 table, value, selected, focus, row, col);
    //
    // This is called constantly 
    //        System.out.println( "In renderer" );
    
              if ((row % 2) == 0) {
                 renderComponent.setBackground(activeDark);
              } else {
                 renderComponent.setBackground(activeLight);
              }
    
              NumberFormat nf = NumberFormat.getInstance();
              nf.setMinimumFractionDigits(0);
    
              if (value != null)
                 System.out.println( "Value: " + value + "; " + value.getClass() );
              super.setText((value == null) ? "" : nf.format(value));
              super.setHorizontalAlignment( SwingConstants.RIGHT );
    
              return renderComponent;
           }
        }
    
        class NumberCellEditor extends DefaultCellEditor
        {
           private Double minimum = 0.0;
           private Double value   = null;
           private final  JTextField textField;
           private final  NumberFormat nf = NumberFormat.getInstance();
    
           public NumberCellEditor()
           {
              super(new JTextField());
              textField = (JTextField) getComponent();
              nf.setMinimumFractionDigits(0);
              textField.setHorizontalAlignment(JTextField.RIGHT);
              textField.addFocusListener(new FocusListener()
              {
                 @Override
                 public void focusGained(FocusEvent e)
                 {
                    System.out.println( "In editor" );
                    if (value != null)
                       textField.setText(nf.format(value.doubleValue()));
                    else
                       textField.setText("");
                    textField.setCaretPosition(0);
                 }
    
                 @Override
                 public void focusLost(FocusEvent e)
                 {
                    System.out.println( "Lost focus editing cell" );
                    if (!stopCellEditing()) cancelCellEditing();
                 }
               });
            }
    
            public boolean stopCellEditing()
            {
              String s = (String)super.getCellEditorValue();
              System.out.println( "Stop Cell editing: " + value );
              if ("".equals(s))
              {
                 value = null;
                 super.stopCellEditing();
              }
              try
              {
                  value = nf.parse(s).doubleValue();
              }
              catch (Exception e)
              {
                 textField.setBorder(new LineBorder(Color.red));
                 return false;
              }
              return super.stopCellEditing();
           }
    
           public Component getTableCellEditorComponent(JTable table, Object value,
                                                        boolean isSelected,
                                                        int row, int col)
           {
              this.value = (Double) value;
              textField.setBorder(new LineBorder(Color.black));
              return super.getTableCellEditorComponent(
                              table, value, isSelected, row, col);
           }
    
           public Object getCellEditorValue()
           {
              value = (Double) value;
              return value;
           }
        }
    
        public static void main( String[] args )
        {
            ExcelTableTest excelTest = new ExcelTableTest();
        }
    }
    

    这是下面描述的简单解决方案。它只需要一行 -

            tableMe.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
    

    如果我开始输入一个单元格,然后在单元格中单击鼠标,我会进入编辑器并丢失正在键入的内容。如果我点击其他任何地方,保存的内容将被保存,编辑器将被停止。

    我曾尝试过定义一个标志 -

       private boolean       reallyInEditor = true;
    

    然后覆盖editCellAt而不是准备编辑器

           @Override
           public boolean editCellAt( int row, int col, EventObject e )
           {
              boolean result = super.editCellAt( row, col, e );
    
              Component editComponent = getEditorComponent();
              if (  editComponent == null || 
                  !(editComponent instanceof JTextComponent) )
                 return result;
              if (e instanceof KeyEvent)
              {
                 ((JTextComponent) editComponent).selectAll();
                 reallyInEditor = false;
             }
    
              return result;
           }
        }
    

    设置我的标志以通过键入而不是双击

    来指示用户进入编辑器

    并将FocusListener放在表格

            tableMe.addFocusListener( new MyFocusListener() );
    
        class MyFocusListener implements FocusListener
        {
           @Override
           public void focusGained(FocusEvent e)
           {
              System.out.println("Table has focus");
           }
    
           @Override
           public void focusLost(FocusEvent e)
           {
              System.out.println("Table lost focus");
              MyJTable        table       = (MyJTable) e.getSource();
              int[]           columns     = table.getSelectedColumns();
    //
    // I will not be allowing multiple column selection
              if ( columns.length > 0 )
              {
                 TableColumn       tableColumn = table.getColumnModel()
                                                      .getColumn(columns[0]);
                 DefaultCellEditor cellEditor  = (DefaultCellEditor)
                                                  tableColumn.getCellEditor();
                 if (cellEditor != null && !reallyInEditor)
                 {
                    System.out.println( "Editor focus: " + 
                                         cellEditor.getComponent().hasFocus() );
                    if (!cellEditor.stopCellEditing())
                    {
                       cellEditor.cancelCellEditing();
                    }
                    reallyInEditor = true;
                 }
              }
           }
        }
    

    与所有这些额外工作的区别在于,如果我开始在单元格中输入,然后单击单元格,单元格将使用我输入的值进行更新,编辑器将停止。

1 个答案:

答案 0 :(得分:4)

使用以下代码行来实现您的目标:

tableMe.putClientProperty("terminateEditOnFocusLost",Boolean.TRUE);