Ошибка при удалении столбца JTable из меню компонентов в том же столбце - PullRequest
0 голосов
/ 17 октября 2019

У меня есть JTable с меню компонентов, установленным для заголовка таблицы. Он содержит записи для удаления столбцов. Моя проблема возникает при удалении того же столбца, из которого было вызвано меню компонента, и это действие рассматривается как перетаскиваемый столбец, что приводит к 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();
    }
}

В трассировке стека, которую я вижучто он воспринимает столбец как перетаскиваемый, потому что он вводит условное значение if (draggedColumn != null) { в BasicTableHeaderUI.java, в то время как выполняемое мной действие на самом деле не является перетаскиванием. Полная трассировка стека приведена ниже:

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$4.run(RepaintManager.java:831)
    at javax.swing.RepaintManager$4.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.access$1200(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.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:709)
    at java.awt.EventQueue$3.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 не вариант.

Ответы [ 2 ]

1 голос
/ 20 октября 2019

Метод, который, кажется, работает, - это отменить перетаскиваемый столбец внутри действия пункта меню:

@Override
public void actionPerformed(ActionEvent e) {
    table.getTableHeader().setDraggedColumn(null);
    TableColumn tc = table.getColumn(columnName);
    table.getColumnModel().removeColumn(tc);
}

Следует отметить, что это, вероятно, предотвратит перекрашивание таблицы, если ее перетаскивать, используя правый клик, идействие не выбрано или действие не вызывает перерисовку таблицы.

1 голос
/ 18 октября 2019

Случайно у нас возникает такая же ошибка в нашей среде, но действительно спорадическая, поэтому я не смог ее воспроизвести. Но ваш пример идеален, и я смог воспроизвести мою ошибку. См. Код ниже.

Я изменил свой ответ во время расследования. Старые комментарии не отражают последний код.

Ваша ошибка, о которой сообщают: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8068824

А вот решение, основанное на обходе ошибки:

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 распространяет события мыши на базовый компонент. Вы можете увидеть это, если замените пользовательский интерфейс на заголовок таблицы. И вот как я воспроизвел исключение с помощью нашего кода: нажмите правую кнопку мыши на заголовке, но не отпускайте его и продолжайте перемещать мышь над всплывающим меню вниз от заголовка. Отпустите правую кнопку, когда указатель будет удален от заголовка (чтобы заголовок не обрабатывал событие MOUSE_RELEASED). Получите исключение.

...