在OS X Lion上使用Java 7中的JTable进行setValueAt的虚假调用?

在升级到Lion和Java 7之后,我遇到了JTables的问题。 当我使用箭头键移动选择时,它调用带有空字符串的setValueAt()作为编辑值。

为了测试这个,我创建了一个带有表的简单JFrame,并将以下类设置为其模型。

 public class SpyModel extends AbstractTableModel { public int getColumnCount() { return 5; } public int getRowCount() { return 5; } public Object getValueAt(int rowIndex, int columnIndex) { return ""; } public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { System.out.println(aValue == null ? "null" : "\"" + aValue + "\""); } } 

当我在Java 6下运行它,然后使用箭头键移动它。 它工作正常。 例如

 $ java -version java version "1.6.0_33" Java(TM) SE Runtime Environment (build 1.6.0_33-b03-424-11M3720) Java HotSpot(TM) 64-Bit Server VM (build 20.8-b03-424, mixed mode) $ java -jar JavaApplication5.jar 

但是,当我在Java 7(在Lion上)运行它,并使用箭头键移动选择时,它最终调用带有空字符串的setValueAt()

例如

 $ java -version java version "1.7.0_05" Java(TM) SE Runtime Environment (build 1.7.0_05-b06) Java HotSpot(TM) 64-Bit Server VM (build 23.1-b03, mixed mode) $ java -jar JavaApplication5.jar "" "" "" "" "" $ 

我搜索过bug,但我还没有想出任何东西。 这是一个已知的问题吗?

在玩这个表示例时,似乎有一个以上的错误。

我使用了以下SSCCE,它在JDK1.6下按预期工作

 import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; public class TableBugDemo { public static void main( String[] args ) { JFrame frame = new JFrame( "TestFrame" ); final JTable table = new JTable( new SpyModel() ); table.getSelectionModel().addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged( ListSelectionEvent e ) { Thread.dumpStack(); System.out.println(table.getSelectedRow()); } } ); frame.add( table ); frame.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); } public static class SpyModel extends DefaultTableModel{ public SpyModel() { super( new String[][]{ new String[]{ "row1-1", "row1-2", "row1-3"}, new String[]{ "row2-1", "row2-2", "row2-3"}, new String[]{ "row3-1", "row3-2", "row3-3"}, new String[]{ "row4-1", "row4-2", "row4-3"}, }, new String[]{"col1", "col2", "col3"}); } @Override public void setValueAt( Object aValue, int row, int column ) { System.out.println( "TableBugDemo$SpyModel.setValueAt" ); Thread.dumpStack(); super.setValueAt( aValue, row, column ); } @Override public boolean isCellEditable( int row, int column ) { return false; } } } 

但是在JDK1.7下:

  • 我看到在使表格可编辑时调用setValueAt 。 但是,不是使用空字符串,而是使用TableModel中包含的实际值。 这意味着没有任何内容更改为我的数据。 唯一令人烦恼的是,在导航过程中我的表不断更新。 解决方法当然是在没有更新值时使用快速退出路径调整setValueAt方法,例如添加

     if ( ( aValue != null && aValue.equals( getValueAt( row, column ) ) ) || ( aValue == null && getValueAt( row, column ) == null ) ){ return; } 
  • 使用向上和向下箭头导航使选择一次跳跃2行。 Stacktraces揭示了选择的变化源自BasicTableUI BasicTableUI#Actions类(这是有意义的,因为这是放置在动作映射中的动作)。 奇怪的是,对于一次按键,此动作会被触发两次。 这已经解释了为什么选择一次跳2行。 进一步调试显示,一旦我收到两个不同的KEY_PRESSED事件,就按下我的箭头键。 据我所知,这些事件就像在EventQueue上那样放置,与JTable无关。 为了确保,我创建了一个不包含JTable的小型SSCCE:

      import javax.swing.JFrame; import javax.swing.WindowConstants; import java.awt.AWTEvent; import java.awt.EventQueue; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.KeyEvent; public class KeyEventBugDemo { public static void main( String[] args ) { EventQueue.invokeLater(new Runnable() { @Override public void run() { JFrame testframe = new JFrame( "testframe" ); testframe.setSize( 300,300 ); testframe.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE ); testframe.setVisible( true ); } } ); Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener() { @Override public void eventDispatched( AWTEvent event ) { if (event instanceof KeyEvent ){ KeyEvent keyevent = ( KeyEvent ) event; System.out.println( "keyevent.getKeyCode() = " + keyevent.getKeyCode() ); System.out.println( "ID = " + System.identityHashCode( keyevent ) ); System.out.println( "keyevent = " + keyevent ); } } }, AWTEvent.KEY_EVENT_MASK ); } } 

将焦点放在框架上,然后按下DOWN_ARROW会产生以下输出(剥离toString的输出以使其可读)

 keyevent.getKeyCode() = 40 ID = 960135925 keyevent = java.awt.event.KeyEvent[KEY_PRESSED,keyCode=40,... keyevent.getKeyCode() = 40 ID = 1192754471 keyevent = java.awt.event.KeyEvent[KEY_PRESSED,keyCode=40,... keyevent.getKeyCode() = 40 ID = 2012032999 keyevent = java.awt.event.KeyEvent[KEY_RELEASED,keyCode=40,... 

在这里你可以清楚地看到你得到两个混淆了JTable KEY_PRESSED事件。 使用常规字符键时不会发生这种情况

 keyevent.getKeyCode() = 65 ID = 1023134153 keyevent = java.awt.event.KeyEvent[KEY_PRESSED,keyCode=65,keyText=A, ID = 914147942 keyevent = java.awt.event.KeyEvent[KEY_TYPED,keyCode=0,keyText=Unknown keyevent.getKeyCode() = 65 ID = 986450556 keyevent = java.awt.event.KeyEvent[KEY_RELEASED,keyCode=65,keyText=A,keyChar='a', 

查看KeyEvent类中的javadoc:

KEY_TYPED(仅在生成有效的Unicode字符时生成。)

有意义的是,我在点击箭头时不会得到KEY_TYPED事件,但它触发KEY_PRESSED两次在我看来是一个错误(稍后会记录一个错误报告)。 一个解决方法可能是拦截这样的事件,而不是通过链传递,但这对我来说听起来像一个丑陋的黑客。

编辑

另一个奇怪的事情。 如果您将以下行添加到代码段

 table.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ). put( KeyStroke.getKeyStroke( 'a' ), "selectNextRow" ); 

您可以使用a跳转到下一行(默认情况下使用DOWN_ARROW触发的操作)。 由于按a时事件的顺序正确,似乎也没有调用setValueAt方法。 这让我觉得两个KEY_PRESSED事件以某种方式开始编辑……

解决方法是使用:

 putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); 

但在这种情况下,您将无法通过直接键入在单元格中开始编辑。 您必须使用鼠标开始编辑。 我还向Oracle http://bugs.sun.com/view_bug.do?bug_id=9006933发出了一个错误,但它也不可用……似乎他们遇到了系统问题。

但是我仍然看到1.7.0_40的这个问题,因为我用光标键在我的JTable单元格中移动它会使每个字段变成空白。

添加:

 table.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE) 

停止发生这个问题,但是我必须按“Enter”键开始编辑。

而是在Jtables模型的开头,检查空字符串并返回将解决问题

  if(Platform.isOSX()) { if(value.equals("")) { return; } } 

但这确实意味着如果用户实际上想要清空该字段,那么该改变将被拒绝。 在我自己的应用程序中,这不是一个问题,因为我有一个单独的deleteField()动作,他们使用。

这个问题可以在这里的OpenJdk bug跟踪器中找到

https://bugs.openjdk.java.net/browse/JDK-8025126

目前尚未到期,直到Jdk 9