JTable 输入验证器

发布于 2024-11-04 20:07:08 字数 1973 浏览 4 评论 0原文

我正在尝试为 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(..)方法,但我不知道如何获取单元格的坐标。

或者有没有更简单的方法来创建输入验证器?

此致 扬尼斯·K.

I am trying to create a simple Input Verifier for a JTable.
I ended up with overriding the method: editingStopped().
The problem is that the event does not include informations about the cell that has been updated.

This is my "pseudo code":

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

The first I tried was this here:

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

But this ends up with an : StackOverflow Exception. I guess the problem is that: setValueAt(..) fires another tableChanged() event and an endless loop is being generated.

Now, I tried this here:

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

But as you can see I can just retrieve the new value of the cell, not the "coordinates".
I need to call: setValueAt( .. ) method, but I dont know how to get the cell`s coordinates.

Or is there a more simple way to create an input verifier??

Best regards
Ioannis K.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

寂寞清仓 2024-11-11 20:07:08

第一:JTable 编辑的输入验证没有得到很好的支持。 TableModelListener 中的几条注释

  • tableChanged 不是进行验证的好地方,在那个时间点,更改已经发生(模型通知其侦听器这一事实),
  • 因此,无论您选择什么验证(验证)方法挂钩,永远不要与模型反驳,你最终会陷入无限循环(正如你所见)
  • 应用程序提供的 CellEditorListener 相当无用,因为 a) 无法保证通知的顺序(JTable 可能已经或尚未更新模型)b)编辑器的生命周期定义不明确

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

First: input validation on JTable editing is not well supported. A couple of comments

  • tableChanged in a TableModelListener is not a good place to do validation, at that point in time the change already happened (the model notifies its listeners of the fact)
  • as a consequence, whatever validation (verify) method hook you choose, never-ever talk back to the model, you'll end up in an infinite loop (as you have seen)
  • application-provided CellEditorListeners are rather useless because a) there's no guarantee about sequence of notification (JTable might or not have already updated the model) b) the life-cylce of an editor is ill-defined

After all those (incomplete, unfortunately ;-) no-nos, a little hope: best bet is to implement a custom CellEditor which does the validation in stopCellCellEditing: if the new value isn't valid, return false and optionally provide a visual error feedback. Have a look at JTable.GenericEditor to get an idea of how that might be done

滥情空心 2024-11-11 20:07:08

什么对我有用(向克利奥帕特拉脱帽致敬):

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

What worked for me (tip 'o the hat to 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()));
波浪屿的海角声 2024-11-11 20:07:08

嗯,可能有一个更简单的解决方案。请尝试这个,它对我有用。
关键是记住最后选择的项目,然后对当前项目进行验证。
如果输入错误,您可以回滚到最后选择的项目,并通知用户。
回滚是使用 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;
            }
        }
    }
}

hmm, there might be a simpler solution to this. Please try this, it worked for me.
The key is to remember last selected item, and then perform validation on the current item.
If input is wrong, you can roll back to the last selected item, and notify user about that.
Roll back is performed using EventQueue.invokeLater(...), therefore avoiding recursive call to the listeners.

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;
            }
        }
    }
}
潜移默化 2024-11-11 20:07:08

我同意将验证器保留在 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);
     }
          
    }

I agree with keeping the verifier out of JTextField context (verifier could be reused from other Entry fields that are not table edits). If you just want to display an error message while continuing editing, verification code could be easily attached to getCellEditorValue() method on TableCellEditor. You may attach a mask to the text field, you may format entry here by passing formatters...up to your reqs; minimal code bellow:

    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);
     }
          
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文