6

升级到 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 
""
""
""
""
""
$

我已经搜索了错误,但我没有想出任何东西。这是一个已知问题吗?

4

3 回答 3

1

在使用该表格示例时,似乎存在不止一个错误。

我使用了以下 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在使表格可编辑时被调用。但是,不是使用空字符串,而是使用 my 中包含的实际值TableModel。这意味着我的数据没有任何变化。唯一令人讨厌的是,在导航过程中,我的表格会不断更新。setValueAt解决方法当然是在值根本没有更新时使用快速退出路径调整方法,例如添加

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

     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,...

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

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事件以某种方式开始了编辑......

于 2012-11-15T22:44:43.087 回答
0

解决方法是使用:

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

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

于 2013-09-20T11:31:15.190 回答
0

只是一个更新,当我用光标键在 JTable 单元格中移动时,我仍然看到 1.7.0_40 的这个问题,这会导致它清空进入的每个字段。

添加:

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

停止该问题的发生,但是我必须按“Enter”键才能开始编辑。

相反,在您的 Jtables 模型开始时,您检查空字符串并返回将解决该问题

IE

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

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

可以在此处的 OpenJdk 错误跟踪器中找到该问题

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

目前要等到 Jdk 9 才能修复

于 2013-10-30T12:29:18.633 回答