JTable输入验证器

7

我正在尝试为JTable创建一个简单的输入验证器。

最终我重写了editingStopped()方法。

问题在于,事件并没有包含有关已更新单元格的信息。

以下是我的“伪代码”:

  If (user finished editing a cell)  {
     Check if cell`s value is "1" or "0" or "-"  (Karnaugh-Veitch)
     If (check = false)
        setValue (cell, "");
   }

我尝试的第一个方法是这个:

table.getModel().addTableModelListener(new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent e) {
                inputVerify (e.getColumn(), e.getFirstRow());
            }
});

    public void inputVerify (int column, int row) {
        boolean verified = true;
        String field = table.getValueAt(row, column).toString();

        if (field != null && field.length() == 1) {
            if ( !(field.charAt(0) == '0' || field.charAt(0) == '1' || field.charAt(0) == '-' ))
                verified = false;
        }
        else {
            verified = false;
        }

        if (!verified) {
            table.getModel().setValueAt("", row, column);
            java.awt.Toolkit.getDefaultToolkit().beep();
        }

        System.out.println ("Column = " + column + " Row = " + row + " Value = " + table.getValueAt(row, column) +" Verified = "+verified);
    }

但这会导致一个:StackOverflow异常。我猜问题在于:setValueAt(..)触发了另一个tableChanged()事件,从而生成了一个无限循环。
现在,我尝试了这个:
    table.getDefaultEditor(Object.class).addCellEditorListener(new CellEditorListener() {

        // called when editing stops
        public void editingStopped(ChangeEvent e) {

            // print out the value in the TableCellEditor
            System.out.println(((CellEditor) e.getSource()).getCellEditorValue().toString());

        }

        public void editingCanceled(ChangeEvent e) {
            // whatever
        }
    });

但是,正如您所看到的,我只能检索单元格的新值,而不能检索“坐标”。 我需要调用:setValueAt(..)方法,但我不知道如何获取单元格的坐标。

还是有更简单的方法来创建输入验证器吗?

最好的问候 Ioannis K.


不需要setValueAt,因此您不需要坐标 :-) - kleopatra
4个回答

12

首先,JTable编辑的输入验证支持不够好。以下是一些注释:

  • 在TableModelListener中进行tableChanged操作并不是一个好的验证方法,因为在此时更改已经发生(模型会通知其侦听器)
  • 因此,无论您选择哪种验证方法(verify),都绝不能回应模型,否则您将陷入无限循环(正如您所看到的)
  • 由于a)没有关于通知顺序的保证(JTable可能已经更新了模型),b)编辑器的生命周期定义不清晰,因此提供的CellEditorListeners有点无用。

在这些(不完整的,遗憾的)禁忌之后,有一点希望:最好的方法是实现一个自定义的CellEditor,在stopCellEditing中进行验证:如果新值无效,则返回false,并可选地提供视觉错误反馈。可以查看JTable.GenericEditor以了解如何完成此操作。


谢谢Kleopatra提供这个好用而有用的澄清! 我意识到,在我的情况下,输入验证器非常不方便。 不仅对我来说如此,对用户也是如此。 我决定添加/修改CellEditor。 最终,我使用DefaultCellEditor和JComboBox作为构造函数参数。 每当用户想要编辑单元格时, 一个带有条目“0”“1”和“-”的JComboBox出现。 这样就不需要检查有效输入,对用户来说更加舒适 :-) - Ioannis K.

4

以下是对我有效的方法(向kleopatra致敬):

private class CellEditor extends DefaultCellEditor {

    InputVerifier verifier = null;

    public CellEditor(InputVerifier verifier) {
        super(new JTextField());
        this.verifier = verifier;

    }

    @Override
    public boolean stopCellEditing() {
        return verifier.verify(editorComponent) && super.stopCellEditing();
    }

}

// ...

private class PortVerifier extends InputVerifier {

    @Override
    public boolean verify(JComponent input) {
        boolean verified = false;
        String text = ((JTextField) input).getText();
        try {
            int port = Integer.valueOf(text);
            if ((0 < port) && (port <= 65535)) {
                input.setBackground(Color.WHITE);
                verified = true;
            } else {
                input.setBackground(Color.RED);
            }
        } catch (NumberFormatException e) {
            input.setBackground(Color.RED);
        }
        return verified;
    }
}

// ...

table.getColumn("Port").setCellEditor(new CellEditor(new PortVerifier()));

只是要提醒一下:严格来说,您的verify实现并不完全正确,因为不允许具有副作用。尽管在这里副作用仅限于设置背景,但您可能会遇到问题。 - kleopatra

1
嗯,可能有一个更简单的解决方案。请尝试这个方法,它对我有效。 关键是要记住上次选择的项目,然后对当前项目进行验证。 如果输入有误,您可以回滚到上次选择的项目,并通知用户。 回滚使用EventQueue.invokeLater(...)执行,从而避免递归调用监听器。
private final DefaultTableModel dtm = new DefaultTableModel();
private final JTable table = new JTable(dtm);
private final Object[] lastItem;
private final AtomicInteger lastIndex = new AtomicInteger(-1);
private final ItemValidator validator = new ItemValidator();


public YourConstructor() {

    lastItem = new Object[table.getColumnCount()];


    //store last value of selected table item in an array.
    table.addMouseListener(new MouseAdapter(){
        public void mouseClicked(MouseEvent evt){
            lastIndex.set(table.getSelectedRow());
            int row = lastIndex.get();
            for(int i=0;i<lastItem.length;i++){
                lastItem[i] = table.getValueAt(row, i);
            }
        }
    });

    //for input validation, and database update.
    dtm.addTableModelListener(new TableModelListener(){

        @Override
        public void tableChanged(TableModelEvent e) {
            switch(e.getType()){
            case TableModelEvent.INSERT:
                System.out.println("insert");
                break;
            case TableModelEvent.UPDATE:
                validateUpdate();
                break;
            case TableModelEvent.DELETE:
                System.out.println("delete");
                break;
            default:
                break;
            }
        }

    });
}

public void validateUpdate(){
    String item;
    for(int i=0;i<lastItem.length;i++)
    {
        item = (String)table.getValueAt(lastIndex.get(), i);
        if(i>1 && i<lastItem.length)//column range to be checked
        {
            if(!validator.hasNumericText(item))
            {
                final int col = i;
                final Object lastObject = lastItem[i];
                final int row = lastIndex.get();

                //the most important part, to avoid StackOverflow
                //by using EventQueue, you avoid looping around 
                //the TableModelListener.
                EventQueue.invokeLater(new Runnable(){
                    public void run(){
                        table.setValueAt(lastObject, row, col);
                    }
                });

                System.out.println("Error at " + i);
                break;
            }
        }
    }
}

0

我同意将验证器保持在JTextField上下文之外(验证器可以从其他不是表格编辑的输入字段中重用)。如果您只想在继续编辑时显示错误消息,则可以将验证代码轻松附加到TableCellEditor的getCellEditorValue()方法上。您可以将掩码附加到文本字段,也可以通过传递格式化程序在此处格式化条目...取决于您的要求;以下是最小代码:

    public class ObjectDefaultCellEditor extends javax.swing.DefaultCellEditor {
        
        protected InputVerifier inputVerifier;
        
         public static final String ERROR_MESSAGE_START = "INVALID: ";  
        /**
         * ObjectAbstractCellEditor constructor comment.
         * @param textField javax.swing.JTextField
         */
        public ObjectDefaultCellEditor(javax.swing.JTextField textField) {
            super(textField);
            super.setClickCountToStart(1);

            textField.setBorder(null);
            editorComponent = textField;
        }
        
        /**
         *  
         * @param textField
         * @param inputVerifier
         */
            public ObjectDefaultCellEditor(JFormattedTextField textField, InputVerifier inputVerifier) {
                this(textField);
                this.inputVerifier = inputVerifier;
                
    //          if (inputVerifier != null) {
    //              textField.setInputVerifier(inputVerifier);
    //          }
                
    //          if (maskFormatter != null) {
    //              maskFormatter.install(textField);    // we did decouple creation, so we can pass formatter as an  option
    //          }
                
            }

    /**
     * called by Jtable to get the cell Object value. This value will then be set in table model
     */
    public Object getCellEditorValue() {
            //  Object value = super.getCellEditorValue();  // this resumes to delegate.getCellEditorValue()
            //  Object formattedValue = formatMyValue( value) ;
        
        // call validator
        if (inputVerifier != null) {
            boolean valid = inputVerifier.verify(editorComponent);
            if (!valid) {
                showErrorMessage ((JTextComponent)editorComponent);     
            }
        }
        
        return delegate.getCellEditorValue();
    }

    /**
     * show error on entry field
     * @param aTextComponent
     */
     private void showErrorMessage(JTextComponent aTextComponent) {
              
              if (! isShowingErrorMessage((JTextComponent)editorComponent)) {
          
                StringBuilder message = new StringBuilder(ERROR_MESSAGE_START);
                message.append("\"");
                message.append(aTextComponent.getText());
                message.append("\"");
                if (aTextComponent.getToolTipText() != null ) {
                    message.append(aTextComponent.getToolTipText());
                }
                aTextComponent.setText(message.toString());
                
              }
     }
          
     private boolean isShowingErrorMessage(JTextComponent aTextComponent){
                return aTextComponent.getText().startsWith(ERROR_MESSAGE_START);
     }
          
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接