从同一列的组件菜单中删除 JTable 列时出错
Error when removing JTable column from component menu on same column
我有一个 JTable
,其中为 table header 设置了组件菜单。它包含用于删除列的条目。我的问题发生在删除触发组件菜单的同一列时,它似乎将此操作视为正在拖动的列,这导致 ArrayIndexOutOfBoundsException
和 -1
。
如何安全地从组件菜单中删除当前列而不引发此错误?
以下是如何触发此类行为的最小示例。只需 运行 它并删除您所在的同一列(请注意,当您删除最后一列时它似乎有效,但这与我几乎无关):
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
public class BasicExample extends JFrame {
private JTable t;
private DefaultTableModel dtm;
public BasicExample() {
init();
}
private void init() {
dtm = new DefaultTableModel(new String[][]{{"a", "b"}, {"c", "d"}}, new String[]{"A", "B"});
t = new JTable(dtm);
t.getTableHeader().setComponentPopupMenu(new PopupMenu(t));
this.setLayout(new BorderLayout());
add(t.getTableHeader(), BorderLayout.NORTH);
add(t, BorderLayout.CENTER);
pack();
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public class PopupMenu extends JPopupMenu {
private JTable table;
public PopupMenu(JTable table) {
this.table = table;
init();
}
private void init() {
for (int i = 0; i < table.getModel().getColumnCount(); i++) {
String columnName = table.getModel().getColumnName(i);
JMenuItem item = new JMenuItem(columnName);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TableColumn tc = table.getColumn(columnName);
table.getColumnModel().removeColumn(tc);
}
});
this.add(item);
}
}
}
public static void main(String[] args) {
BasicExample be = new BasicExample();
}
}
在堆栈跟踪中,我看到它似乎将该列视为已拖动,因为它在 BasicTableHeaderUI.java
中输入了条件 if (draggedColumn != null) {
,而我正在执行的操作实际上并不是拖。完整的堆栈跟踪如下:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.Vector.elementData(Vector.java:734)
at java.util.Vector.elementAt(Vector.java:477)
at javax.swing.table.DefaultTableColumnModel.getColumn(DefaultTableColumnModel.java:294)
at javax.swing.plaf.basic.BasicTableHeaderUI.getHeaderRenderer(BasicTableHeaderUI.java:693)
at javax.swing.plaf.basic.BasicTableHeaderUI.paintCell(BasicTableHeaderUI.java:709)
at javax.swing.plaf.basic.BasicTableHeaderUI.paint(BasicTableHeaderUI.java:685)
at javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
at javax.swing.JComponent.paintComponent(JComponent.java:780)
at javax.swing.JComponent.paint(JComponent.java:1056)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5210)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1579)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1502)
at javax.swing.RepaintManager.paint(RepaintManager.java:1272)
at javax.swing.JComponent._paintImmediately(JComponent.java:5158)
at javax.swing.JComponent.paintImmediately(JComponent.java:4969)
at javax.swing.RepaintManager.run(RepaintManager.java:831)
at javax.swing.RepaintManager.run(RepaintManager.java:814)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:814)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:789)
at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:738)
at javax.swing.RepaintManager.access00(RepaintManager.java:64)
at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1732)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
at java.awt.EventQueue.access0(EventQueue.java:97)
at java.awt.EventQueue.run(EventQueue.java:709)
at java.awt.EventQueue.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
如果相关,这发生在 Java 8。更改 JRE/JDK 不是一个选项。
不小心我们的环境中出现了同样的错误,但确实偶尔出现,所以我无法重现它。
但是您的示例非常完美,我能够重现我的错误。请参阅下面的代码。
我在调查期间修改了我的答案。旧注释不能反映最新代码。
你的是一个已报告的错误:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8068824
这是一个基于 bug 解决方法的解决方案:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
public class BasicExample extends JFrame {
private JTable t;
private DefaultTableModel dtm;
public BasicExample() {
init();
}
private void init() {
dtm = new DefaultTableModel(new String[][] { { "a", "b" }, { "c", "d" } },
new String[] { "A", "B" });
t = new JTable(dtm);
t.getTableHeader().setComponentPopupMenu(new PopupMenu(t));
this.setLayout(new BorderLayout());
add(t.getTableHeader(), BorderLayout.NORTH);
add(t, BorderLayout.CENTER);
pack();
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public class PopupMenu extends JPopupMenu {
private JTable table;
public PopupMenu(JTable table) {
this.table = table;
init();
}
private void init() {
for (int i = 0; i < table.getModel().getColumnCount(); i++) {
String columnName = table.getModel().getColumnName(i);
JMenuItem item = new JMenuItem(columnName);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TableColumn tc = table.getColumn(columnName);
t.getTableHeader().setDraggedColumn(null);
table.getColumnModel().removeColumn(tc);
}
});
this.add(item);
}
}
}
public static void main(String[] args) {
BasicExample be = new BasicExample();
}
}
删除前只需将 draggedColumn 设置为 null。
我一直在挖掘 因为它没有解释为什么在我们的环境中这个错误很少发生并且只在 QA 中发生。 :)
这是代码在我们的环境中的样子(再次感谢非常好的小例子)。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
public class BasicExample extends JFrame {
private JTable t;
private DefaultTableModel dtm;
public BasicExample() {
init();
}
private void init() {
dtm = new DefaultTableModel(new String[][] { { "a", "b" }, { "c", "d" } },
new String[] { "A", "B" });
t = new JTable(dtm);
PopupMenu lPopupMenu = new PopupMenu(t);
// t.getTableHeader().setComponentPopupMenu(new PopupMenu(t));
t.getTableHeader().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent pE) {
lPopupMenu.show(pE.getComponent(), pE.getX(), pE.getY());
}
});
this.setLayout(new BorderLayout());
add(t.getTableHeader(), BorderLayout.NORTH);
add(t, BorderLayout.CENTER);
pack();
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public class PopupMenu extends JPopupMenu {
private JTable table;
public PopupMenu(JTable table) {
this.table = table;
init();
}
private void init() {
for (int i = 0; i < table.getModel().getColumnCount(); i++) {
String columnName = table.getModel().getColumnName(i);
JMenuItem item = new JMenuItem(columnName);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TableColumn tc = table.getColumn(columnName);
// t.getTableHeader().setDraggedColumn(null);
table.getColumnModel().removeColumn(tc);
}
});
this.add(item);
}
}
}
public static void main(String[] args) {
BasicExample be = new BasicExample();
}
}
与您的代码不同的是我们不使用setComponentPopupMenu(),而是手动显示PopupMenu。并且您的用例不会在我们的代码中产生异常。
为什么?
好问题,我没有答案。
您的代码 JPopupMenu 以某种方式消耗鼠标事件。并使用我们的代码 JPopupMenu 将鼠标事件传播到底层组件。用UI代替tableheader.
就可以看到了
这就是我用我们的代码重现异常的方式:在 header 上按下鼠标右键,但不要释放它并继续将鼠标移到弹出窗口上,远离 header。当指针离开 header 时松开右键(这样 header 就不会处理 MOUSE_RELEASED 事件)。获取异常。
一种似乎有效的方法是取消设置菜单项操作中的拖动列:
@Override
public void actionPerformed(ActionEvent e) {
table.getTableHeader().setDraggedColumn(null);
TableColumn tc = table.getColumn(columnName);
table.getColumnModel().removeColumn(tc);
}
应该注意的是,如果使用右键单击拖动 table 并且未选择任何操作或该操作不会导致 table 重绘,这可能会阻止重绘 table。
我有一个 JTable
,其中为 table header 设置了组件菜单。它包含用于删除列的条目。我的问题发生在删除触发组件菜单的同一列时,它似乎将此操作视为正在拖动的列,这导致 ArrayIndexOutOfBoundsException
和 -1
。
如何安全地从组件菜单中删除当前列而不引发此错误?
以下是如何触发此类行为的最小示例。只需 运行 它并删除您所在的同一列(请注意,当您删除最后一列时它似乎有效,但这与我几乎无关):
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
public class BasicExample extends JFrame {
private JTable t;
private DefaultTableModel dtm;
public BasicExample() {
init();
}
private void init() {
dtm = new DefaultTableModel(new String[][]{{"a", "b"}, {"c", "d"}}, new String[]{"A", "B"});
t = new JTable(dtm);
t.getTableHeader().setComponentPopupMenu(new PopupMenu(t));
this.setLayout(new BorderLayout());
add(t.getTableHeader(), BorderLayout.NORTH);
add(t, BorderLayout.CENTER);
pack();
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public class PopupMenu extends JPopupMenu {
private JTable table;
public PopupMenu(JTable table) {
this.table = table;
init();
}
private void init() {
for (int i = 0; i < table.getModel().getColumnCount(); i++) {
String columnName = table.getModel().getColumnName(i);
JMenuItem item = new JMenuItem(columnName);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TableColumn tc = table.getColumn(columnName);
table.getColumnModel().removeColumn(tc);
}
});
this.add(item);
}
}
}
public static void main(String[] args) {
BasicExample be = new BasicExample();
}
}
在堆栈跟踪中,我看到它似乎将该列视为已拖动,因为它在 BasicTableHeaderUI.java
中输入了条件 if (draggedColumn != null) {
,而我正在执行的操作实际上并不是拖。完整的堆栈跟踪如下:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.Vector.elementData(Vector.java:734)
at java.util.Vector.elementAt(Vector.java:477)
at javax.swing.table.DefaultTableColumnModel.getColumn(DefaultTableColumnModel.java:294)
at javax.swing.plaf.basic.BasicTableHeaderUI.getHeaderRenderer(BasicTableHeaderUI.java:693)
at javax.swing.plaf.basic.BasicTableHeaderUI.paintCell(BasicTableHeaderUI.java:709)
at javax.swing.plaf.basic.BasicTableHeaderUI.paint(BasicTableHeaderUI.java:685)
at javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
at javax.swing.JComponent.paintComponent(JComponent.java:780)
at javax.swing.JComponent.paint(JComponent.java:1056)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5210)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1579)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1502)
at javax.swing.RepaintManager.paint(RepaintManager.java:1272)
at javax.swing.JComponent._paintImmediately(JComponent.java:5158)
at javax.swing.JComponent.paintImmediately(JComponent.java:4969)
at javax.swing.RepaintManager.run(RepaintManager.java:831)
at javax.swing.RepaintManager.run(RepaintManager.java:814)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:814)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:789)
at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:738)
at javax.swing.RepaintManager.access00(RepaintManager.java:64)
at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1732)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
at java.awt.EventQueue.access0(EventQueue.java:97)
at java.awt.EventQueue.run(EventQueue.java:709)
at java.awt.EventQueue.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
如果相关,这发生在 Java 8。更改 JRE/JDK 不是一个选项。
不小心我们的环境中出现了同样的错误,但确实偶尔出现,所以我无法重现它。 但是您的示例非常完美,我能够重现我的错误。请参阅下面的代码。
我在调查期间修改了我的答案。旧注释不能反映最新代码。
你的是一个已报告的错误: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8068824
这是一个基于 bug 解决方法的解决方案:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
public class BasicExample extends JFrame {
private JTable t;
private DefaultTableModel dtm;
public BasicExample() {
init();
}
private void init() {
dtm = new DefaultTableModel(new String[][] { { "a", "b" }, { "c", "d" } },
new String[] { "A", "B" });
t = new JTable(dtm);
t.getTableHeader().setComponentPopupMenu(new PopupMenu(t));
this.setLayout(new BorderLayout());
add(t.getTableHeader(), BorderLayout.NORTH);
add(t, BorderLayout.CENTER);
pack();
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public class PopupMenu extends JPopupMenu {
private JTable table;
public PopupMenu(JTable table) {
this.table = table;
init();
}
private void init() {
for (int i = 0; i < table.getModel().getColumnCount(); i++) {
String columnName = table.getModel().getColumnName(i);
JMenuItem item = new JMenuItem(columnName);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TableColumn tc = table.getColumn(columnName);
t.getTableHeader().setDraggedColumn(null);
table.getColumnModel().removeColumn(tc);
}
});
this.add(item);
}
}
}
public static void main(String[] args) {
BasicExample be = new BasicExample();
}
}
删除前只需将 draggedColumn 设置为 null。
我一直在挖掘 因为它没有解释为什么在我们的环境中这个错误很少发生并且只在 QA 中发生。 :)
这是代码在我们的环境中的样子(再次感谢非常好的小例子)。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
public class BasicExample extends JFrame {
private JTable t;
private DefaultTableModel dtm;
public BasicExample() {
init();
}
private void init() {
dtm = new DefaultTableModel(new String[][] { { "a", "b" }, { "c", "d" } },
new String[] { "A", "B" });
t = new JTable(dtm);
PopupMenu lPopupMenu = new PopupMenu(t);
// t.getTableHeader().setComponentPopupMenu(new PopupMenu(t));
t.getTableHeader().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent pE) {
lPopupMenu.show(pE.getComponent(), pE.getX(), pE.getY());
}
});
this.setLayout(new BorderLayout());
add(t.getTableHeader(), BorderLayout.NORTH);
add(t, BorderLayout.CENTER);
pack();
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public class PopupMenu extends JPopupMenu {
private JTable table;
public PopupMenu(JTable table) {
this.table = table;
init();
}
private void init() {
for (int i = 0; i < table.getModel().getColumnCount(); i++) {
String columnName = table.getModel().getColumnName(i);
JMenuItem item = new JMenuItem(columnName);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TableColumn tc = table.getColumn(columnName);
// t.getTableHeader().setDraggedColumn(null);
table.getColumnModel().removeColumn(tc);
}
});
this.add(item);
}
}
}
public static void main(String[] args) {
BasicExample be = new BasicExample();
}
}
与您的代码不同的是我们不使用setComponentPopupMenu(),而是手动显示PopupMenu。并且您的用例不会在我们的代码中产生异常。
为什么?
好问题,我没有答案。
您的代码 JPopupMenu 以某种方式消耗鼠标事件。并使用我们的代码 JPopupMenu 将鼠标事件传播到底层组件。用UI代替tableheader.
就可以看到了
这就是我用我们的代码重现异常的方式:在 header 上按下鼠标右键,但不要释放它并继续将鼠标移到弹出窗口上,远离 header。当指针离开 header 时松开右键(这样 header 就不会处理 MOUSE_RELEASED 事件)。获取异常。
一种似乎有效的方法是取消设置菜单项操作中的拖动列:
@Override
public void actionPerformed(ActionEvent e) {
table.getTableHeader().setDraggedColumn(null);
TableColumn tc = table.getColumn(columnName);
table.getColumnModel().removeColumn(tc);
}
应该注意的是,如果使用右键单击拖动 table 并且未选择任何操作或该操作不会导致 table 重绘,这可能会阻止重绘 table。