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 Exception。我猜问题是: 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.

4

4 回答 4

12

第一:对 JTable 编辑的输入验证没有得到很好的支持。一些评论

  • TableModelListener 中的 tableChanged 不是进行验证的好地方,此时更改已经发生(模型通知其侦听器这一事实)
  • 因此,无论您选择何种验证(验证)方法挂钩,永远不会与模型对话,您最终将陷入无限循环(如您所见)
  • 应用程序提供的 CellEditorListeners 相当没用,因为 a) 不能保证通知的顺序(JTable 可能已经更新了模型,也可能没有) b) 编辑器的生命周期定义不明确

毕竟那些(不完整,不幸的是;-)没有,有点希望:最好的办法是实现一个自定义 CellEditor,它在 stopCellCellEditing 中进行验证:如果新值无效,则返回 false 并可选地提供视觉错误反馈。查看 JTable.GenericEditor 以了解如何完成

于 2011-05-03T21:52:43.200 回答
4

什么对我有用(给克娄巴特拉戴上帽子):

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()));
于 2013-04-12T16:20:30.247 回答
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;
            }
        }
    }
}
于 2016-07-16T17:20:28.057 回答
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);
     }
          
    }
于 2021-02-23T22:46:10.640 回答