这种简单地使用 JTable 默认单元格编辑器来验证文本的方法不起作用。有人可以帮助理解为什么吗?

This simple use of JTable default cell editor to validate text does not work. Can someone help understand why?

我想在让用户结束编辑之前验证输入到 JTable 单元格中的数据是否正确。在这个 link 的 Oracle 教程中 https://docs.oracle.com/javase/tutorial/uiswing/components/table.html#validtext 他们让它听起来很简单。摘录说

The automatic checking of user-entered strings occurs when the default editor attempts to create a new instance of the class associated with the cell's column. The default editor creates this instance using a constructor that takes a String as an argument. For example, in a column whose cells have type Integer, when the user types in "123" the default editor creates the corresponding Integer using code equivalent to new Integer("123"). If the constructor throws an exception, the cell's outline turns red and refuses to let focus move out of the cell. If you implement a class used as a column data type, you can use the default editor if your class supplies a constructor that takes a single argument of type String.

为了测试这个,我创建了我能想到的最简单的案例,使用这个 class:

    class CustomClass {
        int value = 0;
        public CustomClass(String newValue) throws NumberFormatException {
            value = Integer.parseInt(newValue);
            if(value<0 || value>100) throw new NumberFormatException("value out of range");
        }
        public String toString() {return value+"";}
    }

主要思想是,如果自定义 class 有一个只有字符串的构造函数和一个 toString() 方法,它应该可以工作。我将 table 设置为 CustomClass 作为第一列的类型。它不会产生预期或期望的结果。任何编辑这些单元格的尝试都会导致单元格以红色勾勒出轮廓,并且无法完成编辑。如果输入的数据错误,这就是所描述的行为。 这是发生了什么:

我知道还有很多其他方法可以做到这一点。但是这种方法很有吸引力,因为它提供了使用默认编辑器和渲染器的希望,而在自定义 class(无论如何我都需要)中只需很少的额外工作。但它似乎并不像宣传的那样有效。我希望被证明是错误的 ;-)

完整的程序贴在下面。添加了一些打印件以帮助我了解发生了什么,但它们从未在预期时间打印出来。也就是说,似乎没有调用构造函数和 toString() 方法来验证和 return 输入的文本。有人知道为什么吗?谢谢。

----------------完整的测试程序------------

import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
final public class TestCodeGUI {
    private JFrame frame;
    private JTable table;
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TestCodeGUI window = new TestCodeGUI();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    public TestCodeGUI() {
        initialize();
    }
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 370, 168);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(null);
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setBounds(10, 11, 330, 103);
        frame.getContentPane().add(scrollPane);
        table = new JTable();
        scrollPane.setViewportView(table);
        table.setModel(new DefaultTableModel(
            new Object[][] {
                {new CustomClass("00"), Integer.parseInt("01"), "02"},
                {new CustomClass("10"), Integer.parseInt("11"), "12"},
                {new CustomClass("20"), Integer.parseInt("21"), "22"},
                {new CustomClass("30"), Integer.parseInt("31"), "32"},
            },
            new String[] {
                "Column 0", "Column 1", "Column 2"
            }
        ) {
            Class[] columnTypes = new Class[] {
                CustomClass.class, Integer.class, String.class
            };
            public Class getColumnClass(int columnIndex) {
                return columnTypes[columnIndex];
            }
        });
        System.out.println("Initialize complete");
    }
}

class CustomClass {
    int value = 0;
    public CustomClass(String newValue) throws NumberFormatException {
        System.out.println("CustomClass constructor with \"" + newValue + "\"");
        value = Integer.parseInt(newValue);
        if(value<0 || value>100) throw new NumberFormatException("value out of range");
    }
    public String toString() {
        String s = value+"";
        System.out.println("CustomClass toString returning \"" + s + "\"");
        return s;
    }
}

查看 JTable class 的源代码,您会在其中找到 GenericEditor class,它是 table 的默认编辑器。 stopCellEditing()方法负责设置红色边框。

我尝试使用以下代码在您的 class 中复制 stopCellEditing() 逻辑:

System.out.println("Initialize complete");
System.out.println(table.getDefaultEditor(Object.class));
System.out.println(table.getDefaultEditor(CustomClass.class));

Class[] argTypes = new Class[]{String.class};
java.lang.reflect.Constructor constructor;
//Class<?> type = table.getColumnClass(column);
Class<?> type = CustomClass.class;

//String s = (String)super.getCellEditorValue();
String s = "25";

try
{
    //SwingUtilities2.checkAccess(constructor.getModifiers());
    constructor = type.getConstructor(argTypes);
    Object value = constructor.newInstance(new Object[]{s});
    System.out.println(value + " : " + value.getClass());
}
catch (Exception e)
{
    System.out.println(e);
    //((JComponent)getComponent()).setBorder(new LineBorder(Color.red));
    //return false;
}

并且代码似乎可以正常工作,因为创建了一个新的 CustomClass 对象。但是,我的代码无法调用 SwingUtilities2.checkAccess(...) 方法,所以也许这就是问题所在?

无论如何,我没有发现您正在测试的内容有问题。我想您需要使用调试器逐步执行真实代码,看看发生了什么。

感谢 camickr 提供的答案线索。 JTable 代码使用内省来访问自定义 class 的构造函数和 toString,因此 class 必须是 public。这样做可以解决问题。感谢 camickr。并感谢计算器。我的第一个问题在 15 小时内提出并得到了回答。